Merge remote-tracking branch 'origin/main' into frame_time_graph
This commit is contained in:
commit
81c242d894
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@ -1 +1 @@
|
||||
custom: https://bevyengine.org/donate/
|
||||
custom: https://bevy.org/donate/
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/docs_improvement.md
vendored
2
.github/ISSUE_TEMPLATE/docs_improvement.md
vendored
@ -10,4 +10,4 @@ assignees: ''
|
||||
|
||||
Provide a link to the documentation and describe how it could be improved. In what ways is it incomplete, incorrect, or misleading?
|
||||
|
||||
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevyengine.org/learn/contribute/helping-out/writing-docs/) instead.
|
||||
If you have suggestions on exactly what the new docs should say, feel free to include them here. Alternatively, make the changes yourself and [create a pull request](https://bevy.org/learn/contribute/helping-out/writing-docs/) instead.
|
||||
|
||||
15
.github/workflows/ci.yml
vendored
15
.github/workflows/ci.yml
vendored
@ -13,9 +13,9 @@ env:
|
||||
CARGO_PROFILE_TEST_DEBUG: 0
|
||||
CARGO_PROFILE_DEV_DEBUG: 0
|
||||
# If nightly is breaking CI, modify this variable to target a specific nightly version.
|
||||
NIGHTLY_TOOLCHAIN: nightly-2025-05-16 # pinned until a fix for https://github.com/rust-lang/miri/issues/4323 is released
|
||||
NIGHTLY_TOOLCHAIN: nightly
|
||||
RUSTFLAGS: "-D warnings"
|
||||
BINSTALL_VERSION: "v1.12.3"
|
||||
BINSTALL_VERSION: "v1.14.1"
|
||||
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{github.ref}}
|
||||
@ -95,7 +95,7 @@ jobs:
|
||||
- name: CI job
|
||||
# To run the tests one item at a time for troubleshooting, use
|
||||
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
|
||||
run: cargo miri test -p bevy_ecs
|
||||
run: cargo miri test -p bevy_ecs --features bevy_utils/debug
|
||||
env:
|
||||
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
|
||||
RUSTFLAGS: -Zrandomize-layout
|
||||
@ -247,7 +247,7 @@ jobs:
|
||||
- name: Check wasm
|
||||
run: cargo check --target wasm32-unknown-unknown -Z build-std=std,panic_abort
|
||||
env:
|
||||
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory -D warnings"
|
||||
RUSTFLAGS: "-C target-feature=+atomics,+bulk-memory"
|
||||
|
||||
markdownlint:
|
||||
runs-on: ubuntu-latest
|
||||
@ -260,7 +260,7 @@ jobs:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Run Markdown Lint
|
||||
uses: super-linter/super-linter/slim@v7.3.0
|
||||
uses: super-linter/super-linter/slim@v7.4.0
|
||||
env:
|
||||
MULTI_STATUS: false
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
@ -272,7 +272,8 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: cargo-bins/cargo-binstall@v1.12.3
|
||||
# Update in sync with BINSTALL_VERSION
|
||||
- uses: cargo-bins/cargo-binstall@v1.14.1
|
||||
- name: Install taplo
|
||||
run: cargo binstall taplo-cli@0.9.3 --locked
|
||||
- name: Run Taplo
|
||||
@ -293,7 +294,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.32.0
|
||||
uses: crate-ci/typos@v1.34.0
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
2
.github/workflows/docs.yml
vendored
2
.github/workflows/docs.yml
vendored
@ -82,7 +82,7 @@ jobs:
|
||||
- name: Finalize documentation
|
||||
run: |
|
||||
echo "<meta http-equiv=\"refresh\" content=\"0; url=bevy/index.html\">" > target/doc/index.html
|
||||
echo "dev-docs.bevyengine.org" > target/doc/CNAME
|
||||
echo "dev-docs.bevy.org" > target/doc/CNAME
|
||||
echo $'User-Agent: *\nDisallow: /' > target/doc/robots.txt
|
||||
rm target/doc/.lock
|
||||
|
||||
|
||||
3
.github/workflows/post-release.yml
vendored
3
.github/workflows/post-release.yml
vendored
@ -49,7 +49,8 @@ jobs:
|
||||
--exclude ci \
|
||||
--exclude errors \
|
||||
--exclude bevy_mobile_example \
|
||||
--exclude build-wasm-example
|
||||
--exclude build-wasm-example \
|
||||
--exclude no_std_library
|
||||
|
||||
- name: Create PR
|
||||
uses: peter-evans/create-pull-request@v7
|
||||
|
||||
2
.github/workflows/welcome.yml
vendored
2
.github/workflows/welcome.yml
vendored
@ -43,5 +43,5 @@ jobs:
|
||||
repo: context.repo.repo,
|
||||
body: `**Welcome**, new contributor!
|
||||
|
||||
Please make sure you've read our [contributing guide](https://bevyengine.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
|
||||
Please make sure you've read our [contributing guide](https://bevy.org/learn/contribute/introduction) and we look forward to reviewing your pull request shortly ✨`
|
||||
})
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
# Contributing to Bevy
|
||||
|
||||
If you'd like to help build Bevy, start by reading this
|
||||
[introduction](https://bevyengine.org/learn/contribute/introduction). Thanks for contributing!
|
||||
[introduction](https://bevy.org/learn/contribute/introduction). Thanks for contributing!
|
||||
|
||||
298
Cargo.toml
298
Cargo.toml
@ -1,16 +1,16 @@
|
||||
[package]
|
||||
name = "bevy"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||
description = "A refreshingly simple data-driven game engine and app framework"
|
||||
exclude = ["assets/", "tools/", ".github/", "crates/", "examples/wasm/assets/"]
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
documentation = "https://docs.rs/bevy"
|
||||
rust-version = "1.85.0"
|
||||
rust-version = "1.88.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
@ -133,6 +133,7 @@ default = [
|
||||
"bevy_audio",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_core_widgets",
|
||||
"bevy_anti_aliasing",
|
||||
"bevy_gilrs",
|
||||
"bevy_gizmos",
|
||||
@ -150,6 +151,7 @@ default = [
|
||||
"bevy_text",
|
||||
"bevy_ui",
|
||||
"bevy_ui_picking_backend",
|
||||
"bevy_ui_render",
|
||||
"bevy_window",
|
||||
"bevy_winit",
|
||||
"custom_cursor",
|
||||
@ -163,6 +165,8 @@ default = [
|
||||
"vorbis",
|
||||
"webgl2",
|
||||
"x11",
|
||||
"debug",
|
||||
"zstd_rust",
|
||||
]
|
||||
|
||||
# Recommended defaults for no_std applications
|
||||
@ -245,6 +249,15 @@ bevy_render = ["bevy_internal/bevy_render", "bevy_color"]
|
||||
# Provides scene functionality
|
||||
bevy_scene = ["bevy_internal/bevy_scene", "bevy_asset"]
|
||||
|
||||
# Provides raytraced lighting (experimental)
|
||||
bevy_solari = [
|
||||
"bevy_internal/bevy_solari",
|
||||
"bevy_asset",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_pbr",
|
||||
"bevy_render",
|
||||
]
|
||||
|
||||
# Provides sprite functionality
|
||||
bevy_sprite = [
|
||||
"bevy_internal/bevy_sprite",
|
||||
@ -267,6 +280,9 @@ bevy_ui = [
|
||||
"bevy_anti_aliasing",
|
||||
]
|
||||
|
||||
# Provides rendering functionality for bevy_ui
|
||||
bevy_ui_render = ["bevy_internal/bevy_ui_render", "bevy_render", "bevy_ui"]
|
||||
|
||||
# Windowing layer
|
||||
bevy_window = ["bevy_internal/bevy_window"]
|
||||
|
||||
@ -291,8 +307,11 @@ bevy_log = ["bevy_internal/bevy_log"]
|
||||
# Enable input focus subsystem
|
||||
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
|
||||
|
||||
# Use the configurable global error handler as the default error handler.
|
||||
configurable_error_handler = ["bevy_internal/configurable_error_handler"]
|
||||
# Headless widget collection for Bevy UI.
|
||||
bevy_core_widgets = ["bevy_internal/bevy_core_widgets"]
|
||||
|
||||
# Feathers widget collection.
|
||||
experimental_bevy_feathers = ["bevy_internal/bevy_feathers"]
|
||||
|
||||
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
|
||||
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
|
||||
@ -319,6 +338,9 @@ trace = ["bevy_internal/trace", "dep:tracing"]
|
||||
# Basis Universal compressed texture support
|
||||
basis-universal = ["bevy_internal/basis-universal"]
|
||||
|
||||
# Enables compressed KTX2 UASTC texture output on the asset processor
|
||||
compressed_image_saver = ["bevy_internal/compressed_image_saver"]
|
||||
|
||||
# BMP image format support
|
||||
bmp = ["bevy_internal/bmp"]
|
||||
|
||||
@ -367,8 +389,11 @@ webp = ["bevy_internal/webp"]
|
||||
# For KTX2 supercompression
|
||||
zlib = ["bevy_internal/zlib"]
|
||||
|
||||
# For KTX2 supercompression
|
||||
zstd = ["bevy_internal/zstd"]
|
||||
# For KTX2 Zstandard decompression using pure rust [ruzstd](https://crates.io/crates/ruzstd). This is the safe default. For maximum performance, use "zstd_c".
|
||||
zstd_rust = ["bevy_internal/zstd_rust"]
|
||||
|
||||
# For KTX2 Zstandard decompression using [zstd](https://crates.io/crates/zstd). This is a faster backend, but uses unsafe C bindings. For the safe option, stick to the default backend with "zstd_rust".
|
||||
zstd_c = ["bevy_internal/zstd_c"]
|
||||
|
||||
# FLAC audio format support
|
||||
flac = ["bevy_internal/flac"]
|
||||
@ -437,7 +462,7 @@ android_shared_stdcxx = ["bevy_internal/android_shared_stdcxx"]
|
||||
detailed_trace = ["bevy_internal/detailed_trace"]
|
||||
|
||||
# Include tonemapping Look Up Tables KTX2 files. If everything is pink, you need to enable this feature or change the `Tonemapping` method for your `Camera2d` or `Camera3d`.
|
||||
tonemapping_luts = ["bevy_internal/tonemapping_luts", "ktx2", "zstd"]
|
||||
tonemapping_luts = ["bevy_internal/tonemapping_luts", "ktx2", "bevy_image/zstd"]
|
||||
|
||||
# Include SMAA Look Up Tables KTX2 Files
|
||||
smaa_luts = ["bevy_internal/smaa_luts"]
|
||||
@ -466,6 +491,12 @@ shader_format_wesl = ["bevy_internal/shader_format_wesl"]
|
||||
# Enable support for transmission-related textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs
|
||||
pbr_transmission_textures = ["bevy_internal/pbr_transmission_textures"]
|
||||
|
||||
# Enable support for Clustered Decals
|
||||
pbr_clustered_decals = ["bevy_internal/pbr_clustered_decals"]
|
||||
|
||||
# Enable support for Light Textures
|
||||
pbr_light_textures = ["bevy_internal/pbr_light_textures"]
|
||||
|
||||
# Enable support for multi-layer material textures in the `StandardMaterial`, at the risk of blowing past the global, per-shader texture limit on older/lower-end GPUs
|
||||
pbr_multi_layer_material_textures = [
|
||||
"bevy_internal/pbr_multi_layer_material_textures",
|
||||
@ -496,7 +527,10 @@ file_watcher = ["bevy_internal/file_watcher"]
|
||||
embedded_watcher = ["bevy_internal/embedded_watcher"]
|
||||
|
||||
# Enable stepping-based debugging of Bevy systems
|
||||
bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"]
|
||||
bevy_debug_stepping = [
|
||||
"bevy_internal/bevy_debug_stepping",
|
||||
"bevy_internal/debug",
|
||||
]
|
||||
|
||||
# Enables the meshlet renderer for dense high-poly scenes (experimental)
|
||||
meshlet = ["bevy_internal/meshlet"]
|
||||
@ -537,30 +571,41 @@ libm = ["bevy_internal/libm"]
|
||||
# Enables use of browser APIs. Note this is currently only applicable on `wasm32` architectures.
|
||||
web = ["bevy_internal/web"]
|
||||
|
||||
# Enable hotpatching of Bevy systems
|
||||
hotpatching = ["bevy_internal/hotpatching"]
|
||||
|
||||
# Enable converting glTF coordinates to Bevy's coordinate system by default. This will be Bevy's default behavior starting in 0.18.
|
||||
gltf_convert_coordinates_default = [
|
||||
"bevy_internal/gltf_convert_coordinates_default",
|
||||
]
|
||||
|
||||
# Enable collecting debug information about systems and components to help with diagnostics
|
||||
debug = ["bevy_internal/debug"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.17.0-dev", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
|
||||
# Wasm does not support dynamic linking.
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.0"
|
||||
rand_chacha = "0.3.1"
|
||||
ron = "0.8.0"
|
||||
ron = "0.10"
|
||||
flate2 = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
bytemuck = "1.7"
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.16.0-dev", default-features = false }
|
||||
serde_json = "1.0.140"
|
||||
bytemuck = "1"
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.17.0-dev", default-features = false }
|
||||
# The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself.
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.16.0-dev", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.0-dev", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.16.0-dev", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.0-dev", default-features = false }
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.17.0-dev", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.17.0-dev", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.17.0-dev", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.17.0-dev", default-features = false }
|
||||
# Needed to poll Task examples
|
||||
futures-lite = "2.0.1"
|
||||
async-std = "1.13"
|
||||
@ -572,7 +617,7 @@ hyper = { version = "1", features = ["server", "http1"] }
|
||||
http-body-util = "0.1"
|
||||
anyhow = "1"
|
||||
macro_rules_attribute = "0.2"
|
||||
accesskit = "0.18"
|
||||
accesskit = "0.19"
|
||||
nonmax = "0.5"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
@ -585,6 +630,17 @@ ureq = { version = "3.0.8", features = ["json"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = { version = "0.3", features = ["Window"] }
|
||||
|
||||
[[example]]
|
||||
name = "context_menu"
|
||||
path = "examples/usage/context_menu.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.context_menu]
|
||||
name = "Context Menu"
|
||||
description = "Example of a context menu"
|
||||
category = "Usage"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "hello_world"
|
||||
path = "examples/hello_world.rs"
|
||||
@ -812,8 +868,20 @@ doc-scrape-examples = true
|
||||
name = "Texture Atlas"
|
||||
description = "Generates a texture atlas (sprite sheet) from individual sprites"
|
||||
category = "2D Rendering"
|
||||
# Loading asset folders is not supported in Wasm, but required to create the atlas.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "tilemap_chunk"
|
||||
path = "examples/2d/tilemap_chunk.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.tilemap_chunk]
|
||||
name = "Tilemap Chunk"
|
||||
description = "Renders a tilemap chunk"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "transparency_2d"
|
||||
path = "examples/2d/transparency_2d.rs"
|
||||
@ -878,6 +946,7 @@ doc-scrape-examples = true
|
||||
name = "2D Wireframe"
|
||||
description = "Showcases wireframes for 2d meshes"
|
||||
category = "2D Rendering"
|
||||
# PolygonMode::Line wireframes are not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
# 3D Rendering
|
||||
@ -945,6 +1014,7 @@ doc-scrape-examples = true
|
||||
name = "Anti-aliasing"
|
||||
description = "Compares different anti-aliasing methods"
|
||||
category = "3D Rendering"
|
||||
# TAA not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -989,6 +1059,7 @@ doc-scrape-examples = true
|
||||
name = "Auto Exposure"
|
||||
description = "A scene showcasing auto exposure"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders, which are not supported by WebGL.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1002,6 +1073,17 @@ description = "Showcases different blend modes"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "manual_material"
|
||||
path = "examples/3d/manual_material.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.manual_material]
|
||||
name = "Manual Material Implementation"
|
||||
description = "Demonstrates how to implement a material manually using the mid-level render APIs"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "edit_material_on_gltf"
|
||||
path = "examples/3d/edit_material_on_gltf.rs"
|
||||
@ -1045,6 +1127,7 @@ doc-scrape-examples = true
|
||||
name = "Screen Space Ambient Occlusion"
|
||||
description = "A scene showcasing screen space ambient occlusion"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders, which are not supported by WebGL.
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1144,6 +1227,7 @@ doc-scrape-examples = true
|
||||
name = "Order Independent Transparency"
|
||||
description = "Demonstrates how to use OIT"
|
||||
category = "3D Rendering"
|
||||
# Not supported by WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1243,7 +1327,19 @@ doc-scrape-examples = true
|
||||
name = "Skybox"
|
||||
description = "Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats."
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "solari"
|
||||
path = "examples/3d/solari.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_solari"]
|
||||
|
||||
[package.metadata.example.solari]
|
||||
name = "Solari"
|
||||
description = "Demonstrates realtime dynamic raytraced lighting using Bevy Solari."
|
||||
category = "3D Rendering"
|
||||
wasm = false # Raytracing is not supported on the web
|
||||
|
||||
[[example]]
|
||||
name = "spherical_area_lights"
|
||||
@ -1342,6 +1438,7 @@ doc-scrape-examples = true
|
||||
name = "Wireframe"
|
||||
description = "Showcases wireframe rendering"
|
||||
category = "3D Rendering"
|
||||
# Not supported on WebGL
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1353,6 +1450,8 @@ doc-scrape-examples = true
|
||||
name = "Irradiance Volumes"
|
||||
description = "Demonstrates irradiance volumes"
|
||||
category = "3D Rendering"
|
||||
# On WebGL and WebGPU, the number of texture bindings is too low
|
||||
# See <https://github.com/bevyengine/bevy/issues/11885>
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1365,6 +1464,7 @@ required-features = ["meshlet"]
|
||||
name = "Meshlet"
|
||||
description = "Meshlet rendering for dense high-poly scenes (experimental)"
|
||||
category = "3D Rendering"
|
||||
# Requires compute shaders and WGPU extensions, not supported by WebGL nor WebGPU.
|
||||
wasm = false
|
||||
setup = [
|
||||
[
|
||||
@ -1400,7 +1500,7 @@ doc-scrape-examples = true
|
||||
name = "Lightmaps"
|
||||
description = "Rendering a scene with baked lightmaps"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "no_prepass"
|
||||
@ -1553,6 +1653,7 @@ doc-scrape-examples = true
|
||||
name = "Custom Loop"
|
||||
description = "Demonstrates how to create a custom runner (to update an app manually)"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1564,6 +1665,7 @@ doc-scrape-examples = true
|
||||
name = "Drag and Drop"
|
||||
description = "An example that shows how to handle drag and drop in an app"
|
||||
category = "Application"
|
||||
# Browser drag and drop is not supported
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1575,6 +1677,7 @@ doc-scrape-examples = true
|
||||
name = "Empty"
|
||||
description = "An empty application (does nothing)"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1598,6 +1701,7 @@ required-features = ["bevy_log"]
|
||||
name = "Headless"
|
||||
description = "An application that runs without default plugins"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1620,6 +1724,8 @@ doc-scrape-examples = true
|
||||
name = "Log layers"
|
||||
description = "Illustrate how to add custom log layers"
|
||||
category = "Application"
|
||||
# Accesses `time`, which is not available on the web
|
||||
# Also doesn't render anything
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -1631,6 +1737,7 @@ doc-scrape-examples = true
|
||||
name = "Advanced log layers"
|
||||
description = "Illustrate how to transfer data between log layers and Bevy's ECS"
|
||||
category = "Application"
|
||||
# Doesn't render anything, doesn't create a canvas
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
@ -2006,12 +2113,6 @@ description = "Full guide to Bevy's ECS"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[package.metadata.example.apply_deferred]
|
||||
name = "Apply System Buffers"
|
||||
description = "Show how to use `ApplyDeferred` system"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "change_detection"
|
||||
path = "examples/ecs/change_detection.rs"
|
||||
@ -2061,6 +2162,7 @@ wasm = false
|
||||
name = "dynamic"
|
||||
path = "examples/ecs/dynamic.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["debug"]
|
||||
|
||||
[package.metadata.example.dynamic]
|
||||
name = "Dynamic ECS"
|
||||
@ -2215,7 +2317,6 @@ wasm = false
|
||||
name = "fallible_params"
|
||||
path = "examples/ecs/fallible_params.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["configurable_error_handler"]
|
||||
|
||||
[package.metadata.example.fallible_params]
|
||||
name = "Fallible System Parameters"
|
||||
@ -2227,7 +2328,7 @@ wasm = false
|
||||
name = "error_handling"
|
||||
path = "examples/ecs/error_handling.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"]
|
||||
required-features = ["bevy_mesh_picking_backend"]
|
||||
|
||||
[package.metadata.example.error_handling]
|
||||
name = "Error handling"
|
||||
@ -3422,6 +3523,28 @@ description = "An example for CSS Grid layout"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "gradients"
|
||||
path = "examples/ui/gradients.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.gradients]
|
||||
name = "Gradients"
|
||||
description = "An example demonstrating gradients"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "stacked_gradients"
|
||||
path = "examples/ui/stacked_gradients.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.stacked_gradients]
|
||||
name = "Stacked Gradients"
|
||||
description = "An example demonstrating stacked gradients"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "scroll"
|
||||
path = "examples/ui/scroll.rs"
|
||||
@ -3510,6 +3633,17 @@ description = "Illustrates how to use 9 Slicing for TextureAtlases in UI"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "ui_transform"
|
||||
path = "examples/ui/ui_transform.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.ui_transform]
|
||||
name = "UI Transform"
|
||||
description = "An example demonstrating how to translate, rotate and scale UI elements."
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "viewport_debug"
|
||||
path = "examples/ui/viewport_debug.rs"
|
||||
@ -3893,6 +4027,16 @@ description = "A simple 2D screen shake effect"
|
||||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "2d_on_ui"
|
||||
path = "examples/camera/2d_on_ui.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.2d_on_ui]
|
||||
name = "2D on Bevy UI"
|
||||
description = "Shows how to render 2D objects on top of Bevy UI"
|
||||
category = "Camera"
|
||||
wasm = true
|
||||
|
||||
[package.metadata.example.fps_overlay]
|
||||
name = "FPS overlay"
|
||||
@ -4198,6 +4342,14 @@ description = "Demonstrates specular tints and maps"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "test_invalid_skinned_mesh"
|
||||
path = "tests/3d/test_invalid_skinned_mesh.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.test_invalid_skinned_mesh]
|
||||
hidden = true
|
||||
|
||||
[profile.wasm-release]
|
||||
inherits = "release"
|
||||
opt-level = "z"
|
||||
@ -4297,6 +4449,7 @@ wasm = true
|
||||
name = "clustered_decals"
|
||||
path = "examples/3d/clustered_decals.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["pbr_clustered_decals"]
|
||||
|
||||
[package.metadata.example.clustered_decals]
|
||||
name = "Clustered Decals"
|
||||
@ -4304,6 +4457,18 @@ description = "Demonstrates clustered decals"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "light_textures"
|
||||
path = "examples/3d/light_textures.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["pbr_light_textures"]
|
||||
|
||||
[package.metadata.example.light_textures]
|
||||
name = "Light Textures"
|
||||
description = "Demonstrates light textures"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "occlusion_culling"
|
||||
path = "examples/3d/occlusion_culling.rs"
|
||||
@ -4361,3 +4526,72 @@ name = "Extended Bindless Material"
|
||||
description = "Demonstrates bindless `ExtendedMaterial`"
|
||||
category = "Shaders"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "cooldown"
|
||||
path = "examples/usage/cooldown.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.cooldown]
|
||||
name = "Cooldown"
|
||||
description = "Example for cooldown on button clicks"
|
||||
category = "Usage"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "hotpatching_systems"
|
||||
path = "examples/ecs/hotpatching_systems.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["hotpatching"]
|
||||
|
||||
[package.metadata.example.hotpatching_systems]
|
||||
name = "Hotpatching Systems"
|
||||
description = "Demonstrates how to hotpatch systems"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "core_widgets"
|
||||
path = "examples/ui/core_widgets.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.core_widgets]
|
||||
name = "Core Widgets"
|
||||
description = "Demonstrates use of core (headless) widgets in Bevy UI"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "core_widgets_observers"
|
||||
path = "examples/ui/core_widgets_observers.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.core_widgets_observers]
|
||||
name = "Core Widgets (w/Observers)"
|
||||
description = "Demonstrates use of core (headless) widgets in Bevy UI, with Observers"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "scrollbars"
|
||||
path = "examples/ui/scrollbars.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.scrollbars]
|
||||
name = "Scrollbars"
|
||||
description = "Demonstrates use of core scrollbar in Bevy UI"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "feathers"
|
||||
path = "examples/ui/feathers.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["experimental_bevy_feathers"]
|
||||
|
||||
[package.metadata.example.feathers]
|
||||
name = "Feathers Widgets"
|
||||
description = "Gallery of Feathers Widgets"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
hidden = true
|
||||
|
||||
26
README.md
26
README.md
@ -1,4 +1,4 @@
|
||||
# [](https://bevyengine.org)
|
||||
# [](https://bevy.org)
|
||||
|
||||
[](https://github.com/bevyengine/bevy#license)
|
||||
[](https://crates.io/crates/bevy)
|
||||
@ -13,7 +13,7 @@ Bevy is a refreshingly simple data-driven game engine built in Rust. It is free
|
||||
|
||||
## WARNING
|
||||
|
||||
Bevy is still in the early stages of development. Important features are missing. Documentation is sparse. A new version of Bevy containing breaking changes to the API is released [approximately once every 3 months](https://bevyengine.org/news/bevy-0-6/#the-train-release-schedule). We provide [migration guides](https://bevyengine.org/learn/migration-guides/), but we can't guarantee migrations will always be easy. Use only if you are willing to work in this environment.
|
||||
Bevy is still in the early stages of development. Important features are missing. Documentation is sparse. A new version of Bevy containing breaking changes to the API is released [approximately once every 3 months](https://bevy.org/news/bevy-0-6/#the-train-release-schedule). We provide [migration guides](https://bevy.org/learn/migration-guides/), but we can't guarantee migrations will always be easy. Use only if you are willing to work in this environment.
|
||||
|
||||
**MSRV:** Bevy relies heavily on improvements in the Rust language and compiler.
|
||||
As a result, the Minimum Supported Rust Version (MSRV) is generally close to "the latest stable release" of Rust.
|
||||
@ -29,15 +29,15 @@ As a result, the Minimum Supported Rust Version (MSRV) is generally close to "th
|
||||
|
||||
## About
|
||||
|
||||
* **[Features](https://bevyengine.org):** A quick overview of Bevy's features.
|
||||
* **[News](https://bevyengine.org/news/)**: A development blog that covers our progress, plans and shiny new features.
|
||||
* **[Features](https://bevy.org):** A quick overview of Bevy's features.
|
||||
* **[News](https://bevy.org/news/)**: A development blog that covers our progress, plans and shiny new features.
|
||||
|
||||
## Docs
|
||||
|
||||
* **[Quick Start Guide](https://bevyengine.org/learn/quick-start/introduction):** Bevy's official Quick Start Guide. The best place to start learning Bevy.
|
||||
* **[Quick Start Guide](https://bevy.org/learn/quick-start/introduction):** Bevy's official Quick Start Guide. The best place to start learning Bevy.
|
||||
* **[Bevy Rust API Docs](https://docs.rs/bevy):** Bevy's Rust API docs, which are automatically generated from the doc comments in this repo.
|
||||
* **[Official Examples](https://github.com/bevyengine/bevy/tree/latest/examples):** Bevy's dedicated, runnable examples, which are great for digging into specific concepts.
|
||||
* **[Community-Made Learning Resources](https://bevyengine.org/assets/#learning)**: More tutorials, documentation, and examples made by the Bevy community.
|
||||
* **[Community-Made Learning Resources](https://bevy.org/assets/#learning)**: More tutorials, documentation, and examples made by the Bevy community.
|
||||
|
||||
## Community
|
||||
|
||||
@ -46,11 +46,11 @@ Before contributing or participating in discussions with the community, you shou
|
||||
* **[Discord](https://discord.gg/bevy):** Bevy's official discord server.
|
||||
* **[Reddit](https://reddit.com/r/bevy):** Bevy's official subreddit.
|
||||
* **[GitHub Discussions](https://github.com/bevyengine/bevy/discussions):** The best place for questions about Bevy, answered right here!
|
||||
* **[Bevy Assets](https://bevyengine.org/assets/):** A collection of awesome Bevy projects, tools, plugins and learning materials.
|
||||
* **[Bevy Assets](https://bevy.org/assets/):** A collection of awesome Bevy projects, tools, plugins and learning materials.
|
||||
|
||||
### Contributing
|
||||
|
||||
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevyengine.org/learn/contribute/introduction)**.
|
||||
If you'd like to help build Bevy, check out the **[Contributor's Guide](https://bevy.org/learn/contribute/introduction)**.
|
||||
For simple problems, feel free to [open an issue](https://github.com/bevyengine/bevy/issues) or
|
||||
[PR](https://github.com/bevyengine/bevy/pulls) and tackle it yourself!
|
||||
|
||||
@ -58,9 +58,9 @@ For more complex architecture decisions and experimental mad science, please ope
|
||||
|
||||
## Getting Started
|
||||
|
||||
We recommend checking out the [Quick Start Guide](https://bevyengine.org/learn/quick-start/introduction) for a brief introduction.
|
||||
We recommend checking out the [Quick Start Guide](https://bevy.org/learn/quick-start/introduction) for a brief introduction.
|
||||
|
||||
Follow the [Setup guide](https://bevyengine.org/learn/quick-start/getting-started/setup) to ensure your development environment is set up correctly.
|
||||
Follow the [Setup guide](https://bevy.org/learn/quick-start/getting-started/setup) to ensure your development environment is set up correctly.
|
||||
Once set up, you can quickly try out the [examples](https://github.com/bevyengine/bevy/tree/latest/examples) by cloning this repo and running the following commands:
|
||||
|
||||
```sh
|
||||
@ -75,7 +75,7 @@ To draw a window with standard functionality enabled, use:
|
||||
```rust
|
||||
use bevy::prelude::*;
|
||||
|
||||
fn main(){
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.run();
|
||||
@ -84,7 +84,7 @@ fn main(){
|
||||
|
||||
### Fast Compiles
|
||||
|
||||
Bevy can be built just fine using default configuration on stable Rust. However for really fast iterative compiles, you should enable the "fast compiles" setup by [following the instructions here](https://bevyengine.org/learn/quick-start/getting-started/setup).
|
||||
Bevy can be built just fine using default configuration on stable Rust. However for really fast iterative compiles, you should enable the "fast compiles" setup by [following the instructions here](https://bevy.org/learn/quick-start/getting-started/setup).
|
||||
|
||||
## [Bevy Cargo Features][cargo_features]
|
||||
|
||||
@ -96,7 +96,7 @@ This [list][cargo_features] outlines the different cargo features supported by B
|
||||
|
||||
Bevy is the result of the hard work of many people. A huge thanks to all Bevy contributors, the many open source projects that have come before us, the [Rust gamedev ecosystem](https://arewegameyet.rs/), and the many libraries we build on.
|
||||
|
||||
A huge thanks to Bevy's [generous sponsors](https://bevyengine.org). Bevy will always be free and open source, but it isn't free to make. Please consider [sponsoring our work](https://bevyengine.org/donate/) if you like what we're building.
|
||||
A huge thanks to Bevy's [generous sponsors](https://bevy.org). Bevy will always be free and open source, but it isn't free to make. Please consider [sponsoring our work](https://bevy.org/donate/) if you like what we're building.
|
||||
|
||||
<!-- This next line need to stay exactly as is. It is required for BrowserStack sponsorship. -->
|
||||
This project is tested with BrowserStack.
|
||||
|
||||
@ -9,20 +9,20 @@
|
||||
(
|
||||
node_type: Blend,
|
||||
mask: 0,
|
||||
weight: 1.0,
|
||||
weight: 0.5,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation0")),
|
||||
node_type: Clip("models/animated/Fox.glb#Animation0"),
|
||||
mask: 0,
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation1")),
|
||||
node_type: Clip("models/animated/Fox.glb#Animation1"),
|
||||
mask: 0,
|
||||
weight: 1.0,
|
||||
),
|
||||
(
|
||||
node_type: Clip(AssetPath("models/animated/Fox.glb#Animation2")),
|
||||
node_type: Clip("models/animated/Fox.glb#Animation2"),
|
||||
mask: 0,
|
||||
weight: 1.0,
|
||||
),
|
||||
|
||||
113
assets/branding/bevy_solari.svg
Normal file
113
assets/branding/bevy_solari.svg
Normal file
@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
id="svg2321"
|
||||
version="1.1"
|
||||
viewBox="0 0 63.304429 63.304432"
|
||||
height="63.304432mm"
|
||||
width="63.304428mm"
|
||||
sodipodi:docname="bevy_solari.svg"
|
||||
inkscape:version="1.4 (86a8ad7, 2024-10-11)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#">
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:zoom="0.27643554"
|
||||
inkscape:cx="54.262198"
|
||||
inkscape:cy="-311.10327"
|
||||
inkscape:window-width="1440"
|
||||
inkscape:window-height="788"
|
||||
inkscape:window-x="-6"
|
||||
inkscape:window-y="-6"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg2321" />
|
||||
<defs
|
||||
id="defs2315" />
|
||||
<metadata
|
||||
id="metadata2318">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="" />
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
id="g11"
|
||||
style="display:inline"
|
||||
transform="translate(-27.298342,-111.49082)">
|
||||
<path
|
||||
style="fill:#ff8904;fill-opacity:1;stroke:none;stroke-width:0.264583;stroke-opacity:1"
|
||||
id="path10-7"
|
||||
d="m 38.15031,109.44704 c 0.743942,3.07133 6.496307,7.00051 6.678099,10.15542 0.116231,2.01713 -5.225098,3.23914 -5.79502,5.17757 -0.891392,3.03182 2.125712,9.31077 0.705692,12.1339 -0.907907,1.80501 -6.144635,0.19263 -7.607418,1.5864 -2.287879,2.17994 -2.814466,9.12622 -5.455804,10.86111 -1.688772,1.10923 -5.417723,-2.9055 -7.381414,-2.42985 -3.071331,0.74394 -7.000511,6.49631 -10.1554234,6.6781 -2.0171309,0.11623 -3.2391354,-5.2251 -5.1775665,-5.79502 -3.03182154,-0.89139 -9.3107731,2.12571 -12.1339038,0.70569 -1.8050021,-0.9079 -0.1926248,-6.14463 -1.5863942,-7.60742 -2.1799381,-2.28787 -9.1262221,-2.81446 -10.8611151,-5.4558 -1.109224,-1.68877 2.9055,-5.41772 2.429851,-7.38141 -0.743942,-3.07133 -6.496306,-7.00051 -6.678099,-10.15543 -0.116231,-2.01713 5.225098,-3.23913 5.79502,-5.17756 0.891393,-3.03182 -2.125711,-9.31078 -0.705692,-12.13391 0.907907,-1.804999 6.144635,-0.19262 7.607418,-1.586391 2.2878791,-2.179939 2.8144662,-9.126222 5.4558046,-10.861115 1.6887711,-1.109225 5.4177222,2.905499 7.38141398,2.429851 3.07133072,-0.743942 7.00051032,-6.496307 10.15542342,-6.678099 2.01713,-0.116231 3.239135,5.225097 5.177566,5.79502 3.031822,0.891392 9.310773,-2.125712 12.133904,-0.705692 1.805002,0.907906 0.192625,6.144634 1.586394,7.607417 2.179938,2.28788 9.126222,2.814467 10.861115,5.455809 1.109224,1.68877 -2.905499,5.41772 -2.429851,7.38141 z"
|
||||
transform="matrix(0.90823691,0,0,0.90823691,49.886257,35.27956)" />
|
||||
<g
|
||||
id="g3-6"
|
||||
transform="translate(-517.96199,-278.01119)"
|
||||
style="display:inline;opacity:1;fill:#ffd230;fill-opacity:1;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none">
|
||||
<g
|
||||
transform="matrix(-0.40426719,-0.17438247,-0.17438247,0.40426719,678.77611,389.84765)"
|
||||
style="fill:#ffd230;fill-opacity:1"
|
||||
id="g2-1">
|
||||
<path
|
||||
id="path1-4"
|
||||
style="fill:#ffd230;fill-opacity:1;stroke:none;stroke-width:0.559814px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 2246.0312,2340.1914 v 0 c -0.016,3e-4 -0.031,0 -0.047,0 -0.9804,3.0675 -1.7386,6.3997 -1.8828,10.1953 -0.2712,7.1263 0.453,11.4639 -0.3672,16.0801 -0.8202,4.6163 -3.2453,9.161 -9.4141,16.2871 -7.3424,8.482 -18.9789,15.0453 -32.4199,17.2637 -2.5015,1.5971 -5.1421,3.0609 -7.9199,4.3633 10.4618,3.9385 21.4025,4.1531 30.0761,1.3066 15.2793,-5.0141 14.0962,-8.6155 20.9434,-19.1074 2.1569,-3.3051 4.6474,-5.8282 7.1484,-7.9004 7.1248,3.1068 14.1431,5.1015 18.5157,4.6074 2.351,-5.4505 -0.057,-11.7712 -4.0586,-17.7461 3.2821,-10.196 -1.6986,-20.4059 -12.7305,-24.0156 -2.8775,-0.9415 -5.4633,-1.3844 -7.8438,-1.3379 z m 8.2754,14.9707 a 4.1668789,4.2454995 48.679502 0 1 3.1973,1.3965 4.1668789,4.2454995 48.679502 0 1 -0.4375,5.9336 4.1668789,4.2454995 48.679502 0 1 -5.9394,-0.3262 4.1668789,4.2454995 48.679502 0 1 0.4375,-5.9336 4.1668789,4.2454995 48.679502 0 1 2.7421,-1.0703 z m -68.375,45.3789 c 0.1273,0.075 0.2572,0.1408 0.3848,0.2149 0.131,-0.049 0.2642,-0.1009 0.3945,-0.1504 -0.2598,-0.023 -0.5188,-0.039 -0.7793,-0.064 z"
|
||||
transform="matrix(-0.55180403,-0.23802315,-0.23802315,0.55180403,1946.7322,-620.612)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g5-2"
|
||||
transform="matrix(-0.45399624,0.36689705,0.36689705,0.45399624,73.527335,10.816805)"
|
||||
style="display:inline;opacity:1;fill:#fef3c6;fill-opacity:1;stroke:none;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1">
|
||||
<g
|
||||
id="g4-3"
|
||||
transform="matrix(-0.35254083,0.28490586,0.28490586,0.35254083,477.11004,-1021.7666)"
|
||||
style="opacity:1;fill:#fef3c6;fill-opacity:1">
|
||||
<path
|
||||
id="path2-2"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fef3c6;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.02362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 2191.1465,2276.7832 c -5.9729,-0.035 -12.0979,2.348 -17.3613,7.459 -6.9129,6.7127 -9.0602,12.7555 -7.8477,20.2949 l 0.332,2.0684 -2.0664,-0.336 c -15.1877,-2.4609 -33.9847,-1.2178 -55.3711,7.4336 6.2868,2.6948 17.8259,7.1926 30.6309,13.3418 l 4.0605,1.9512 -4.414,0.8945 c -16.9087,3.4274 -36.9729,13.3275 -55.2989,34.9336 8.1981,-0.6372 24.9531,-2.6089 42.4278,-2.582 9.7138,0.015 19.2869,0.687 27.0859,2.709 7.7991,2.022 14.8874,6.6498 15.8861,10.0406 0.9987,3.3908 0.432,5.1761 -0.5519,7.8285 -0.9839,2.6524 -4.0098,6.6817 -8.1953,9.3418 -4.1855,2.6601 -9.4961,4.9849 -15.0137,6.9609 -11.0352,3.9521 -22.7798,6.4773 -27.9648,6.959 -1.1021,0.1024 -1.5421,0.4983 -1.9668,1.2696 -0.4247,0.7712 -0.659,1.9824 -0.6934,3.25 -0.046,1.6926 0.217,2.576 0.6949,3.246 0.4779,0.67 1.2243,0.9381 1.9934,0.9902 32.5822,2.2052 56.9441,-5.9907 74.6379,-13.0116 20.3508,-9.3311 33.2134,-27.7577 36.0058,-44.3477 1.7499,-10.395 1.3746,-15.4894 -0.3124,-19.8281 -1.6873,-4.3387 -4.9223,-8.1914 -9.0254,-15.5488 -2.6368,-4.7281 -4.1077,-9.367 -5.0196,-13.6875 l -0.1933,-0.9102 0.7265,-0.582 c 7.5403,-6.0446 13.6809,-12.6444 15.9102,-17.4492 -4.5742,-4.8648 -12.4787,-5.893 -21.3223,-4.9473 l -0.7265,0.076 -0.5118,-0.5215 c -4.7125,-4.8006 -10.5615,-7.2614 -16.5351,-7.2969 z m 2.6484,11.2324 c 2.593,-0.041 4.8808,1.7566 5.502,4.3223 0.7307,3.0216 -1.0812,6.0525 -4.0469,6.7695 -2.9656,0.7176 -5.9625,-1.1502 -6.6934,-4.1719 -0.7307,-3.0216 1.0812,-6.0525 4.0469,-6.7695 0.3902,-0.094 0.7897,-0.1445 1.1914,-0.1504 z"
|
||||
transform="translate(5.0092774e-5,-757.87625)" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
style="display:inline;opacity:1;fill:#fee685;fill-opacity:1;stroke-width:0.755906;stroke-miterlimit:4;stroke-dasharray:none"
|
||||
id="g10-2"
|
||||
transform="matrix(-0.50509374,0.06754889,0.06754889,0.50509374,156.75523,55.243465)">
|
||||
<g
|
||||
style="fill:#fee685;fill-opacity:1"
|
||||
id="g9-1"
|
||||
transform="translate(-20.244579,-6.1209206)">
|
||||
<g
|
||||
style="fill:#fee685;fill-opacity:1"
|
||||
id="g8-6"
|
||||
transform="translate(61.54776,-5.6726683)">
|
||||
<g
|
||||
id="g7-8"
|
||||
style="fill:#fee685;fill-opacity:1">
|
||||
<g
|
||||
id="g6-5"
|
||||
transform="matrix(-0.514626,0.06882369,0.06882369,0.514626,1184.3644,-811.9091)"
|
||||
style="opacity:1;fill:#fee685;fill-opacity:1">
|
||||
<path
|
||||
id="path4-7"
|
||||
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-variant-east-asian:normal;font-feature-settings:normal;font-variation-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;shape-margin:0;inline-size:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:#fee685;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:3.02362;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:accumulate;stop-color:#000000;stop-opacity:1"
|
||||
d="m 2230.8945,2301.1738 c -1.9108,-0.039 -3.9117,0.162 -5.9785,0.6328 -0.1394,0.032 -0.2613,0.071 -0.3984,0.1036 -2.274,2.2481 -4.8127,4.5047 -7.5293,6.7168 0.8746,3.8597 2.1735,7.8829 4.4707,12.0019 3.9872,7.1495 7.2742,10.9657 9.2031,15.9258 1.9289,4.9601 2.2639,10.7945 0.4746,21.4238 -2.2183,13.178 -10.2404,27.1324 -22.959,37.4336 9.8717,-2.8792 18.2866,-8.1915 23.8575,-14.6269 6.0132,-6.9464 8.0191,-10.8762 8.7226,-14.836 0.7036,-3.9598 0.044,-8.2997 0.3242,-15.664 0.1805,-4.7447 1.1911,-8.8958 2.4766,-12.545 l 0.3086,-0.875 0.9219,-0.1211 c 8.2284,-1.0673 15.6654,-3.167 19.5097,-5.6484 -1.2349,-5.5522 -6.4807,-9.8603 -13.4277,-13.1348 l -0.6621,-0.3125 -0.166,-0.7129 c -2.2034,-9.4614 -9.5905,-15.5632 -19.1485,-15.7617 z m 4.7832,11.6856 a 4.8229105,4.9139092 17.729059 0 1 1.4473,0.2246 4.8229105,4.9139092 17.729059 0 1 3.0977,6.1484 4.8229105,4.9139092 17.729059 0 1 -6.0899,3.2129 4.8229105,4.9139092 17.729059 0 1 -3.0976,-6.1484 4.8229105,4.9139092 17.729059 0 1 4.6425,-3.4375 z"
|
||||
transform="translate(1.2499985e-4,-757.87627)" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 11 KiB |
BIN
assets/lightmaps/caustic_directional_texture.png
Normal file
BIN
assets/lightmaps/caustic_directional_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
BIN
assets/lightmaps/faces_pointlight_texture_blurred.png
Normal file
BIN
assets/lightmaps/faces_pointlight_texture_blurred.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 551 KiB |
BIN
assets/lightmaps/torch_spotlight_texture.png
Normal file
BIN
assets/lightmaps/torch_spotlight_texture.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 93 KiB |
BIN
assets/models/Faces/faces.glb
Normal file
BIN
assets/models/Faces/faces.glb
Normal file
Binary file not shown.
@ -7,8 +7,8 @@
|
||||
}
|
||||
#import bevy_core_pipeline::tonemapping::tone_mapping
|
||||
|
||||
@group(2) @binding(0) var my_array_texture: texture_2d_array<f32>;
|
||||
@group(2) @binding(1) var my_array_texture_sampler: sampler;
|
||||
@group(3) @binding(0) var my_array_texture: texture_2d_array<f32>;
|
||||
@group(3) @binding(1) var my_array_texture_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var texture: texture_2d<f32>;
|
||||
@group(2) @binding(1) var texture_sampler: sampler;
|
||||
@group(3) @binding(0) var texture: texture_2d<f32>;
|
||||
@group(3) @binding(1) var texture_sampler: sampler;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
|
||||
@ -15,12 +15,12 @@ struct MaterialBindings {
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(0) var<storage> materials: array<MaterialBindings>;
|
||||
@group(2) @binding(10) var<storage> material_color: binding_array<Color>;
|
||||
@group(3) @binding(0) var<storage> materials: array<MaterialBindings>;
|
||||
@group(3) @binding(10) var<storage> material_color: binding_array<Color>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(0) var<uniform> material_color: Color;
|
||||
@group(2) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@group(2) @binding(2) var material_color_sampler: sampler;
|
||||
@group(3) @binding(0) var<uniform> material_color: Color;
|
||||
@group(3) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@group(3) @binding(2) var material_color_sampler: sampler;
|
||||
#endif // BINDLESS
|
||||
|
||||
@fragment
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
|
||||
#ifdef CUBEMAP_ARRAY
|
||||
@group(2) @binding(0) var base_color_texture: texture_cube_array<f32>;
|
||||
@group(3) @binding(0) var base_color_texture: texture_cube_array<f32>;
|
||||
#else
|
||||
@group(2) @binding(0) var base_color_texture: texture_cube<f32>;
|
||||
@group(3) @binding(0) var base_color_texture: texture_cube<f32>;
|
||||
#endif
|
||||
|
||||
@group(2) @binding(1) var base_color_sampler: sampler;
|
||||
@group(3) @binding(1) var base_color_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -3,10 +3,10 @@ layout(location = 0) in vec2 v_Uv;
|
||||
|
||||
layout(location = 0) out vec4 o_Target;
|
||||
|
||||
layout(set = 2, binding = 0) uniform vec4 CustomMaterial_color;
|
||||
layout(set = 3, binding = 0) uniform vec4 CustomMaterial_color;
|
||||
|
||||
layout(set = 2, binding = 1) uniform texture2D CustomMaterial_texture;
|
||||
layout(set = 2, binding = 2) uniform sampler CustomMaterial_sampler;
|
||||
layout(set = 3, binding = 1) uniform texture2D CustomMaterial_texture;
|
||||
layout(set = 3, binding = 2) uniform sampler CustomMaterial_sampler;
|
||||
|
||||
// wgsl modules can be imported and used in glsl
|
||||
// FIXME - this doesn't work any more ...
|
||||
|
||||
@ -25,9 +25,9 @@ struct Mesh {
|
||||
};
|
||||
|
||||
#ifdef PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
layout(set = 1, binding = 0) uniform Mesh Meshes[#{PER_OBJECT_BUFFER_BATCH_SIZE}];
|
||||
layout(set = 2, binding = 0) uniform Mesh Meshes[#{PER_OBJECT_BUFFER_BATCH_SIZE}];
|
||||
#else
|
||||
layout(set = 1, binding = 0) readonly buffer _Meshes {
|
||||
layout(set = 2, binding = 0) readonly buffer _Meshes {
|
||||
Mesh Meshes[];
|
||||
};
|
||||
#endif // PER_OBJECT_BUFFER_BATCH_SIZE
|
||||
|
||||
@ -10,7 +10,7 @@ struct CustomMaterial {
|
||||
time: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
@group(3) @binding(0) var<uniform> material: CustomMaterial;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
// we can import items from shader modules in the assets folder with a quoted path
|
||||
#import "shaders/custom_material_import.wgsl"::COLOR_MULTIPLIER
|
||||
|
||||
@group(2) @binding(0) var<uniform> material_color: vec4<f32>;
|
||||
@group(2) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@group(2) @binding(2) var material_color_sampler: sampler;
|
||||
@group(3) @binding(0) var<uniform> material_color: vec4<f32>;
|
||||
@group(3) @binding(1) var material_color_texture: texture_2d<f32>;
|
||||
@group(3) @binding(2) var material_color_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
utils::coords_to_viewport_uv,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var texture: texture_2d<f32>;
|
||||
@group(2) @binding(1) var texture_sampler: sampler;
|
||||
@group(3) @binding(0) var texture: texture_2d<f32>;
|
||||
@group(3) @binding(1) var texture_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
// For 2d replace `bevy_pbr::mesh_functions` with `bevy_sprite::mesh2d_functions`
|
||||
// and `mesh_position_local_to_clip` with `mesh2d_position_local_to_clip`.
|
||||
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
|
||||
|
||||
struct CustomMaterial {
|
||||
color: vec4<f32>,
|
||||
};
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
@group(3) @binding(0) var<uniform> material: CustomMaterial;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
|
||||
@ -17,9 +17,15 @@
|
||||
|
||||
struct MyExtendedMaterial {
|
||||
quantize_steps: u32,
|
||||
#ifdef SIXTEEN_BYTE_ALIGNMENT
|
||||
// Web examples WebGL2 support: structs must be 16 byte aligned.
|
||||
_webgl2_padding_8b: u32,
|
||||
_webgl2_padding_12b: u32,
|
||||
_webgl2_padding_16b: u32,
|
||||
#endif
|
||||
}
|
||||
|
||||
@group(2) @binding(100)
|
||||
@group(3) @binding(100)
|
||||
var<uniform> my_extended_material: MyExtendedMaterial;
|
||||
|
||||
@fragment
|
||||
|
||||
@ -42,19 +42,19 @@ struct ExampleBindlessExtendedMaterial {
|
||||
|
||||
// The indices of the bindless resources in the bindless resource arrays, for
|
||||
// the `ExampleBindlessExtension` fields.
|
||||
@group(2) @binding(100) var<storage> example_extended_material_indices:
|
||||
@group(3) @binding(100) var<storage> example_extended_material_indices:
|
||||
array<ExampleBindlessExtendedMaterialIndices>;
|
||||
// An array that holds the `ExampleBindlessExtendedMaterial` plain old data,
|
||||
// indexed by `ExampleBindlessExtendedMaterialIndices.material`.
|
||||
@group(2) @binding(101) var<storage> example_extended_material:
|
||||
@group(3) @binding(101) var<storage> example_extended_material:
|
||||
array<ExampleBindlessExtendedMaterial>;
|
||||
|
||||
#else // BINDLESS
|
||||
|
||||
// In non-bindless mode, we simply use a uniform for the plain old data.
|
||||
@group(2) @binding(50) var<uniform> example_extended_material: ExampleBindlessExtendedMaterial;
|
||||
@group(2) @binding(51) var modulate_texture: texture_2d<f32>;
|
||||
@group(2) @binding(52) var modulate_sampler: sampler;
|
||||
@group(3) @binding(50) var<uniform> example_extended_material: ExampleBindlessExtendedMaterial;
|
||||
@group(3) @binding(51) var modulate_texture: texture_2d<f32>;
|
||||
@group(3) @binding(52) var modulate_sampler: sampler;
|
||||
|
||||
#endif // BINDLESS
|
||||
|
||||
|
||||
@ -1,22 +1,22 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
|
||||
@group(2) @binding(0) var test_texture_1d: texture_1d<f32>;
|
||||
@group(2) @binding(1) var test_texture_1d_sampler: sampler;
|
||||
@group(3) @binding(0) var test_texture_1d: texture_1d<f32>;
|
||||
@group(3) @binding(1) var test_texture_1d_sampler: sampler;
|
||||
|
||||
@group(2) @binding(2) var test_texture_2d: texture_2d<f32>;
|
||||
@group(2) @binding(3) var test_texture_2d_sampler: sampler;
|
||||
@group(3) @binding(2) var test_texture_2d: texture_2d<f32>;
|
||||
@group(3) @binding(3) var test_texture_2d_sampler: sampler;
|
||||
|
||||
@group(2) @binding(4) var test_texture_2d_array: texture_2d_array<f32>;
|
||||
@group(2) @binding(5) var test_texture_2d_array_sampler: sampler;
|
||||
@group(3) @binding(4) var test_texture_2d_array: texture_2d_array<f32>;
|
||||
@group(3) @binding(5) var test_texture_2d_array_sampler: sampler;
|
||||
|
||||
@group(2) @binding(6) var test_texture_cube: texture_cube<f32>;
|
||||
@group(2) @binding(7) var test_texture_cube_sampler: sampler;
|
||||
@group(3) @binding(6) var test_texture_cube: texture_cube<f32>;
|
||||
@group(3) @binding(7) var test_texture_cube_sampler: sampler;
|
||||
|
||||
@group(2) @binding(8) var test_texture_cube_array: texture_cube_array<f32>;
|
||||
@group(2) @binding(9) var test_texture_cube_array_sampler: sampler;
|
||||
@group(3) @binding(8) var test_texture_cube_array: texture_cube_array<f32>;
|
||||
@group(3) @binding(9) var test_texture_cube_array_sampler: sampler;
|
||||
|
||||
@group(2) @binding(10) var test_texture_3d: texture_3d<f32>;
|
||||
@group(2) @binding(11) var test_texture_3d_sampler: sampler;
|
||||
@group(3) @binding(10) var test_texture_3d: texture_3d<f32>;
|
||||
@group(3) @binding(11) var test_texture_3d_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(in: VertexOutput) {}
|
||||
|
||||
@ -12,7 +12,7 @@ struct VoxelVisualizationIrradianceVolumeInfo {
|
||||
intensity: f32,
|
||||
}
|
||||
|
||||
@group(2) @binding(100)
|
||||
@group(3) @binding(100)
|
||||
var<uniform> irradiance_volume_info: VoxelVisualizationIrradianceVolumeInfo;
|
||||
|
||||
@fragment
|
||||
|
||||
@ -4,7 +4,7 @@ struct LineMaterial {
|
||||
color: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: LineMaterial;
|
||||
@group(3) @binding(0) var<uniform> material: LineMaterial;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
11
assets/shaders/manual_material.wgsl
Normal file
11
assets/shaders/manual_material.wgsl
Normal file
@ -0,0 +1,11 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
|
||||
@group(3) @binding(0) var material_color_texture: texture_2d<f32>;
|
||||
@group(3) @binding(1) var material_color_sampler: sampler;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return textureSample(material_color_texture, material_color_sampler, mesh.uv);
|
||||
}
|
||||
@ -4,7 +4,7 @@ struct CustomMaterial {
|
||||
color: vec4<f32>,
|
||||
};
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
@group(3) @binding(0) var<uniform> material: CustomMaterial;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -11,7 +11,7 @@ struct ShowPrepassSettings {
|
||||
padding_1: u32,
|
||||
padding_2: u32,
|
||||
}
|
||||
@group(2) @binding(0) var<uniform> settings: ShowPrepassSettings;
|
||||
@group(3) @binding(0) var<uniform> settings: ShowPrepassSettings;
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
view_transformations::position_world_to_clip
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
|
||||
@group(3) @binding(0) var<storage, read> colors: array<vec4<f32>, 5>;
|
||||
|
||||
struct Vertex {
|
||||
@builtin(instance_index) instance_index: u32,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
#import bevy_pbr::forward_io::VertexOutput
|
||||
|
||||
@group(2) @binding(0) var textures: binding_array<texture_2d<f32>>;
|
||||
@group(2) @binding(1) var nearest_sampler: sampler;
|
||||
@group(3) @binding(0) var textures: binding_array<texture_2d<f32>>;
|
||||
@group(3) @binding(1) var nearest_sampler: sampler;
|
||||
// We can also have array of samplers
|
||||
// var samplers: binding_array<sampler>;
|
||||
|
||||
|
||||
@ -23,9 +23,9 @@ struct WaterSettings {
|
||||
|
||||
@group(0) @binding(1) var<uniform> globals: Globals;
|
||||
|
||||
@group(2) @binding(100) var water_normals_texture: texture_2d<f32>;
|
||||
@group(2) @binding(101) var water_normals_sampler: sampler;
|
||||
@group(2) @binding(102) var<uniform> water_settings: WaterSettings;
|
||||
@group(3) @binding(100) var water_normals_texture: texture_2d<f32>;
|
||||
@group(3) @binding(101) var water_normals_sampler: sampler;
|
||||
@group(3) @binding(102) var<uniform> water_settings: WaterSettings;
|
||||
|
||||
// Samples a single octave of noise and returns the resulting normal.
|
||||
fn sample_noise_octave(uv: vec2<f32>, strength: f32) -> vec3<f32> {
|
||||
|
||||
BIN
assets/textures/food_kenney.png
Normal file
BIN
assets/textures/food_kenney.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@ -10,7 +10,7 @@ autobenches = false
|
||||
[dependencies]
|
||||
# The primary crate that runs and analyzes our benchmarks. This is a regular dependency because the
|
||||
# `bench!` macro refers to it in its documentation.
|
||||
criterion = { version = "0.5.1", features = ["html_reports"] }
|
||||
criterion = { version = "0.6.0", features = ["html_reports"] }
|
||||
|
||||
[dev-dependencies]
|
||||
# Bevy crates
|
||||
|
||||
67
benches/benches/bevy_ecs/bundles/insert_many.rs
Normal file
67
benches/benches/bevy_ecs/bundles/insert_many.rs
Normal file
@ -0,0 +1,67 @@
|
||||
use benches::bench;
|
||||
use bevy_ecs::{component::Component, world::World};
|
||||
use criterion::Criterion;
|
||||
|
||||
const ENTITY_COUNT: usize = 2_000;
|
||||
|
||||
#[derive(Component)]
|
||||
struct C<const N: usize>(usize);
|
||||
|
||||
pub fn insert_many(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group(bench!("insert_many"));
|
||||
|
||||
group.bench_function("all", |bencher| {
|
||||
let mut world = World::new();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..ENTITY_COUNT {
|
||||
world
|
||||
.spawn_empty()
|
||||
.insert(C::<0>(1))
|
||||
.insert(C::<1>(1))
|
||||
.insert(C::<2>(1))
|
||||
.insert(C::<3>(1))
|
||||
.insert(C::<4>(1))
|
||||
.insert(C::<5>(1))
|
||||
.insert(C::<6>(1))
|
||||
.insert(C::<7>(1))
|
||||
.insert(C::<8>(1))
|
||||
.insert(C::<9>(1))
|
||||
.insert(C::<10>(1))
|
||||
.insert(C::<11>(1))
|
||||
.insert(C::<12>(1))
|
||||
.insert(C::<13>(1))
|
||||
.insert(C::<14>(1));
|
||||
}
|
||||
world.clear_entities();
|
||||
});
|
||||
});
|
||||
|
||||
group.bench_function("only_last", |bencher| {
|
||||
let mut world = World::new();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..ENTITY_COUNT {
|
||||
world
|
||||
.spawn((
|
||||
C::<0>(1),
|
||||
C::<1>(1),
|
||||
C::<2>(1),
|
||||
C::<3>(1),
|
||||
C::<4>(1),
|
||||
C::<5>(1),
|
||||
C::<6>(1),
|
||||
C::<7>(1),
|
||||
C::<8>(1),
|
||||
C::<9>(1),
|
||||
C::<10>(1),
|
||||
C::<11>(1),
|
||||
C::<12>(1),
|
||||
C::<13>(1),
|
||||
))
|
||||
.insert(C::<14>(1));
|
||||
}
|
||||
world.clear_entities();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
14
benches/benches/bevy_ecs/bundles/mod.rs
Normal file
14
benches/benches/bevy_ecs/bundles/mod.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use criterion::criterion_group;
|
||||
|
||||
mod insert_many;
|
||||
mod spawn_many;
|
||||
mod spawn_many_zst;
|
||||
mod spawn_one_zst;
|
||||
|
||||
criterion_group!(
|
||||
benches,
|
||||
spawn_one_zst::spawn_one_zst,
|
||||
spawn_many_zst::spawn_many_zst,
|
||||
spawn_many::spawn_many,
|
||||
insert_many::insert_many,
|
||||
);
|
||||
40
benches/benches/bevy_ecs/bundles/spawn_many.rs
Normal file
40
benches/benches/bevy_ecs/bundles/spawn_many.rs
Normal file
@ -0,0 +1,40 @@
|
||||
use benches::bench;
|
||||
use bevy_ecs::{component::Component, world::World};
|
||||
use criterion::Criterion;
|
||||
|
||||
const ENTITY_COUNT: usize = 2_000;
|
||||
|
||||
#[derive(Component)]
|
||||
struct C<const N: usize>(usize);
|
||||
|
||||
pub fn spawn_many(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group(bench!("spawn_many"));
|
||||
|
||||
group.bench_function("static", |bencher| {
|
||||
let mut world = World::new();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..ENTITY_COUNT {
|
||||
world.spawn((
|
||||
C::<0>(1),
|
||||
C::<1>(1),
|
||||
C::<2>(1),
|
||||
C::<3>(1),
|
||||
C::<4>(1),
|
||||
C::<5>(1),
|
||||
C::<6>(1),
|
||||
C::<7>(1),
|
||||
C::<8>(1),
|
||||
C::<9>(1),
|
||||
C::<10>(1),
|
||||
C::<11>(1),
|
||||
C::<12>(1),
|
||||
C::<13>(1),
|
||||
C::<14>(1),
|
||||
));
|
||||
}
|
||||
world.clear_entities();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
27
benches/benches/bevy_ecs/bundles/spawn_many_zst.rs
Normal file
27
benches/benches/bevy_ecs/bundles/spawn_many_zst.rs
Normal file
@ -0,0 +1,27 @@
|
||||
use benches::bench;
|
||||
use bevy_ecs::{component::Component, world::World};
|
||||
use criterion::Criterion;
|
||||
|
||||
const ENTITY_COUNT: usize = 2_000;
|
||||
|
||||
#[derive(Component)]
|
||||
struct C<const N: usize>;
|
||||
|
||||
pub fn spawn_many_zst(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group(bench!("spawn_many_zst"));
|
||||
|
||||
group.bench_function("static", |bencher| {
|
||||
let mut world = World::new();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..ENTITY_COUNT {
|
||||
world.spawn((
|
||||
C::<0>, C::<1>, C::<2>, C::<3>, C::<4>, C::<5>, C::<6>, C::<7>, C::<8>, C::<9>,
|
||||
C::<10>, C::<11>, C::<12>, C::<13>, C::<14>,
|
||||
));
|
||||
}
|
||||
world.clear_entities();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
24
benches/benches/bevy_ecs/bundles/spawn_one_zst.rs
Normal file
24
benches/benches/bevy_ecs/bundles/spawn_one_zst.rs
Normal file
@ -0,0 +1,24 @@
|
||||
use benches::bench;
|
||||
use bevy_ecs::{component::Component, world::World};
|
||||
use criterion::Criterion;
|
||||
|
||||
const ENTITY_COUNT: usize = 10_000;
|
||||
|
||||
#[derive(Component)]
|
||||
struct A;
|
||||
|
||||
pub fn spawn_one_zst(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group(bench!("spawn_one_zst"));
|
||||
|
||||
group.bench_function("static", |bencher| {
|
||||
let mut world = World::new();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..ENTITY_COUNT {
|
||||
world.spawn(A);
|
||||
}
|
||||
world.clear_entities();
|
||||
});
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
@ -49,6 +49,7 @@ impl BenchModify for Table {
|
||||
black_box(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl BenchModify for Sparse {
|
||||
fn bench_modify(&mut self) -> f32 {
|
||||
self.0 += 1f32;
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use benches::bench;
|
||||
use bevy_ecs::bundle::Bundle;
|
||||
use bevy_ecs::bundle::{Bundle, InsertMode};
|
||||
use bevy_ecs::component::ComponentCloneBehavior;
|
||||
use bevy_ecs::entity::EntityCloner;
|
||||
use bevy_ecs::hierarchy::ChildOf;
|
||||
@ -17,41 +17,15 @@ criterion_group!(
|
||||
hierarchy_tall,
|
||||
hierarchy_wide,
|
||||
hierarchy_many,
|
||||
filter
|
||||
);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C1(Mat4);
|
||||
struct C<const N: usize>(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C2(Mat4);
|
||||
type ComplexBundle = (C<1>, C<2>, C<3>, C<4>, C<5>, C<6>, C<7>, C<8>, C<9>, C<10>);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C3(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C4(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C5(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C6(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C7(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C8(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C9(Mat4);
|
||||
|
||||
#[derive(Component, Reflect, Default, Clone)]
|
||||
struct C10(Mat4);
|
||||
|
||||
type ComplexBundle = (C1, C2, C3, C4, C5, C6, C7, C8, C9, C10);
|
||||
|
||||
/// Sets the [`ComponentCloneHandler`] for all explicit and required components in a bundle `B` to
|
||||
/// Sets the [`ComponentCloneBehavior`] for all explicit and required components in a bundle `B` to
|
||||
/// use the [`Reflect`] trait instead of [`Clone`].
|
||||
fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||
world: &mut World,
|
||||
@ -71,7 +45,7 @@ fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||
// this bundle are saved.
|
||||
let component_ids: Vec<_> = world.register_bundle::<B>().contributed_components().into();
|
||||
|
||||
let mut builder = EntityCloner::build(world);
|
||||
let mut builder = EntityCloner::build_opt_out(world);
|
||||
|
||||
// Overwrite the clone handler for all components in the bundle to use `Reflect`, not `Clone`.
|
||||
for component in component_ids {
|
||||
@ -82,16 +56,15 @@ fn reflection_cloner<B: Bundle + GetTypeRegistration>(
|
||||
builder.finish()
|
||||
}
|
||||
|
||||
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
|
||||
/// bundle `B`.
|
||||
/// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`.
|
||||
///
|
||||
/// The bundle must implement [`Default`], which is used to create the first entity that gets cloned
|
||||
/// in the benchmark.
|
||||
///
|
||||
/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneHandler`] for all
|
||||
/// components (which is usually [`ComponentCloneHandler::clone_handler()`]). If `clone_via_reflect`
|
||||
/// If `clone_via_reflect` is false, this will use the default [`ComponentCloneBehavior`] for all
|
||||
/// components (which is usually [`ComponentCloneBehavior::clone()`]). If `clone_via_reflect`
|
||||
/// is true, it will overwrite the handler for all components in the bundle to be
|
||||
/// [`ComponentCloneHandler::reflect_handler()`].
|
||||
/// [`ComponentCloneBehavior::reflect()`].
|
||||
fn bench_clone<B: Bundle + Default + GetTypeRegistration>(
|
||||
b: &mut Bencher,
|
||||
clone_via_reflect: bool,
|
||||
@ -114,8 +87,7 @@ fn bench_clone<B: Bundle + Default + GetTypeRegistration>(
|
||||
});
|
||||
}
|
||||
|
||||
/// A helper function that benchmarks running the [`EntityCommands::clone_and_spawn()`] command on a
|
||||
/// bundle `B`.
|
||||
/// A helper function that benchmarks running [`EntityCloner::spawn_clone`] with a bundle `B`.
|
||||
///
|
||||
/// As compared to [`bench_clone()`], this benchmarks recursively cloning an entity with several
|
||||
/// children. It does so by setting up an entity tree with a given `height` where each entity has a
|
||||
@ -135,7 +107,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
|
||||
let mut cloner = if clone_via_reflect {
|
||||
reflection_cloner::<B>(&mut world, true)
|
||||
} else {
|
||||
let mut builder = EntityCloner::build(&mut world);
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.linked_cloning(true);
|
||||
builder.finish()
|
||||
};
|
||||
@ -169,7 +141,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
|
||||
|
||||
// Each benchmark runs twice: using either the `Clone` or `Reflect` traits to clone entities. This
|
||||
// constant represents this as an easy array that can be used in a `for` loop.
|
||||
const SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)];
|
||||
const CLONE_SCENARIOS: [(&str, bool); 2] = [("clone", false), ("reflect", true)];
|
||||
|
||||
/// Benchmarks cloning a single entity with 10 components and no children.
|
||||
fn single(c: &mut Criterion) {
|
||||
@ -178,7 +150,7 @@ fn single(c: &mut Criterion) {
|
||||
// We're cloning 1 entity.
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
for (id, clone_via_reflect) in CLONE_SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone::<ComplexBundle>(b, clone_via_reflect);
|
||||
});
|
||||
@ -194,9 +166,9 @@ fn hierarchy_tall(c: &mut Criterion) {
|
||||
// We're cloning both the root entity and its 50 descendents.
|
||||
group.throughput(Throughput::Elements(51));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
for (id, clone_via_reflect) in CLONE_SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<C1>(b, 50, 1, clone_via_reflect);
|
||||
bench_clone_hierarchy::<C<1>>(b, 50, 1, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
@ -210,9 +182,9 @@ fn hierarchy_wide(c: &mut Criterion) {
|
||||
// We're cloning both the root entity and its 50 direct children.
|
||||
group.throughput(Throughput::Elements(51));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
for (id, clone_via_reflect) in CLONE_SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<C1>(b, 1, 50, clone_via_reflect);
|
||||
bench_clone_hierarchy::<C<1>>(b, 1, 50, clone_via_reflect);
|
||||
});
|
||||
}
|
||||
|
||||
@ -228,7 +200,7 @@ fn hierarchy_many(c: &mut Criterion) {
|
||||
// of entities spawned in `bench_clone_hierarchy()` with a `println!()` statement. :)
|
||||
group.throughput(Throughput::Elements(364));
|
||||
|
||||
for (id, clone_via_reflect) in SCENARIOS {
|
||||
for (id, clone_via_reflect) in CLONE_SCENARIOS {
|
||||
group.bench_function(id, |b| {
|
||||
bench_clone_hierarchy::<ComplexBundle>(b, 5, 3, clone_via_reflect);
|
||||
});
|
||||
@ -236,3 +208,157 @@ fn hierarchy_many(c: &mut Criterion) {
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
/// Filter scenario variant for bot opt-in and opt-out filters
|
||||
#[derive(Clone, Copy)]
|
||||
#[expect(
|
||||
clippy::enum_variant_names,
|
||||
reason = "'Opt' is not understood as an prefix but `OptOut'/'OptIn' are"
|
||||
)]
|
||||
enum FilterScenario {
|
||||
OptOutNone,
|
||||
OptOutNoneKeep(bool),
|
||||
OptOutAll,
|
||||
OptInNone,
|
||||
OptInAll,
|
||||
OptInAllWithoutRequired,
|
||||
OptInAllKeep(bool),
|
||||
OptInAllKeepWithoutRequired(bool),
|
||||
}
|
||||
|
||||
impl From<FilterScenario> for String {
|
||||
fn from(value: FilterScenario) -> Self {
|
||||
match value {
|
||||
FilterScenario::OptOutNone => "opt_out_none",
|
||||
FilterScenario::OptOutNoneKeep(true) => "opt_out_none_keep_none",
|
||||
FilterScenario::OptOutNoneKeep(false) => "opt_out_none_keep_all",
|
||||
FilterScenario::OptOutAll => "opt_out_all",
|
||||
FilterScenario::OptInNone => "opt_in_none",
|
||||
FilterScenario::OptInAll => "opt_in_all",
|
||||
FilterScenario::OptInAllWithoutRequired => "opt_in_all_without_required",
|
||||
FilterScenario::OptInAllKeep(true) => "opt_in_all_keep_none",
|
||||
FilterScenario::OptInAllKeep(false) => "opt_in_all_keep_all",
|
||||
FilterScenario::OptInAllKeepWithoutRequired(true) => {
|
||||
"opt_in_all_keep_none_without_required"
|
||||
}
|
||||
FilterScenario::OptInAllKeepWithoutRequired(false) => {
|
||||
"opt_in_all_keep_all_without_required"
|
||||
}
|
||||
}
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Common scenarios for different filter to be benchmarked.
|
||||
const FILTER_SCENARIOS: [FilterScenario; 11] = [
|
||||
FilterScenario::OptOutNone,
|
||||
FilterScenario::OptOutNoneKeep(true),
|
||||
FilterScenario::OptOutNoneKeep(false),
|
||||
FilterScenario::OptOutAll,
|
||||
FilterScenario::OptInNone,
|
||||
FilterScenario::OptInAll,
|
||||
FilterScenario::OptInAllWithoutRequired,
|
||||
FilterScenario::OptInAllKeep(true),
|
||||
FilterScenario::OptInAllKeep(false),
|
||||
FilterScenario::OptInAllKeepWithoutRequired(true),
|
||||
FilterScenario::OptInAllKeepWithoutRequired(false),
|
||||
];
|
||||
|
||||
/// A helper function that benchmarks running [`EntityCloner::clone_entity`] with a bundle `B`.
|
||||
///
|
||||
/// The bundle must implement [`Default`], which is used to create the first entity that gets its components cloned
|
||||
/// in the benchmark. It may also be used to populate the target entity depending on the scenario.
|
||||
fn bench_filter<B: Bundle + Default>(b: &mut Bencher, scenario: FilterScenario) {
|
||||
let mut world = World::default();
|
||||
let mut spawn = |empty| match empty {
|
||||
false => world.spawn(B::default()).id(),
|
||||
true => world.spawn_empty().id(),
|
||||
};
|
||||
let source = spawn(false);
|
||||
let (target, mut cloner);
|
||||
|
||||
match scenario {
|
||||
FilterScenario::OptOutNone => {
|
||||
target = spawn(true);
|
||||
cloner = EntityCloner::default();
|
||||
}
|
||||
FilterScenario::OptOutNoneKeep(is_new) => {
|
||||
target = spawn(is_new);
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.insert_mode(InsertMode::Keep);
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptOutAll => {
|
||||
target = spawn(true);
|
||||
let mut builder = EntityCloner::build_opt_out(&mut world);
|
||||
builder.deny::<B>();
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInNone => {
|
||||
target = spawn(true);
|
||||
let builder = EntityCloner::build_opt_in(&mut world);
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAll => {
|
||||
target = spawn(true);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.allow::<B>();
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAllWithoutRequired => {
|
||||
target = spawn(true);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.without_required_components(|builder| {
|
||||
builder.allow::<B>();
|
||||
});
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAllKeep(is_new) => {
|
||||
target = spawn(is_new);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.allow_if_new::<B>();
|
||||
cloner = builder.finish();
|
||||
}
|
||||
FilterScenario::OptInAllKeepWithoutRequired(is_new) => {
|
||||
target = spawn(is_new);
|
||||
let mut builder = EntityCloner::build_opt_in(&mut world);
|
||||
builder.without_required_components(|builder| {
|
||||
builder.allow_if_new::<B>();
|
||||
});
|
||||
cloner = builder.finish();
|
||||
}
|
||||
}
|
||||
|
||||
b.iter(|| {
|
||||
// clones the given entity into the target
|
||||
cloner.clone_entity(&mut world, black_box(source), black_box(target));
|
||||
world.flush();
|
||||
});
|
||||
}
|
||||
|
||||
/// Benchmarks filtering of cloning a single entity with 5 unclonable components (each requiring 1 unclonable component) into a target.
|
||||
fn filter(c: &mut Criterion) {
|
||||
#[derive(Component, Default)]
|
||||
#[component(clone_behavior = Ignore)]
|
||||
struct C<const N: usize>;
|
||||
|
||||
#[derive(Component, Default)]
|
||||
#[component(clone_behavior = Ignore)]
|
||||
#[require(C::<N>)]
|
||||
struct R<const N: usize>;
|
||||
|
||||
type RequiringBundle = (R<1>, R<2>, R<3>, R<4>, R<5>);
|
||||
|
||||
let mut group = c.benchmark_group(bench!("filter"));
|
||||
|
||||
// We're cloning 1 entity into a target.
|
||||
group.throughput(Throughput::Elements(1));
|
||||
|
||||
for scenario in FILTER_SCENARIOS {
|
||||
group.bench_function(scenario, |b| {
|
||||
bench_filter::<RequiringBundle>(b, scenario);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
pub struct Benchmark<const SIZE: usize>(Events<BenchEvent<SIZE>>);
|
||||
@ -10,7 +10,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
let mut events = Events::default();
|
||||
|
||||
for _ in 0..count {
|
||||
events.send(BenchEvent([0u8; SIZE]));
|
||||
events.write(BenchEvent([0u8; SIZE]));
|
||||
}
|
||||
|
||||
Self(events)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
mod iter;
|
||||
mod send;
|
||||
mod write;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
|
||||
@ -10,20 +10,20 @@ fn send(c: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_4_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<4>::new(count);
|
||||
group.bench_function(format!("size_4_events_{count}"), |b| {
|
||||
let mut bench = write::Benchmark::<4>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_16_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<16>::new(count);
|
||||
group.bench_function(format!("size_16_events_{count}"), |b| {
|
||||
let mut bench = write::Benchmark::<16>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_512_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<512>::new(count);
|
||||
group.bench_function(format!("size_512_events_{count}"), |b| {
|
||||
let mut bench = write::Benchmark::<512>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
@ -35,19 +35,19 @@ fn iter(c: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_4_events_{}", count), |b| {
|
||||
group.bench_function(format!("size_4_events_{count}"), |b| {
|
||||
let mut bench = iter::Benchmark::<4>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_16_events_{}", count), |b| {
|
||||
group.bench_function(format!("size_16_events_{count}"), |b| {
|
||||
let mut bench = iter::Benchmark::<4>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_512_events_{}", count), |b| {
|
||||
group.bench_function(format!("size_512_events_{count}"), |b| {
|
||||
let mut bench = iter::Benchmark::<512>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
|
||||
#[derive(Event)]
|
||||
#[derive(Event, BufferedEvent)]
|
||||
struct BenchEvent<const SIZE: usize>([u8; SIZE]);
|
||||
|
||||
impl<const SIZE: usize> Default for BenchEvent<SIZE> {
|
||||
@ -21,7 +21,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
// Force both internal buffers to be allocated.
|
||||
for _ in 0..2 {
|
||||
for _ in 0..count {
|
||||
events.send(BenchEvent([0u8; SIZE]));
|
||||
events.write(BenchEvent([0u8; SIZE]));
|
||||
}
|
||||
events.update();
|
||||
}
|
||||
@ -32,7 +32,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
|
||||
pub fn run(&mut self) {
|
||||
for _ in 0..self.count {
|
||||
self.events
|
||||
.send(core::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
.write(core::hint::black_box(BenchEvent([0u8; SIZE])));
|
||||
}
|
||||
self.events.update();
|
||||
}
|
||||
@ -45,7 +45,6 @@ pub fn heavy_compute(c: &mut Criterion) {
|
||||
|
||||
let mut system = IntoSystem::into_system(sys);
|
||||
system.initialize(&mut world);
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
|
||||
b.iter(move || system.run((), &mut world));
|
||||
});
|
||||
|
||||
@ -37,12 +37,11 @@ impl Benchmark {
|
||||
|
||||
let mut system = IntoSystem::into_system(query_system);
|
||||
system.initialize(&mut world);
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
Self(world, Box::new(system))
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn run(&mut self) {
|
||||
self.1.run((), &mut self.0);
|
||||
self.1.run((), &mut self.0).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ fn par_iter_simple(c: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for f in [0, 10, 100, 1000] {
|
||||
group.bench_function(format!("with_{}_fragment", f), |b| {
|
||||
group.bench_function(format!("with_{f}_fragment"), |b| {
|
||||
let mut bench = par_iter_simple::Benchmark::new(f);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
use criterion::criterion_main;
|
||||
|
||||
mod bundles;
|
||||
mod change_detection;
|
||||
mod components;
|
||||
mod empty_archetypes;
|
||||
@ -18,6 +19,7 @@ mod scheduling;
|
||||
mod world;
|
||||
|
||||
criterion_main!(
|
||||
bundles::benches,
|
||||
change_detection::benches,
|
||||
components::benches,
|
||||
empty_archetypes::benches,
|
||||
|
||||
@ -61,14 +61,10 @@ pub fn event_propagation(criterion: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
#[derive(Clone, Component)]
|
||||
#[derive(Event, EntityEvent, Clone, Component)]
|
||||
#[entity_event(traversal = &'static ChildOf, auto_propagate)]
|
||||
struct TestEvent<const N: usize> {}
|
||||
|
||||
impl<const N: usize> Event for TestEvent<N> {
|
||||
type Traversal = &'static ChildOf;
|
||||
const AUTO_PROPAGATE: bool = true;
|
||||
}
|
||||
|
||||
fn send_events<const N: usize, const N_EVENTS: usize>(world: &mut World, leaves: &[Entity]) {
|
||||
let target = leaves.iter().choose(&mut rand::thread_rng()).unwrap();
|
||||
|
||||
@ -117,6 +113,6 @@ fn add_listeners_to_hierarchy<const DENSITY: usize, const N: usize>(
|
||||
}
|
||||
}
|
||||
|
||||
fn empty_listener<const N: usize>(trigger: Trigger<TestEvent<N>>) {
|
||||
fn empty_listener<const N: usize>(trigger: On<TestEvent<N>>) {
|
||||
black_box(trigger);
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use bevy_ecs::{
|
||||
event::Event,
|
||||
observer::{Trigger, TriggerTargets},
|
||||
event::{EntityEvent, Event},
|
||||
observer::{On, TriggerTargets},
|
||||
world::World,
|
||||
};
|
||||
|
||||
@ -13,7 +13,7 @@ fn deterministic_rand() -> ChaCha8Rng {
|
||||
ChaCha8Rng::seed_from_u64(42)
|
||||
}
|
||||
|
||||
#[derive(Clone, Event)]
|
||||
#[derive(Clone, Event, EntityEvent)]
|
||||
struct EventBase;
|
||||
|
||||
pub fn observe_simple(criterion: &mut Criterion) {
|
||||
@ -46,7 +46,7 @@ pub fn observe_simple(criterion: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn empty_listener_base(trigger: Trigger<EventBase>) {
|
||||
fn empty_listener_base(trigger: On<EventBase>) {
|
||||
black_box(trigger);
|
||||
}
|
||||
|
||||
|
||||
@ -24,7 +24,7 @@ pub fn run_condition_yes(criterion: &mut Criterion) {
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -46,7 +46,7 @@ pub fn run_condition_no(criterion: &mut Criterion) {
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -77,7 +77,7 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) {
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -105,7 +105,7 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) {
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
|
||||
@ -26,7 +26,7 @@ pub fn empty_systems(criterion: &mut Criterion) {
|
||||
schedule.add_systems(empty);
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -38,7 +38,7 @@ pub fn empty_systems(criterion: &mut Criterion) {
|
||||
schedule.add_systems((empty, empty, empty, empty, empty));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{amount}_systems"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -79,10 +79,7 @@ pub fn busy_systems(criterion: &mut Criterion) {
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
entity_bunches, system_amount
|
||||
),
|
||||
format!("{entity_bunches:02}x_entities_{system_amount:02}_systems"),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
@ -128,10 +125,7 @@ pub fn contrived(criterion: &mut Criterion) {
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
entity_bunches, system_amount
|
||||
),
|
||||
format!("{entity_bunches:02}x_entities_{system_amount:02}_systems"),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
|
||||
@ -37,7 +37,7 @@ pub fn spawn_commands(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
@ -62,6 +62,31 @@ pub fn spawn_commands(criterion: &mut Criterion) {
|
||||
group.finish();
|
||||
}
|
||||
|
||||
pub fn nonempty_spawn_commands(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("nonempty_spawn_commands");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
for i in 0..entity_count {
|
||||
if black_box(i % 2 == 0) {
|
||||
commands.spawn(A);
|
||||
}
|
||||
}
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
#[derive(Default, Component)]
|
||||
struct Matrix([[f32; 4]; 4]);
|
||||
|
||||
@ -137,7 +162,7 @@ pub fn fake_commands(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for command_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_commands", command_count), |bencher| {
|
||||
group.bench_function(format!("{command_count}_commands"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
@ -182,7 +207,7 @@ pub fn sized_commands_impl<T: Default + Command>(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for command_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_commands", command_count), |bencher| {
|
||||
group.bench_function(format!("{command_count}_commands"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ pub fn world_despawn(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = World::default();
|
||||
|
||||
@ -13,7 +13,7 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = World::default();
|
||||
|
||||
@ -17,6 +17,7 @@ criterion_group!(
|
||||
benches,
|
||||
empty_commands,
|
||||
spawn_commands,
|
||||
nonempty_spawn_commands,
|
||||
insert_commands,
|
||||
fake_commands,
|
||||
zero_sized_commands,
|
||||
|
||||
@ -13,7 +13,7 @@ pub fn world_spawn(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
let mut world = World::default();
|
||||
bencher.iter(|| {
|
||||
for _ in 0..entity_count {
|
||||
|
||||
@ -49,7 +49,7 @@ pub fn world_entity(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities"), |bencher| {
|
||||
let world = setup::<Table>(entity_count);
|
||||
|
||||
bencher.iter(|| {
|
||||
@ -72,7 +72,7 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let world = setup::<Table>(entity_count);
|
||||
|
||||
bencher.iter(|| {
|
||||
@ -84,7 +84,7 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let world = setup::<Sparse>(entity_count);
|
||||
|
||||
bencher.iter(|| {
|
||||
@ -107,7 +107,7 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let mut world = setup::<Table>(entity_count);
|
||||
let mut query = world.query::<&Table>();
|
||||
|
||||
@ -120,7 +120,7 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_table_wide", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table_wide"), |bencher| {
|
||||
let mut world = setup_wide::<(
|
||||
WideTable<0>,
|
||||
WideTable<1>,
|
||||
@ -147,7 +147,7 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let mut world = setup::<Sparse>(entity_count);
|
||||
let mut query = world.query::<&Sparse>();
|
||||
|
||||
@ -160,9 +160,7 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
}
|
||||
});
|
||||
});
|
||||
group.bench_function(
|
||||
format!("{}_entities_sparse_wide", entity_count),
|
||||
|bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse_wide"), |bencher| {
|
||||
let mut world = setup_wide::<(
|
||||
WideSparse<0>,
|
||||
WideSparse<1>,
|
||||
@ -183,14 +181,12 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
// SAFETY: Range is exclusive.
|
||||
let entity = Entity::from_raw(EntityRow::new(unsafe {
|
||||
NonMaxU32::new_unchecked(i)
|
||||
}));
|
||||
let entity =
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(query.get(&world, entity).is_ok());
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
group.finish();
|
||||
@ -202,7 +198,7 @@ pub fn world_query_iter(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let mut world = setup::<Table>(entity_count);
|
||||
let mut query = world.query::<&Table>();
|
||||
|
||||
@ -216,7 +212,7 @@ pub fn world_query_iter(criterion: &mut Criterion) {
|
||||
assert_eq!(black_box(count), entity_count);
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let mut world = setup::<Sparse>(entity_count);
|
||||
let mut query = world.query::<&Sparse>();
|
||||
|
||||
@ -241,7 +237,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let mut world = setup::<Table>(entity_count);
|
||||
let mut query = world.query::<&Table>();
|
||||
|
||||
@ -255,7 +251,7 @@ pub fn world_query_for_each(criterion: &mut Criterion) {
|
||||
assert_eq!(black_box(count), entity_count);
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let mut world = setup::<Sparse>(entity_count);
|
||||
let mut query = world.query::<&Sparse>();
|
||||
|
||||
@ -280,7 +276,7 @@ pub fn query_get(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_entities_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_table"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut entities: Vec<_> = world
|
||||
.spawn_batch((0..entity_count).map(|_| Table::default()))
|
||||
@ -299,7 +295,7 @@ pub fn query_get(criterion: &mut Criterion) {
|
||||
assert_eq!(black_box(count), entity_count);
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_entities_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_entities_sparse"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut entities: Vec<_> = world
|
||||
.spawn_batch((0..entity_count).map(|_| Sparse::default()))
|
||||
@ -329,7 +325,7 @@ pub fn query_get_many<const N: usize>(criterion: &mut Criterion) {
|
||||
group.measurement_time(core::time::Duration::from_secs(2 * N as u64));
|
||||
|
||||
for entity_count in RANGE.map(|i| i * 10_000) {
|
||||
group.bench_function(format!("{}_calls_table", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_calls_table"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut entity_groups: Vec<_> = (0..entity_count)
|
||||
.map(|_| [(); N].map(|_| world.spawn(Table::default()).id()))
|
||||
@ -352,7 +348,7 @@ pub fn query_get_many<const N: usize>(criterion: &mut Criterion) {
|
||||
assert_eq!(black_box(count), entity_count);
|
||||
});
|
||||
});
|
||||
group.bench_function(format!("{}_calls_sparse", entity_count), |bencher| {
|
||||
group.bench_function(format!("{entity_count}_calls_sparse"), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut entity_groups: Vec<_> = (0..entity_count)
|
||||
.map(|_| [(); N].map(|_| world.spawn(Sparse::default()).id()))
|
||||
|
||||
@ -32,7 +32,7 @@ fn segment_ease(c: &mut Criterion) {
|
||||
|
||||
fn curve_position(c: &mut Criterion) {
|
||||
/// A helper function that benchmarks calling [`CubicCurve::position()`] over a generic [`VectorSpace`].
|
||||
fn bench_curve<M: Measurement, P: VectorSpace>(
|
||||
fn bench_curve<M: Measurement, P: VectorSpace<Scalar = f32>>(
|
||||
group: &mut BenchmarkGroup<M>,
|
||||
name: &str,
|
||||
curve: CubicCurve<P>,
|
||||
|
||||
@ -155,6 +155,7 @@ fn bench(c: &mut Criterion) {
|
||||
&mesh.positions,
|
||||
Some(&mesh.normals),
|
||||
Some(&mesh.indices),
|
||||
None,
|
||||
backface_culling,
|
||||
);
|
||||
|
||||
|
||||
@ -142,7 +142,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
|
||||
|
||||
fn u64_to_n_byte_key(k: u64, n: usize) -> String {
|
||||
let mut key = String::with_capacity(n);
|
||||
write!(&mut key, "{}", k).unwrap();
|
||||
write!(&mut key, "{k}").unwrap();
|
||||
|
||||
// Pad key to n bytes.
|
||||
key.extend(iter::repeat_n('\0', n - key.len()));
|
||||
|
||||
@ -55,7 +55,7 @@ fn concrete_struct_field(criterion: &mut Criterion) {
|
||||
&s,
|
||||
|bencher, s| {
|
||||
let field_names = (0..field_count)
|
||||
.map(|i| format!("field_{}", i))
|
||||
.map(|i| format!("field_{i}"))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
bencher.iter(|| {
|
||||
@ -256,7 +256,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
|
||||
|
||||
let mut base = DynamicStruct::default();
|
||||
for i in 0..field_count {
|
||||
let field_name = format!("field_{}", i);
|
||||
let field_name = format!("field_{i}");
|
||||
base.insert(&field_name, 1u32);
|
||||
}
|
||||
|
||||
@ -283,7 +283,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
|
||||
let mut base = DynamicStruct::default();
|
||||
let mut patch = DynamicStruct::default();
|
||||
for i in 0..field_count {
|
||||
let field_name = format!("field_{}", i);
|
||||
let field_name = format!("field_{i}");
|
||||
base.insert(&field_name, 0u32);
|
||||
patch.insert(&field_name, 1u32);
|
||||
}
|
||||
@ -309,11 +309,11 @@ fn dynamic_struct_insert(criterion: &mut Criterion) {
|
||||
|bencher, field_count| {
|
||||
let mut s = DynamicStruct::default();
|
||||
for i in 0..*field_count {
|
||||
let field_name = format!("field_{}", i);
|
||||
let field_name = format!("field_{i}");
|
||||
s.insert(&field_name, ());
|
||||
}
|
||||
|
||||
let field = format!("field_{}", field_count);
|
||||
let field = format!("field_{field_count}");
|
||||
bencher.iter_batched(
|
||||
|| s.to_dynamic_struct(),
|
||||
|mut s| {
|
||||
@ -339,7 +339,7 @@ fn dynamic_struct_get_field(criterion: &mut Criterion) {
|
||||
|bencher, field_count| {
|
||||
let mut s = DynamicStruct::default();
|
||||
for i in 0..*field_count {
|
||||
let field_name = format!("field_{}", i);
|
||||
let field_name = format!("field_{i}");
|
||||
s.insert(&field_name, ());
|
||||
}
|
||||
|
||||
|
||||
@ -41,7 +41,6 @@ disallowed-methods = [
|
||||
{ path = "f32::asinh", reason = "use bevy_math::ops::asinh instead for libm determinism" },
|
||||
{ path = "f32::acosh", reason = "use bevy_math::ops::acosh instead for libm determinism" },
|
||||
{ path = "f32::atanh", reason = "use bevy_math::ops::atanh instead for libm determinism" },
|
||||
{ path = "criterion::black_box", reason = "use core::hint::black_box instead" },
|
||||
]
|
||||
|
||||
# Require `bevy_ecs::children!` to use `[]` braces, instead of `()` or `{}`.
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_a11y"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides accessibility support for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy", "accessibility", "a11y"]
|
||||
@ -40,13 +40,13 @@ critical-section = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
|
||||
# other
|
||||
accesskit = { version = "0.18", default-features = false }
|
||||
accesskit = { version = "0.19", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = [
|
||||
"alloc",
|
||||
], optional = true }
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||
)]
|
||||
#![no_std]
|
||||
|
||||
@ -26,7 +26,8 @@ use accesskit::Node;
|
||||
use bevy_app::Plugin;
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Event},
|
||||
component::Component,
|
||||
event::{BufferedEvent, Event},
|
||||
resource::Resource,
|
||||
schedule::SystemSet,
|
||||
};
|
||||
@ -44,7 +45,7 @@ use serde::{Deserialize, Serialize};
|
||||
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
|
||||
|
||||
/// Wrapper struct for [`accesskit::ActionRequest`]. Required to allow it to be used as an `Event`.
|
||||
#[derive(Event, Deref, DerefMut)]
|
||||
#[derive(Event, BufferedEvent, Deref, DerefMut)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
pub struct ActionRequest(pub accesskit::ActionRequest);
|
||||
|
||||
|
||||
@ -1,47 +1,46 @@
|
||||
[package]
|
||||
name = "bevy_animation"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides animation functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
|
||||
"petgraph",
|
||||
] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
# other
|
||||
petgraph = { version = "0.7", features = ["serde-1"] }
|
||||
ron = "0.8"
|
||||
petgraph = { version = "0.8", features = ["serde-1"] }
|
||||
ron = "0.10"
|
||||
serde = "1"
|
||||
blake3 = { version = "1.0" }
|
||||
downcast-rs = { version = "2", default-features = false, features = ["std"] }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
derive_more = { version = "1", default-features = false, features = ["from"] }
|
||||
derive_more = { version = "2", default-features = false, features = ["from"] }
|
||||
either = "1.13"
|
||||
thread_local = "1"
|
||||
uuid = { version = "1.13.1", features = ["v4"] }
|
||||
smallvec = "1"
|
||||
smallvec = { version = "1", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
||||
@ -55,7 +55,7 @@ pub struct CubicKeyframeCurve<T> {
|
||||
|
||||
impl<V> Curve<V> for CubicKeyframeCurve<V>
|
||||
where
|
||||
V: VectorSpace,
|
||||
V: VectorSpace<Scalar = f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
@ -179,7 +179,7 @@ pub struct WideLinearKeyframeCurve<T> {
|
||||
|
||||
impl<T> IterableCurve<T> for WideLinearKeyframeCurve<T>
|
||||
where
|
||||
T: VectorSpace,
|
||||
T: VectorSpace<Scalar = f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
@ -289,7 +289,7 @@ pub struct WideCubicKeyframeCurve<T> {
|
||||
|
||||
impl<T> IterableCurve<T> for WideCubicKeyframeCurve<T>
|
||||
where
|
||||
T: VectorSpace,
|
||||
T: VectorSpace<Scalar = f32>,
|
||||
{
|
||||
#[inline]
|
||||
fn domain(&self) -> Interval {
|
||||
@ -406,7 +406,7 @@ fn cubic_spline_interpolation<T>(
|
||||
step_duration: f32,
|
||||
) -> T
|
||||
where
|
||||
T: VectorSpace,
|
||||
T: VectorSpace<Scalar = f32>,
|
||||
{
|
||||
let coeffs = (vec4(2.0, 1.0, -2.0, 1.0) * lerp + vec4(-3.0, -2.0, 3.0, -1.0)) * lerp;
|
||||
value_start * (coeffs.x * lerp + 1.0)
|
||||
@ -415,7 +415,7 @@ where
|
||||
+ tangent_in_end * step_duration * lerp * coeffs.w
|
||||
}
|
||||
|
||||
fn cubic_spline_interpolate_slices<'a, T: VectorSpace>(
|
||||
fn cubic_spline_interpolate_slices<'a, T: VectorSpace<Scalar = f32>>(
|
||||
width: usize,
|
||||
first: &'a [T],
|
||||
second: &'a [T],
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
//! The animation graph, which allows animations to be blended together.
|
||||
|
||||
use core::{
|
||||
fmt::Write,
|
||||
iter,
|
||||
ops::{Index, IndexMut, Range},
|
||||
};
|
||||
use std::io::{self, Write};
|
||||
use std::io;
|
||||
|
||||
use bevy_asset::{
|
||||
io::Reader, Asset, AssetEvent, AssetId, AssetLoader, AssetPath, Assets, Handle, LoadContext,
|
||||
@ -18,7 +19,7 @@ use bevy_ecs::{
|
||||
system::{Res, ResMut},
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectSerialize};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect};
|
||||
use derive_more::derive::From;
|
||||
use petgraph::{
|
||||
graph::{DiGraph, NodeIndex},
|
||||
@ -28,6 +29,7 @@ use ron::de::SpannedError;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
use thiserror::Error;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{AnimationClip, AnimationTargetId};
|
||||
|
||||
@ -107,9 +109,8 @@ use crate::{AnimationClip, AnimationTargetId};
|
||||
/// [RON]: https://github.com/ron-rs/ron
|
||||
///
|
||||
/// [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md
|
||||
#[derive(Asset, Reflect, Clone, Debug, Serialize)]
|
||||
#[reflect(Serialize, Debug, Clone)]
|
||||
#[serde(into = "SerializedAnimationGraph")]
|
||||
#[derive(Asset, Reflect, Clone, Debug)]
|
||||
#[reflect(Debug, Clone)]
|
||||
pub struct AnimationGraph {
|
||||
/// The `petgraph` data structure that defines the animation graph.
|
||||
pub graph: AnimationDiGraph,
|
||||
@ -241,20 +242,40 @@ pub enum AnimationNodeType {
|
||||
#[derive(Default)]
|
||||
pub struct AnimationGraphAssetLoader;
|
||||
|
||||
/// Various errors that can occur when serializing or deserializing animation
|
||||
/// graphs to and from RON, respectively.
|
||||
/// Errors that can occur when serializing animation graphs to RON.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AnimationGraphSaveError {
|
||||
/// An I/O error occurred.
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
/// An error occurred in RON serialization.
|
||||
#[error(transparent)]
|
||||
Ron(#[from] ron::Error),
|
||||
/// An error occurred converting the graph to its serialization form.
|
||||
#[error(transparent)]
|
||||
ConvertToSerialized(#[from] NonPathHandleError),
|
||||
}
|
||||
|
||||
/// Errors that can occur when deserializing animation graphs from RON.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum AnimationGraphLoadError {
|
||||
/// An I/O error occurred.
|
||||
#[error("I/O")]
|
||||
#[error(transparent)]
|
||||
Io(#[from] io::Error),
|
||||
/// An error occurred in RON serialization or deserialization.
|
||||
#[error("RON serialization")]
|
||||
/// An error occurred in RON deserialization.
|
||||
#[error(transparent)]
|
||||
Ron(#[from] ron::Error),
|
||||
/// An error occurred in RON deserialization, and the location of the error
|
||||
/// is supplied.
|
||||
#[error("RON serialization")]
|
||||
#[error(transparent)]
|
||||
SpannedRon(#[from] SpannedError),
|
||||
/// The deserialized graph contained legacy data that we no longer support.
|
||||
#[error(
|
||||
"The deserialized AnimationGraph contained an AnimationClip referenced by an AssetId, \
|
||||
which is no longer supported. Consider manually deserializing the SerializedAnimationGraph \
|
||||
type and determine how to migrate any SerializedAnimationClip::AssetId animation clips"
|
||||
)]
|
||||
GraphContainsLegacyAssetId,
|
||||
}
|
||||
|
||||
/// Acceleration structures for animation graphs that allows Bevy to evaluate
|
||||
@ -387,18 +408,32 @@ pub struct SerializedAnimationGraphNode {
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum SerializedAnimationNodeType {
|
||||
/// Corresponds to [`AnimationNodeType::Clip`].
|
||||
Clip(SerializedAnimationClip),
|
||||
Clip(MigrationSerializedAnimationClip),
|
||||
/// Corresponds to [`AnimationNodeType::Blend`].
|
||||
Blend,
|
||||
/// Corresponds to [`AnimationNodeType::Add`].
|
||||
Add,
|
||||
}
|
||||
|
||||
/// A version of `Handle<AnimationClip>` suitable for serializing as an asset.
|
||||
/// A type to facilitate migration from the legacy format of [`SerializedAnimationGraph`] to the
|
||||
/// new format.
|
||||
///
|
||||
/// This replaces any handle that has a path with an [`AssetPath`]. Failing
|
||||
/// that, the asset ID is serialized directly.
|
||||
/// By using untagged serde deserialization, we can try to deserialize the modern form, then
|
||||
/// fallback to the legacy form. Users must migrate to the modern form by Bevy 0.18.
|
||||
// TODO: Delete this after Bevy 0.17.
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum MigrationSerializedAnimationClip {
|
||||
/// This is the new type of this field.
|
||||
Modern(AssetPath<'static>),
|
||||
/// This is the legacy type of this field. Users must migrate away from this.
|
||||
#[serde(skip_serializing)]
|
||||
Legacy(SerializedAnimationClip),
|
||||
}
|
||||
|
||||
/// The legacy form of serialized animation clips. This allows raw asset IDs to be deserialized.
|
||||
// TODO: Delete this after Bevy 0.17.
|
||||
#[derive(Deserialize)]
|
||||
pub enum SerializedAnimationClip {
|
||||
/// Records an asset path.
|
||||
AssetPath(AssetPath<'static>),
|
||||
@ -647,12 +682,13 @@ impl AnimationGraph {
|
||||
///
|
||||
/// If writing to a file, it can later be loaded with the
|
||||
/// [`AnimationGraphAssetLoader`] to reconstruct the graph.
|
||||
pub fn save<W>(&self, writer: &mut W) -> Result<(), AnimationGraphLoadError>
|
||||
pub fn save<W>(&self, writer: &mut W) -> Result<(), AnimationGraphSaveError>
|
||||
where
|
||||
W: Write,
|
||||
{
|
||||
let mut ron_serializer = ron::ser::Serializer::new(writer, None)?;
|
||||
Ok(self.serialize(&mut ron_serializer)?)
|
||||
let serialized_graph: SerializedAnimationGraph = self.clone().try_into()?;
|
||||
Ok(serialized_graph.serialize(&mut ron_serializer)?)
|
||||
}
|
||||
|
||||
/// Adds an animation target (bone) to the mask group with the given ID.
|
||||
@ -757,18 +793,41 @@ impl AssetLoader for AnimationGraphAssetLoader {
|
||||
let serialized_animation_graph = SerializedAnimationGraph::deserialize(&mut deserializer)
|
||||
.map_err(|err| deserializer.span_error(err))?;
|
||||
|
||||
// Load all `AssetPath`s to convert from a
|
||||
// `SerializedAnimationGraph` to a real `AnimationGraph`.
|
||||
Ok(AnimationGraph {
|
||||
graph: serialized_animation_graph.graph.map(
|
||||
|_, serialized_node| AnimationGraphNode {
|
||||
// Load all `AssetPath`s to convert from a `SerializedAnimationGraph` to a real
|
||||
// `AnimationGraph`. This is effectively a `DiGraph::map`, but this allows us to return
|
||||
// errors.
|
||||
let mut animation_graph = DiGraph::with_capacity(
|
||||
serialized_animation_graph.graph.node_count(),
|
||||
serialized_animation_graph.graph.edge_count(),
|
||||
);
|
||||
|
||||
let mut already_warned = false;
|
||||
for serialized_node in serialized_animation_graph.graph.node_weights() {
|
||||
animation_graph.add_node(AnimationGraphNode {
|
||||
node_type: match serialized_node.node_type {
|
||||
SerializedAnimationNodeType::Clip(ref clip) => match clip {
|
||||
SerializedAnimationClip::AssetId(asset_id) => {
|
||||
AnimationNodeType::Clip(Handle::Weak(*asset_id))
|
||||
MigrationSerializedAnimationClip::Modern(path) => {
|
||||
AnimationNodeType::Clip(load_context.load(path.clone()))
|
||||
}
|
||||
SerializedAnimationClip::AssetPath(asset_path) => {
|
||||
AnimationNodeType::Clip(load_context.load(asset_path))
|
||||
MigrationSerializedAnimationClip::Legacy(
|
||||
SerializedAnimationClip::AssetPath(path),
|
||||
) => {
|
||||
if !already_warned {
|
||||
let path = load_context.asset_path();
|
||||
warn!(
|
||||
"Loaded an AnimationGraph asset at \"{path}\" which contains a \
|
||||
legacy-style SerializedAnimationClip. Please re-save the asset \
|
||||
using AnimationGraph::save to automatically migrate to the new \
|
||||
format"
|
||||
);
|
||||
already_warned = true;
|
||||
}
|
||||
AnimationNodeType::Clip(load_context.load(path.clone()))
|
||||
}
|
||||
MigrationSerializedAnimationClip::Legacy(
|
||||
SerializedAnimationClip::AssetId(_),
|
||||
) => {
|
||||
return Err(AnimationGraphLoadError::GraphContainsLegacyAssetId);
|
||||
}
|
||||
},
|
||||
SerializedAnimationNodeType::Blend => AnimationNodeType::Blend,
|
||||
@ -776,9 +835,13 @@ impl AssetLoader for AnimationGraphAssetLoader {
|
||||
},
|
||||
mask: serialized_node.mask,
|
||||
weight: serialized_node.weight,
|
||||
},
|
||||
|_, _| (),
|
||||
),
|
||||
});
|
||||
}
|
||||
for edge in serialized_animation_graph.graph.raw_edges() {
|
||||
animation_graph.add_edge(edge.source(), edge.target(), ());
|
||||
}
|
||||
Ok(AnimationGraph {
|
||||
graph: animation_graph,
|
||||
root: serialized_animation_graph.root,
|
||||
mask_groups: serialized_animation_graph.mask_groups,
|
||||
})
|
||||
@ -789,37 +852,50 @@ impl AssetLoader for AnimationGraphAssetLoader {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AnimationGraph> for SerializedAnimationGraph {
|
||||
fn from(animation_graph: AnimationGraph) -> Self {
|
||||
// If any of the animation clips have paths, then serialize them as
|
||||
// `SerializedAnimationClip::AssetPath` so that the
|
||||
// `AnimationGraphAssetLoader` can load them.
|
||||
Self {
|
||||
graph: animation_graph.graph.map(
|
||||
|_, node| SerializedAnimationGraphNode {
|
||||
impl TryFrom<AnimationGraph> for SerializedAnimationGraph {
|
||||
type Error = NonPathHandleError;
|
||||
|
||||
fn try_from(animation_graph: AnimationGraph) -> Result<Self, NonPathHandleError> {
|
||||
// Convert all the `Handle<AnimationClip>` to AssetPath, so that
|
||||
// `AnimationGraphAssetLoader` can load them. This is effectively just doing a
|
||||
// `DiGraph::map`, except we need to return an error if any handles aren't associated to a
|
||||
// path.
|
||||
let mut serialized_graph = DiGraph::with_capacity(
|
||||
animation_graph.graph.node_count(),
|
||||
animation_graph.graph.edge_count(),
|
||||
);
|
||||
for node in animation_graph.graph.node_weights() {
|
||||
serialized_graph.add_node(SerializedAnimationGraphNode {
|
||||
weight: node.weight,
|
||||
mask: node.mask,
|
||||
node_type: match node.node_type {
|
||||
AnimationNodeType::Clip(ref clip) => match clip.path() {
|
||||
Some(path) => SerializedAnimationNodeType::Clip(
|
||||
SerializedAnimationClip::AssetPath(path.clone()),
|
||||
),
|
||||
None => SerializedAnimationNodeType::Clip(
|
||||
SerializedAnimationClip::AssetId(clip.id()),
|
||||
MigrationSerializedAnimationClip::Modern(path.clone()),
|
||||
),
|
||||
None => return Err(NonPathHandleError),
|
||||
},
|
||||
AnimationNodeType::Blend => SerializedAnimationNodeType::Blend,
|
||||
AnimationNodeType::Add => SerializedAnimationNodeType::Add,
|
||||
},
|
||||
},
|
||||
|_, _| (),
|
||||
),
|
||||
});
|
||||
}
|
||||
for edge in animation_graph.graph.raw_edges() {
|
||||
serialized_graph.add_edge(edge.source(), edge.target(), ());
|
||||
}
|
||||
Ok(Self {
|
||||
graph: serialized_graph,
|
||||
root: animation_graph.root,
|
||||
mask_groups: animation_graph.mask_groups,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Error for when only path [`Handle`]s are supported.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("AnimationGraph contains a handle to an AnimationClip that does not correspond to an asset path")]
|
||||
pub struct NonPathHandleError;
|
||||
|
||||
/// A system that creates, updates, and removes [`ThreadedAnimationGraph`]
|
||||
/// structures for every changed [`AnimationGraph`].
|
||||
///
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![doc(
|
||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||
)]
|
||||
|
||||
//! Animation for the game engine Bevy
|
||||
@ -324,13 +324,13 @@ impl AnimationClip {
|
||||
.push(variable_curve);
|
||||
}
|
||||
|
||||
/// Add a untargeted [`Event`] to this [`AnimationClip`].
|
||||
/// Add an [`EntityEvent`] with no [`AnimationTarget`] to this [`AnimationClip`].
|
||||
///
|
||||
/// The `event` will be cloned and triggered on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||
pub fn add_event(&mut self, time: f32, event: impl Event + Clone) {
|
||||
pub fn add_event(&mut self, time: f32, event: impl EntityEvent + Clone) {
|
||||
self.add_event_fn(
|
||||
time,
|
||||
move |commands: &mut Commands, entity: Entity, _time: f32, _weight: f32| {
|
||||
@ -339,7 +339,7 @@ impl AnimationClip {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add an [`Event`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||
/// Add an [`EntityEvent`] to an [`AnimationTarget`] named by an [`AnimationTargetId`].
|
||||
///
|
||||
/// The `event` will be cloned and triggered on the entity matching the target once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
@ -349,7 +349,7 @@ impl AnimationClip {
|
||||
&mut self,
|
||||
target_id: AnimationTargetId,
|
||||
time: f32,
|
||||
event: impl Event + Clone,
|
||||
event: impl EntityEvent + Clone,
|
||||
) {
|
||||
self.add_event_fn_to_target(
|
||||
target_id,
|
||||
@ -360,19 +360,19 @@ impl AnimationClip {
|
||||
);
|
||||
}
|
||||
|
||||
/// Add a untargeted event function to this [`AnimationClip`].
|
||||
/// Add an event function with no [`AnimationTarget`] to this [`AnimationClip`].
|
||||
///
|
||||
/// The `func` will trigger on the [`AnimationPlayer`] entity once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event`].
|
||||
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event`].
|
||||
/// See also [`add_event_to_target`](Self::add_event_to_target).
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_animation::AnimationClip;
|
||||
/// # let mut clip = AnimationClip::default();
|
||||
/// clip.add_event_fn(1.0, |commands, entity, time, weight| {
|
||||
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// })
|
||||
/// ```
|
||||
pub fn add_event_fn(
|
||||
@ -388,14 +388,14 @@ impl AnimationClip {
|
||||
/// The `func` will trigger on the entity matching the target once the `time` (in seconds)
|
||||
/// is reached in the animation.
|
||||
///
|
||||
/// For a simpler [`Event`]-based alternative, see [`AnimationClip::add_event_to_target`].
|
||||
/// For a simpler [`EntityEvent`]-based alternative, see [`AnimationClip::add_event_to_target`].
|
||||
/// Use [`add_event`](Self::add_event) instead if you don't have a specific target.
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId};
|
||||
/// # let mut clip = AnimationClip::default();
|
||||
/// clip.add_event_fn_to_target(AnimationTargetId::from_iter(["Arm", "Hand"]), 1.0, |commands, entity, time, weight| {
|
||||
/// println!("Animation Event Triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// println!("Animation event triggered {entity:#?} at time {time} with weight {weight}");
|
||||
/// })
|
||||
/// ```
|
||||
pub fn add_event_fn_to_target(
|
||||
@ -1534,7 +1534,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
#[derive(Event, EntityEvent, Reflect, Clone)]
|
||||
struct A;
|
||||
|
||||
#[track_caller]
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_anti_aliasing"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides various anti aliasing implementations for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
@ -16,17 +16,17 @@ smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
|
||||
bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
|
||||
|
||||
# other
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
@ -11,15 +11,16 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
|
||||
prelude::Camera,
|
||||
render_graph::RenderGraphApp,
|
||||
render_graph::RenderGraphExt,
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d, uniform_buffer},
|
||||
*,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSystems,
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
};
|
||||
use bevy_utils::default;
|
||||
|
||||
mod node;
|
||||
|
||||
@ -95,20 +96,12 @@ impl ExtractComponent for ContrastAdaptiveSharpening {
|
||||
}
|
||||
}
|
||||
|
||||
const CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("ef83f0a5-51df-4b51-9ab7-b5fd1ae5a397");
|
||||
|
||||
/// Adds Support for Contrast Adaptive Sharpening (CAS).
|
||||
pub struct CasPlugin;
|
||||
|
||||
impl Plugin for CasPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(
|
||||
app,
|
||||
CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
|
||||
"robust_contrast_adaptive_sharpening.wgsl",
|
||||
Shader::from_wgsl
|
||||
);
|
||||
embedded_asset!(app, "robust_contrast_adaptive_sharpening.wgsl");
|
||||
|
||||
app.register_type::<ContrastAdaptiveSharpening>();
|
||||
app.add_plugins((
|
||||
@ -121,6 +114,7 @@ impl Plugin for CasPlugin {
|
||||
};
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<CasPipeline>>()
|
||||
.add_systems(RenderStartup, init_cas_pipeline)
|
||||
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare));
|
||||
|
||||
{
|
||||
@ -158,24 +152,22 @@ impl Plugin for CasPlugin {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
render_app.init_resource::<CasPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct CasPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for CasPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
let render_device = render_world.resource::<RenderDevice>();
|
||||
pub fn init_cas_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let texture_bind_group = render_device.create_bind_group_layout(
|
||||
"sharpening_texture_bind_group_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
@ -191,11 +183,15 @@ impl FromWorld for CasPipeline {
|
||||
|
||||
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
|
||||
|
||||
CasPipeline {
|
||||
commands.insert_resource(CasPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
fullscreen_shader: fullscreen_shader.clone(),
|
||||
fragment_shader: load_embedded_asset!(
|
||||
asset_server.as_ref(),
|
||||
"robust_contrast_adaptive_sharpening.wgsl"
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone, Copy)]
|
||||
@ -215,22 +211,18 @@ impl SpecializedRenderPipeline for CasPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("contrast_adaptive_sharpening".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: key.texture_format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
..default()
|
||||
}),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
//! Experimental rendering features.
|
||||
//!
|
||||
//! Experimental features are features with known problems, missing features,
|
||||
//! compatibility issues, low performance, and/or future breaking changes, but
|
||||
//! are included nonetheless for testing purposes.
|
||||
|
||||
pub mod taa {
|
||||
pub use crate::taa::{TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing};
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::BevyDefault as _;
|
||||
@ -11,14 +11,14 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
prelude::Camera,
|
||||
render_graph::{RenderGraphApp, ViewNodeRunner},
|
||||
render_graph::{RenderGraphExt, ViewNodeRunner},
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d},
|
||||
*,
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSystems,
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
};
|
||||
use bevy_utils::default;
|
||||
|
||||
@ -80,13 +80,11 @@ impl Default for Fxaa {
|
||||
}
|
||||
}
|
||||
|
||||
const FXAA_SHADER_HANDLE: Handle<Shader> = weak_handle!("fc58c0a8-01c0-46e9-94cc-83a794bae7b0");
|
||||
|
||||
/// Adds support for Fast Approximate Anti-Aliasing (FXAA)
|
||||
pub struct FxaaPlugin;
|
||||
impl Plugin for FxaaPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl);
|
||||
embedded_asset!(app, "fxaa.wgsl");
|
||||
|
||||
app.register_type::<Fxaa>();
|
||||
app.add_plugins(ExtractComponentPlugin::<Fxaa>::default());
|
||||
@ -96,6 +94,7 @@ impl Plugin for FxaaPlugin {
|
||||
};
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
|
||||
.add_systems(RenderStartup, init_fxaa_pipeline)
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_fxaa_pipelines.in_set(RenderSystems::Prepare),
|
||||
@ -119,24 +118,22 @@ impl Plugin for FxaaPlugin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
render_app.init_resource::<FxaaPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct FxaaPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for FxaaPipeline {
|
||||
fn from_world(render_world: &mut World) -> Self {
|
||||
let render_device = render_world.resource::<RenderDevice>();
|
||||
pub fn init_fxaa_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let texture_bind_group = render_device.create_bind_group_layout(
|
||||
"fxaa_texture_bind_group_layout",
|
||||
&BindGroupLayoutEntries::sequential(
|
||||
@ -155,11 +152,12 @@ impl FromWorld for FxaaPipeline {
|
||||
..default()
|
||||
});
|
||||
|
||||
FxaaPipeline {
|
||||
commands.insert_resource(FxaaPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
}
|
||||
}
|
||||
fullscreen_shader: fullscreen_shader.clone(),
|
||||
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "fxaa.wgsl"),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(Component)]
|
||||
@ -181,25 +179,21 @@ impl SpecializedRenderPipeline for FxaaPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("fxaa".into()),
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: FXAA_SHADER_HANDLE,
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs: vec![
|
||||
format!("EDGE_THRESH_{}", key.edge_threshold.get_str()).into(),
|
||||
format!("EDGE_THRESH_MIN_{}", key.edge_threshold_min.get_str()).into(),
|
||||
],
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: key.texture_format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
..default()
|
||||
}),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,26 +2,25 @@
|
||||
#![forbid(unsafe_code)]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
#![doc(
|
||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||
)]
|
||||
|
||||
use bevy_app::Plugin;
|
||||
use contrast_adaptive_sharpening::CasPlugin;
|
||||
use fxaa::FxaaPlugin;
|
||||
use smaa::SmaaPlugin;
|
||||
use taa::TemporalAntiAliasPlugin;
|
||||
|
||||
pub mod contrast_adaptive_sharpening;
|
||||
pub mod experimental;
|
||||
pub mod fxaa;
|
||||
pub mod smaa;
|
||||
|
||||
mod taa;
|
||||
pub mod taa;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AntiAliasingPlugin;
|
||||
impl Plugin for AntiAliasingPlugin {
|
||||
fn build(&self, app: &mut bevy_app::App) {
|
||||
app.add_plugins((FxaaPlugin, CasPlugin, SmaaPlugin));
|
||||
app.add_plugins((FxaaPlugin, SmaaPlugin, TemporalAntiAliasPlugin, CasPlugin));
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,9 +30,7 @@
|
||||
//!
|
||||
//! [SMAA]: https://www.iryoku.com/smaa/
|
||||
use bevy_app::{App, Plugin};
|
||||
#[cfg(feature = "smaa_luts")]
|
||||
use bevy_asset::load_internal_binary_asset;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
#[cfg(not(feature = "smaa_luts"))]
|
||||
use bevy_core_pipeline::tonemapping::lut_placeholder;
|
||||
use bevy_core_pipeline::{
|
||||
@ -48,9 +46,9 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
world::World,
|
||||
};
|
||||
use bevy_image::{BevyDefault, Image};
|
||||
use bevy_image::{BevyDefault, Image, ToExtents};
|
||||
use bevy_math::{vec4, Vec4};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
@ -58,37 +56,27 @@ use bevy_render::{
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
render_asset::RenderAssets,
|
||||
render_graph::{
|
||||
NodeRunError, RenderGraphApp as _, RenderGraphContext, ViewNode, ViewNodeRunner,
|
||||
NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,
|
||||
},
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d, uniform_buffer},
|
||||
AddressMode, BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
|
||||
CachedRenderPipelineId, ColorTargetState, ColorWrites, CompareFunction, DepthStencilState,
|
||||
DynamicUniformBuffer, Extent3d, FilterMode, FragmentState, LoadOp, MultisampleState,
|
||||
Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment,
|
||||
RenderPassDepthStencilAttachment, RenderPassDescriptor, RenderPipeline,
|
||||
RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, Shader, ShaderDefVal,
|
||||
ShaderStages, ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines,
|
||||
StencilFaceState, StencilOperation, StencilState, StoreOp, TextureDescriptor,
|
||||
TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView,
|
||||
VertexState,
|
||||
DynamicUniformBuffer, FilterMode, FragmentState, LoadOp, Operations, PipelineCache,
|
||||
RenderPassColorAttachment, RenderPassDepthStencilAttachment, RenderPassDescriptor,
|
||||
RenderPipeline, RenderPipelineDescriptor, SamplerBindingType, SamplerDescriptor, Shader,
|
||||
ShaderDefVal, ShaderStages, ShaderType, SpecializedRenderPipeline,
|
||||
SpecializedRenderPipelines, StencilFaceState, StencilOperation, StencilState, StoreOp,
|
||||
TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
TextureView, VertexState,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::{CachedTexture, GpuImage, TextureCache},
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSystems,
|
||||
Render, RenderApp, RenderStartup, RenderSystems,
|
||||
};
|
||||
use bevy_utils::prelude::default;
|
||||
|
||||
/// The handle of the `smaa.wgsl` shader.
|
||||
const SMAA_SHADER_HANDLE: Handle<Shader> = weak_handle!("fdd9839f-1ab4-4e0d-88a0-240b67da2ddf");
|
||||
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
|
||||
const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle<Image> =
|
||||
weak_handle!("569c4d67-c7fa-4958-b1af-0836023603c0");
|
||||
/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally.
|
||||
const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle<Image> =
|
||||
weak_handle!("43b97515-252e-4c8a-b9af-f2fc528a1c27");
|
||||
|
||||
/// Adds support for subpixel morphological antialiasing, or SMAA.
|
||||
pub struct SmaaPlugin;
|
||||
|
||||
@ -128,6 +116,14 @@ pub enum SmaaPreset {
|
||||
Ultra,
|
||||
}
|
||||
|
||||
#[derive(Resource)]
|
||||
struct SmaaLuts {
|
||||
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
|
||||
area_lut: Handle<Image>,
|
||||
/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally.
|
||||
search_lut: Handle<Image>,
|
||||
}
|
||||
|
||||
/// A render world resource that holds all render pipeline data needed for SMAA.
|
||||
///
|
||||
/// There are three separate passes, so we need three separate pipelines.
|
||||
@ -147,6 +143,8 @@ struct SmaaEdgeDetectionPipeline {
|
||||
postprocess_bind_group_layout: BindGroupLayout,
|
||||
/// The bind group layout for data specific to this pass.
|
||||
edge_detection_bind_group_layout: BindGroupLayout,
|
||||
/// The shader asset handle.
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// The pipeline data for phase 2 of SMAA: blending weight calculation.
|
||||
@ -155,6 +153,8 @@ struct SmaaBlendingWeightCalculationPipeline {
|
||||
postprocess_bind_group_layout: BindGroupLayout,
|
||||
/// The bind group layout for data specific to this pass.
|
||||
blending_weight_calculation_bind_group_layout: BindGroupLayout,
|
||||
/// The shader asset handle.
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// The pipeline data for phase 3 of SMAA: neighborhood blending.
|
||||
@ -163,6 +163,8 @@ struct SmaaNeighborhoodBlendingPipeline {
|
||||
postprocess_bind_group_layout: BindGroupLayout,
|
||||
/// The bind group layout for data specific to this pass.
|
||||
neighborhood_blending_bind_group_layout: BindGroupLayout,
|
||||
/// The shader asset handle.
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
/// A unique identifier for a set of SMAA pipelines.
|
||||
@ -287,51 +289,28 @@ pub struct SmaaSpecializedRenderPipelines {
|
||||
impl Plugin for SmaaPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
// Load the shader.
|
||||
load_internal_asset!(app, SMAA_SHADER_HANDLE, "smaa.wgsl", Shader::from_wgsl);
|
||||
|
||||
// Load the two lookup textures. These are compressed textures in KTX2
|
||||
// format.
|
||||
#[cfg(feature = "smaa_luts")]
|
||||
load_internal_binary_asset!(
|
||||
app,
|
||||
SMAA_AREA_LUT_TEXTURE_HANDLE,
|
||||
"SMAAAreaLUT.ktx2",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
bytes,
|
||||
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
|
||||
bevy_image::CompressedImageFormats::NONE,
|
||||
false,
|
||||
bevy_image::ImageSampler::Default,
|
||||
bevy_asset::RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.expect("Failed to load SMAA area LUT")
|
||||
);
|
||||
embedded_asset!(app, "smaa.wgsl");
|
||||
|
||||
#[cfg(feature = "smaa_luts")]
|
||||
load_internal_binary_asset!(
|
||||
app,
|
||||
SMAA_SEARCH_LUT_TEXTURE_HANDLE,
|
||||
"SMAASearchLUT.ktx2",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
bytes,
|
||||
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
|
||||
bevy_image::CompressedImageFormats::NONE,
|
||||
false,
|
||||
bevy_image::ImageSampler::Default,
|
||||
bevy_asset::RenderAssetUsages::RENDER_WORLD,
|
||||
)
|
||||
.expect("Failed to load SMAA search LUT")
|
||||
);
|
||||
let smaa_luts = {
|
||||
// Load the two lookup textures. These are compressed textures in KTX2 format.
|
||||
embedded_asset!(app, "SMAAAreaLUT.ktx2");
|
||||
embedded_asset!(app, "SMAASearchLUT.ktx2");
|
||||
|
||||
SmaaLuts {
|
||||
area_lut: load_embedded_asset!(app, "SMAAAreaLUT.ktx2"),
|
||||
search_lut: load_embedded_asset!(app, "SMAASearchLUT.ktx2"),
|
||||
}
|
||||
};
|
||||
#[cfg(not(feature = "smaa_luts"))]
|
||||
app.world_mut()
|
||||
.resource_mut::<bevy_asset::Assets<Image>>()
|
||||
.insert(SMAA_AREA_LUT_TEXTURE_HANDLE.id(), lut_placeholder());
|
||||
|
||||
#[cfg(not(feature = "smaa_luts"))]
|
||||
app.world_mut()
|
||||
.resource_mut::<bevy_asset::Assets<Image>>()
|
||||
.insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder());
|
||||
let smaa_luts = {
|
||||
let mut images = app.world_mut().resource_mut::<bevy_asset::Assets<Image>>();
|
||||
let handle = images.add(lut_placeholder());
|
||||
SmaaLuts {
|
||||
area_lut: handle.clone(),
|
||||
search_lut: handle.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
app.add_plugins(ExtractComponentPlugin::<Smaa>::default())
|
||||
.register_type::<Smaa>();
|
||||
@ -341,8 +320,10 @@ impl Plugin for SmaaPlugin {
|
||||
};
|
||||
|
||||
render_app
|
||||
.insert_resource(smaa_luts)
|
||||
.init_resource::<SmaaSpecializedRenderPipelines>()
|
||||
.init_resource::<SmaaInfoUniformBuffer>()
|
||||
.add_systems(RenderStartup, init_smaa_pipelines)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
@ -371,18 +352,13 @@ impl Plugin for SmaaPlugin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
if let Some(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<SmaaPipelines>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWorld for SmaaPipelines {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
|
||||
pub fn init_smaa_pipelines(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Create the postprocess bind group layout (all passes, bind group 0).
|
||||
let postprocess_bind_group_layout = render_device.create_bind_group_layout(
|
||||
"SMAA postprocess bind group layout",
|
||||
@ -390,8 +366,7 @@ impl FromWorld for SmaaPipelines {
|
||||
ShaderStages::FRAGMENT,
|
||||
(
|
||||
texture_2d(TextureSampleType::Float { filterable: true }),
|
||||
uniform_buffer::<SmaaInfoUniform>(true)
|
||||
.visibility(ShaderStages::VERTEX_FRAGMENT),
|
||||
uniform_buffer::<SmaaInfoUniform>(true).visibility(ShaderStages::VERTEX_FRAGMENT),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -431,21 +406,25 @@ impl FromWorld for SmaaPipelines {
|
||||
),
|
||||
);
|
||||
|
||||
SmaaPipelines {
|
||||
let shader = load_embedded_asset!(asset_server.as_ref(), "smaa.wgsl");
|
||||
|
||||
commands.insert_resource(SmaaPipelines {
|
||||
edge_detection: SmaaEdgeDetectionPipeline {
|
||||
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
|
||||
edge_detection_bind_group_layout,
|
||||
shader: shader.clone(),
|
||||
},
|
||||
blending_weight_calculation: SmaaBlendingWeightCalculationPipeline {
|
||||
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
|
||||
blending_weight_calculation_bind_group_layout,
|
||||
shader: shader.clone(),
|
||||
},
|
||||
neighborhood_blending: SmaaNeighborhoodBlendingPipeline {
|
||||
postprocess_bind_group_layout,
|
||||
neighborhood_blending_bind_group_layout,
|
||||
shader,
|
||||
},
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Phase 1: edge detection.
|
||||
@ -472,23 +451,21 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
|
||||
self.edge_detection_bind_group_layout.clone(),
|
||||
],
|
||||
vertex: VertexState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
entry_point: "edge_detection_vertex_main".into(),
|
||||
entry_point: Some("edge_detection_vertex_main".into()),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "luma_edge_detection_fragment_main".into(),
|
||||
entry_point: Some("luma_edge_detection_fragment_main".into()),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: TextureFormat::Rg8Unorm,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
push_constant_ranges: vec![],
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Stencil8,
|
||||
depth_write_enabled: false,
|
||||
@ -501,8 +478,7 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
|
||||
},
|
||||
bias: default(),
|
||||
}),
|
||||
multisample: MultisampleState::default(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -532,23 +508,21 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
|
||||
self.blending_weight_calculation_bind_group_layout.clone(),
|
||||
],
|
||||
vertex: VertexState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
entry_point: "blending_weight_calculation_vertex_main".into(),
|
||||
entry_point: Some("blending_weight_calculation_vertex_main".into()),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "blending_weight_calculation_fragment_main".into(),
|
||||
entry_point: Some("blending_weight_calculation_fragment_main".into()),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: TextureFormat::Rgba8Unorm,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
push_constant_ranges: vec![],
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: Some(DepthStencilState {
|
||||
format: TextureFormat::Stencil8,
|
||||
depth_write_enabled: false,
|
||||
@ -561,8 +535,7 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
|
||||
},
|
||||
bias: default(),
|
||||
}),
|
||||
multisample: MultisampleState::default(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -580,26 +553,22 @@ impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline {
|
||||
self.neighborhood_blending_bind_group_layout.clone(),
|
||||
],
|
||||
vertex: VertexState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs: shader_defs.clone(),
|
||||
entry_point: "neighborhood_blending_vertex_main".into(),
|
||||
entry_point: Some("neighborhood_blending_vertex_main".into()),
|
||||
buffers: vec![],
|
||||
},
|
||||
fragment: Some(FragmentState {
|
||||
shader: SMAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "neighborhood_blending_fragment_main".into(),
|
||||
entry_point: Some("neighborhood_blending_fragment_main".into()),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
format: key.texture_format,
|
||||
blend: None,
|
||||
write_mask: ColorWrites::ALL,
|
||||
})],
|
||||
}),
|
||||
push_constant_ranges: vec![],
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -695,18 +664,12 @@ fn prepare_smaa_textures(
|
||||
continue;
|
||||
};
|
||||
|
||||
let texture_size = Extent3d {
|
||||
width: texture_size.x,
|
||||
height: texture_size.y,
|
||||
depth_or_array_layers: 1,
|
||||
};
|
||||
|
||||
// Create the two-channel RG texture for phase 1 (edge detection).
|
||||
let edge_detection_color_texture = texture_cache.get(
|
||||
&render_device,
|
||||
TextureDescriptor {
|
||||
label: Some("SMAA edge detection color texture"),
|
||||
size: texture_size,
|
||||
size: texture_size.to_extents(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
@ -721,7 +684,7 @@ fn prepare_smaa_textures(
|
||||
&render_device,
|
||||
TextureDescriptor {
|
||||
label: Some("SMAA edge detection stencil texture"),
|
||||
size: texture_size,
|
||||
size: texture_size.to_extents(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
@ -737,7 +700,7 @@ fn prepare_smaa_textures(
|
||||
&render_device,
|
||||
TextureDescriptor {
|
||||
label: Some("SMAA blend texture"),
|
||||
size: texture_size,
|
||||
size: texture_size.to_extents(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
@ -761,13 +724,14 @@ fn prepare_smaa_bind_groups(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
smaa_pipelines: Res<SmaaPipelines>,
|
||||
smaa_luts: Res<SmaaLuts>,
|
||||
images: Res<RenderAssets<GpuImage>>,
|
||||
view_targets: Query<(Entity, &SmaaTextures), (With<ExtractedView>, With<Smaa>)>,
|
||||
) {
|
||||
// Fetch the two lookup textures. These are bundled in this library.
|
||||
let (Some(search_texture), Some(area_texture)) = (
|
||||
images.get(&SMAA_SEARCH_LUT_TEXTURE_HANDLE),
|
||||
images.get(&SMAA_AREA_LUT_TEXTURE_HANDLE),
|
||||
images.get(&smaa_luts.search_lut),
|
||||
images.get(&smaa_luts.area_lut),
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
@ -838,7 +802,7 @@ impl ViewNode for SmaaNode {
|
||||
view_smaa_uniform_offset,
|
||||
smaa_textures,
|
||||
view_smaa_bind_groups,
|
||||
): QueryItem<'w, Self::ViewQuery>,
|
||||
): QueryItem<'w, '_, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
|
||||
@ -146,10 +146,10 @@
|
||||
* * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below).
|
||||
*
|
||||
* And four presets:
|
||||
* SMAA_PRESET_LOW (%60 of the quality)
|
||||
* SMAA_PRESET_MEDIUM (%80 of the quality)
|
||||
* SMAA_PRESET_HIGH (%95 of the quality)
|
||||
* SMAA_PRESET_ULTRA (%99 of the quality)
|
||||
* SMAA_PRESET_LOW (60% of the quality)
|
||||
* SMAA_PRESET_MEDIUM (80% of the quality)
|
||||
* SMAA_PRESET_HIGH (95% of the quality)
|
||||
* SMAA_PRESET_ULTRA (99% of the quality)
|
||||
*
|
||||
* For example:
|
||||
* #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0)
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
|
||||
use bevy_core_pipeline::{
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
fullscreen_vertex_shader::fullscreen_shader_vertex_state,
|
||||
prelude::Camera3d,
|
||||
prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
|
||||
FullscreenShader,
|
||||
};
|
||||
use bevy_diagnostic::FrameCount;
|
||||
use bevy_ecs::{
|
||||
@ -13,35 +13,34 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
world::World,
|
||||
};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_image::{BevyDefault as _, ToExtents};
|
||||
use bevy_math::vec2;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
camera::{ExtractedCamera, MipBias, TemporalJitter},
|
||||
prelude::{Camera, Projection},
|
||||
render_graph::{NodeRunError, RenderGraphApp, RenderGraphContext, ViewNode, ViewNodeRunner},
|
||||
render_graph::{NodeRunError, RenderGraphContext, RenderGraphExt, ViewNode, ViewNodeRunner},
|
||||
render_resource::{
|
||||
binding_types::{sampler, texture_2d, texture_depth_2d},
|
||||
BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries, CachedRenderPipelineId,
|
||||
ColorTargetState, ColorWrites, Extent3d, FilterMode, FragmentState, MultisampleState,
|
||||
Operations, PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
|
||||
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
|
||||
ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDescriptor,
|
||||
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
|
||||
ColorTargetState, ColorWrites, FilterMode, FragmentState, Operations, PipelineCache,
|
||||
RenderPassColorAttachment, RenderPassDescriptor, RenderPipelineDescriptor, Sampler,
|
||||
SamplerBindingType, SamplerDescriptor, Shader, ShaderStages, SpecializedRenderPipeline,
|
||||
SpecializedRenderPipelines, TextureDescriptor, TextureDimension, TextureFormat,
|
||||
TextureSampleType, TextureUsages,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
sync_component::SyncComponentPlugin,
|
||||
sync_world::RenderEntity,
|
||||
texture::{CachedTexture, TextureCache},
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems,
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderStartup, RenderSystems,
|
||||
};
|
||||
use bevy_utils::default;
|
||||
use tracing::warn;
|
||||
|
||||
const TAA_SHADER_HANDLE: Handle<Shader> = weak_handle!("fea20d50-86b6-4069-aa32-374346aec00c");
|
||||
|
||||
/// Plugin for temporal anti-aliasing.
|
||||
///
|
||||
/// See [`TemporalAntiAliasing`] for more details.
|
||||
@ -49,7 +48,7 @@ pub struct TemporalAntiAliasPlugin;
|
||||
|
||||
impl Plugin for TemporalAntiAliasPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl);
|
||||
embedded_asset!(app, "taa.wgsl");
|
||||
|
||||
app.register_type::<TemporalAntiAliasing>();
|
||||
|
||||
@ -60,11 +59,12 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
};
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<TaaPipeline>>()
|
||||
.add_systems(RenderStartup, init_taa_pipeline)
|
||||
.add_systems(ExtractSchedule, extract_taa_settings)
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_taa_jitter_and_mip_bias.in_set(RenderSystems::ManageViews),
|
||||
prepare_taa_jitter.in_set(RenderSystems::ManageViews),
|
||||
prepare_taa_pipelines.in_set(RenderSystems::Prepare),
|
||||
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
|
||||
),
|
||||
@ -81,14 +81,6 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
fn finish(&self, app: &mut App) {
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
return;
|
||||
};
|
||||
|
||||
render_app.init_resource::<TaaPipeline>();
|
||||
}
|
||||
}
|
||||
|
||||
/// Component to apply temporal anti-aliasing to a 3D perspective camera.
|
||||
@ -115,7 +107,6 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
///
|
||||
/// # Usage Notes
|
||||
///
|
||||
/// The [`TemporalAntiAliasPlugin`] must be added to your app.
|
||||
/// Any camera with this component must also disable [`Msaa`] by setting it to [`Msaa::Off`].
|
||||
///
|
||||
/// [Currently](https://github.com/bevyengine/bevy/issues/8423), TAA cannot be used with [`bevy_render::camera::OrthographicProjection`].
|
||||
@ -128,11 +119,9 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
///
|
||||
/// 1. Write particle motion vectors to the motion vectors prepass texture
|
||||
/// 2. Render particles after TAA
|
||||
///
|
||||
/// If no [`MipBias`] component is attached to the camera, TAA will add a `MipBias(-1.0)` component.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[require(TemporalJitter, DepthPrepass, MotionVectorPrepass)]
|
||||
#[require(TemporalJitter, MipBias, DepthPrepass, MotionVectorPrepass)]
|
||||
#[doc(alias = "Taa")]
|
||||
pub struct TemporalAntiAliasing {
|
||||
/// Set to true to delete the saved temporal history (past frames).
|
||||
@ -243,12 +232,16 @@ struct TaaPipeline {
|
||||
taa_bind_group_layout: BindGroupLayout,
|
||||
nearest_sampler: Sampler,
|
||||
linear_sampler: Sampler,
|
||||
fullscreen_shader: FullscreenShader,
|
||||
fragment_shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for TaaPipeline {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let render_device = world.resource::<RenderDevice>();
|
||||
|
||||
fn init_taa_pipeline(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
fullscreen_shader: Res<FullscreenShader>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
|
||||
label: Some("taa_nearest_sampler"),
|
||||
mag_filter: FilterMode::Nearest,
|
||||
@ -283,12 +276,13 @@ impl FromWorld for TaaPipeline {
|
||||
),
|
||||
);
|
||||
|
||||
TaaPipeline {
|
||||
commands.insert_resource(TaaPipeline {
|
||||
taa_bind_group_layout,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
}
|
||||
}
|
||||
fullscreen_shader: fullscreen_shader.clone(),
|
||||
fragment_shader: load_embedded_asset!(asset_server.as_ref(), "taa.wgsl"),
|
||||
});
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, Clone)]
|
||||
@ -317,11 +311,10 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
||||
RenderPipelineDescriptor {
|
||||
label: Some("taa_pipeline".into()),
|
||||
layout: vec![self.taa_bind_group_layout.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
vertex: self.fullscreen_shader.to_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: TAA_SHADER_HANDLE,
|
||||
shader: self.fragment_shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "taa".into(),
|
||||
targets: vec![
|
||||
Some(ColorTargetState {
|
||||
format,
|
||||
@ -334,27 +327,19 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
||||
write_mask: ColorWrites::ALL,
|
||||
}),
|
||||
],
|
||||
..default()
|
||||
}),
|
||||
primitive: PrimitiveState::default(),
|
||||
depth_stencil: None,
|
||||
multisample: MultisampleState::default(),
|
||||
push_constant_ranges: Vec::new(),
|
||||
zero_initialize_workgroup_memory: false,
|
||||
..default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld>) {
|
||||
let mut cameras_3d = main_world.query_filtered::<(
|
||||
let mut cameras_3d = main_world.query::<(
|
||||
RenderEntity,
|
||||
&Camera,
|
||||
&Projection,
|
||||
&mut TemporalAntiAliasing,
|
||||
), (
|
||||
With<Camera3d>,
|
||||
With<TemporalJitter>,
|
||||
With<DepthPrepass>,
|
||||
With<MotionVectorPrepass>,
|
||||
Option<&mut TemporalAntiAliasing>,
|
||||
)>();
|
||||
|
||||
for (entity, camera, camera_projection, mut taa_settings) in
|
||||
@ -364,14 +349,12 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
|
||||
let mut entity_commands = commands
|
||||
.get_entity(entity)
|
||||
.expect("Camera entity wasn't synced.");
|
||||
if camera.is_active && has_perspective_projection {
|
||||
entity_commands.insert(taa_settings.clone());
|
||||
taa_settings.reset = false;
|
||||
if taa_settings.is_some() && camera.is_active && has_perspective_projection {
|
||||
entity_commands.insert(taa_settings.as_deref().unwrap().clone());
|
||||
taa_settings.as_mut().unwrap().reset = false;
|
||||
} else {
|
||||
// TODO: needs better strategy for cleaning up
|
||||
entity_commands.remove::<(
|
||||
TemporalAntiAliasing,
|
||||
// components added in prepare systems (because `TemporalAntiAliasNode` does not query extracted components)
|
||||
TemporalAntiAliasHistoryTextures,
|
||||
TemporalAntiAliasPipelineId,
|
||||
)>();
|
||||
@ -379,13 +362,22 @@ fn extract_taa_settings(mut commands: Commands, mut main_world: ResMut<MainWorld
|
||||
}
|
||||
}
|
||||
|
||||
fn prepare_taa_jitter_and_mip_bias(
|
||||
fn prepare_taa_jitter(
|
||||
frame_count: Res<FrameCount>,
|
||||
mut query: Query<(Entity, &mut TemporalJitter, Option<&MipBias>), With<TemporalAntiAliasing>>,
|
||||
mut commands: Commands,
|
||||
mut query: Query<
|
||||
&mut TemporalJitter,
|
||||
(
|
||||
With<TemporalAntiAliasing>,
|
||||
With<Camera3d>,
|
||||
With<TemporalJitter>,
|
||||
With<DepthPrepass>,
|
||||
With<MotionVectorPrepass>,
|
||||
),
|
||||
>,
|
||||
) {
|
||||
// Halton sequence (2, 3) - 0.5, skipping i = 0
|
||||
// Halton sequence (2, 3) - 0.5
|
||||
let halton_sequence = [
|
||||
vec2(0.0, 0.0),
|
||||
vec2(0.0, -0.16666666),
|
||||
vec2(-0.25, 0.16666669),
|
||||
vec2(0.25, -0.3888889),
|
||||
@ -393,17 +385,12 @@ fn prepare_taa_jitter_and_mip_bias(
|
||||
vec2(0.125, 0.2777778),
|
||||
vec2(-0.125, -0.2777778),
|
||||
vec2(0.375, 0.055555582),
|
||||
vec2(-0.4375, 0.3888889),
|
||||
];
|
||||
|
||||
let offset = halton_sequence[frame_count.0 as usize % halton_sequence.len()];
|
||||
|
||||
for (entity, mut jitter, mip_bias) in &mut query {
|
||||
for mut jitter in &mut query {
|
||||
jitter.offset = offset;
|
||||
|
||||
if mip_bias.is_none() {
|
||||
commands.entity(entity).insert(MipBias(-1.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,11 +411,7 @@ fn prepare_taa_history_textures(
|
||||
if let Some(physical_target_size) = camera.physical_target_size {
|
||||
let mut texture_descriptor = TextureDescriptor {
|
||||
label: None,
|
||||
size: Extent3d {
|
||||
depth_or_array_layers: 1,
|
||||
width: physical_target_size.x,
|
||||
height: physical_target_size.y,
|
||||
},
|
||||
size: physical_target_size.to_extents(),
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
dimension: TextureDimension::D2,
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_app"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides core App functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
@ -47,7 +47,6 @@ std = [
|
||||
"bevy_ecs/std",
|
||||
"dep:ctrlc",
|
||||
"downcast-rs/std",
|
||||
"bevy_utils/std",
|
||||
"bevy_tasks/std",
|
||||
"bevy_platform/std",
|
||||
]
|
||||
@ -72,16 +71,20 @@ web = [
|
||||
"dep:console_error_panic_hook",
|
||||
]
|
||||
|
||||
hotpatching = [
|
||||
"bevy_ecs/hotpatching",
|
||||
"dep:dioxus-devtools",
|
||||
"dep:crossbeam-channel",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false }
|
||||
|
||||
# other
|
||||
downcast-rs = { version = "2", default-features = false }
|
||||
@ -90,8 +93,10 @@ variadics_please = "1.1"
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
log = { version = "0.4", default-features = false }
|
||||
cfg-if = "1.0.0"
|
||||
dioxus-devtools = { version = "0.7.0-alpha.1", optional = true }
|
||||
crossbeam-channel = { version = "0.5.0", optional = true }
|
||||
|
||||
[target.'cfg(any(unix, windows))'.dependencies]
|
||||
[target.'cfg(any(all(unix, not(target_os = "horizon")), windows))'.dependencies]
|
||||
ctrlc = { version = "3.4.4", optional = true }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
|
||||
@ -10,6 +10,7 @@ use alloc::{
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
component::RequiredComponentsError,
|
||||
error::{DefaultErrorHandler, ErrorHandler},
|
||||
event::{event_update_system, EventCursor},
|
||||
intern::Interned,
|
||||
prelude::*,
|
||||
@ -85,6 +86,7 @@ pub struct App {
|
||||
/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
|
||||
/// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html
|
||||
pub(crate) runner: RunnerFn,
|
||||
default_error_handler: Option<ErrorHandler>,
|
||||
}
|
||||
|
||||
impl Debug for App {
|
||||
@ -104,10 +106,13 @@ impl Default for App {
|
||||
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
{
|
||||
use bevy_ecs::observer::ObservedBy;
|
||||
|
||||
app.init_resource::<AppTypeRegistry>();
|
||||
app.register_type::<Name>();
|
||||
app.register_type::<ChildOf>();
|
||||
app.register_type::<Children>();
|
||||
app.register_type::<ObservedBy>();
|
||||
}
|
||||
|
||||
#[cfg(feature = "reflect_functions")]
|
||||
@ -143,6 +148,7 @@ impl App {
|
||||
sub_apps: HashMap::default(),
|
||||
},
|
||||
runner: Box::new(run_once),
|
||||
default_error_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -338,7 +344,7 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Initializes `T` event handling by inserting an event queue resource ([`Events::<T>`])
|
||||
/// Initializes [`BufferedEvent`] handling for `T` by inserting an event queue resource ([`Events::<T>`])
|
||||
/// and scheduling an [`event_update_system`] in [`First`].
|
||||
///
|
||||
/// See [`Events`] for information on how to define events.
|
||||
@ -349,7 +355,7 @@ impl App {
|
||||
/// # use bevy_app::prelude::*;
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, BufferedEvent)]
|
||||
/// # struct MyEvent;
|
||||
/// # let mut app = App::new();
|
||||
/// #
|
||||
@ -357,7 +363,7 @@ impl App {
|
||||
/// ```
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Event,
|
||||
T: BufferedEvent,
|
||||
{
|
||||
self.main_mut().add_event::<T>();
|
||||
self
|
||||
@ -1115,7 +1121,12 @@ impl App {
|
||||
}
|
||||
|
||||
/// Inserts a [`SubApp`] with the given label.
|
||||
pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) {
|
||||
pub fn insert_sub_app(&mut self, label: impl AppLabel, mut sub_app: SubApp) {
|
||||
if let Some(handler) = self.default_error_handler {
|
||||
sub_app
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
|
||||
}
|
||||
self.sub_apps.sub_apps.insert(label.intern(), sub_app);
|
||||
}
|
||||
|
||||
@ -1298,6 +1309,8 @@ impl App {
|
||||
|
||||
/// Spawns an [`Observer`] entity, which will watch for and respond to the given event.
|
||||
///
|
||||
/// `observer` can be any system whose first parameter is [`On`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
@ -1312,14 +1325,14 @@ impl App {
|
||||
/// # friends_allowed: bool,
|
||||
/// # };
|
||||
/// #
|
||||
/// # #[derive(Event)]
|
||||
/// # #[derive(Event, EntityEvent)]
|
||||
/// # struct Invite;
|
||||
/// #
|
||||
/// # #[derive(Component)]
|
||||
/// # struct Friend;
|
||||
/// #
|
||||
/// // An observer system can be any system where the first parameter is a trigger
|
||||
/// app.add_observer(|trigger: Trigger<Party>, friends: Query<Entity, With<Friend>>, mut commands: Commands| {
|
||||
///
|
||||
/// app.add_observer(|trigger: On<Party>, friends: Query<Entity, With<Friend>>, mut commands: Commands| {
|
||||
/// if trigger.event().friends_allowed {
|
||||
/// for friend in friends.iter() {
|
||||
/// commands.trigger_targets(Invite, friend);
|
||||
@ -1334,6 +1347,49 @@ impl App {
|
||||
self.world_mut().add_observer(observer);
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets the error handler to set for new supapps.
|
||||
///
|
||||
/// Note that the error handler of existing subapps may differ.
|
||||
pub fn get_error_handler(&self) -> Option<ErrorHandler> {
|
||||
self.default_error_handler
|
||||
}
|
||||
|
||||
/// Set the [default error handler] for the all subapps (including the main one and future ones)
|
||||
/// that do not have one.
|
||||
///
|
||||
/// May only be called once and should be set by the application, not by libraries.
|
||||
///
|
||||
/// The handler will be called when an error is produced and not otherwise handled.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if called multiple times.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use bevy_app::*;
|
||||
/// # use bevy_ecs::error::warn;
|
||||
/// # fn MyPlugins(_: &mut App) {}
|
||||
/// App::new()
|
||||
/// .set_error_handler(warn)
|
||||
/// .add_plugins(MyPlugins)
|
||||
/// .run();
|
||||
/// ```
|
||||
///
|
||||
/// [default error handler]: bevy_ecs::error::DefaultErrorHandler
|
||||
pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self {
|
||||
assert!(
|
||||
self.default_error_handler.is_none(),
|
||||
"`set_error_handler` called multiple times on same `App`"
|
||||
);
|
||||
self.default_error_handler = Some(handler);
|
||||
for sub_app in self.sub_apps.iter_mut() {
|
||||
sub_app
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
|
||||
@ -1351,7 +1407,7 @@ fn run_once(mut app: App) -> AppExit {
|
||||
app.should_exit().unwrap_or(AppExit::Success)
|
||||
}
|
||||
|
||||
/// An event that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
|
||||
/// A [`BufferedEvent`] that indicates the [`App`] should exit. If one or more of these are present at the end of an update,
|
||||
/// the [runner](App::set_runner) will end and ([maybe](App::run)) return control to the caller.
|
||||
///
|
||||
/// This event can be used to detect when an exit is requested. Make sure that systems listening
|
||||
@ -1361,7 +1417,7 @@ fn run_once(mut app: App) -> AppExit {
|
||||
/// This type is roughly meant to map to a standard definition of a process exit code (0 means success, not 0 means error). Due to portability concerns
|
||||
/// (see [`ExitCode`](https://doc.rust-lang.org/std/process/struct.ExitCode.html) and [`process::exit`](https://doc.rust-lang.org/std/process/fn.exit.html#))
|
||||
/// we only allow error codes between 1 and [255](u8::MAX).
|
||||
#[derive(Event, Debug, Clone, Default, PartialEq, Eq)]
|
||||
#[derive(Event, BufferedEvent, Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub enum AppExit {
|
||||
/// [`App`] exited without any problems.
|
||||
#[default]
|
||||
@ -1429,9 +1485,9 @@ mod tests {
|
||||
change_detection::{DetectChanges, ResMut},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
event::{Event, EventWriter, Events},
|
||||
event::{BufferedEvent, Event, EventWriter, Events},
|
||||
lifecycle::RemovedComponents,
|
||||
query::With,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
||||
system::{Commands, Query},
|
||||
@ -1526,7 +1582,7 @@ mod tests {
|
||||
app.add_systems(EnterMainMenu, (foo, bar));
|
||||
|
||||
app.world_mut().run_schedule(EnterMainMenu);
|
||||
assert_eq!(app.world().entities().len(), 2);
|
||||
assert_eq!(app.world().entity_count(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -1795,7 +1851,7 @@ mod tests {
|
||||
}
|
||||
#[test]
|
||||
fn events_should_be_updated_once_per_update() {
|
||||
#[derive(Event, Clone)]
|
||||
#[derive(Event, BufferedEvent, Clone)]
|
||||
struct TestEvent;
|
||||
|
||||
let mut app = App::new();
|
||||
@ -1808,7 +1864,7 @@ mod tests {
|
||||
app.update();
|
||||
|
||||
// Sending one event
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
|
||||
let test_events = app.world().resource::<Events<TestEvent>>();
|
||||
assert_eq!(test_events.len(), 1);
|
||||
@ -1816,8 +1872,8 @@ mod tests {
|
||||
app.update();
|
||||
|
||||
// Sending two events on the next frame
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().send_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
app.world_mut().write_event(TestEvent);
|
||||
|
||||
let test_events = app.world().resource::<Events<TestEvent>>();
|
||||
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
|
||||
|
||||
42
crates/bevy_app/src/hotpatch.rs
Normal file
42
crates/bevy_app/src/hotpatch.rs
Normal file
@ -0,0 +1,42 @@
|
||||
//! Utilities for hotpatching code.
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use bevy_ecs::{event::EventWriter, HotPatched};
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
use dioxus_devtools::connect_subsecond;
|
||||
use dioxus_devtools::subsecond;
|
||||
|
||||
pub use dioxus_devtools::subsecond::{call, HotFunction};
|
||||
|
||||
use crate::{Last, Plugin};
|
||||
|
||||
/// Plugin connecting to Dioxus CLI to enable hot patching.
|
||||
#[derive(Default)]
|
||||
pub struct HotPatchPlugin;
|
||||
|
||||
impl Plugin for HotPatchPlugin {
|
||||
fn build(&self, app: &mut crate::App) {
|
||||
let (sender, receiver) = crossbeam_channel::bounded::<HotPatched>(1);
|
||||
|
||||
// Connects to the dioxus CLI that will handle rebuilds
|
||||
// This will open a connection to the dioxus CLI to receive updated jump tables
|
||||
// Sends a `HotPatched` message through the channel when the jump table is updated
|
||||
#[cfg(not(target_family = "wasm"))]
|
||||
connect_subsecond();
|
||||
subsecond::register_handler(Arc::new(move || {
|
||||
sender.send(HotPatched).unwrap();
|
||||
}));
|
||||
|
||||
// Adds a system that will read the channel for new `HotPatched`, and forward them as event to the ECS
|
||||
app.add_event::<HotPatched>().add_systems(
|
||||
Last,
|
||||
move |mut events: EventWriter<HotPatched>| {
|
||||
if receiver.try_recv().is_ok() {
|
||||
events.write_default();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -8,8 +8,8 @@
|
||||
#![cfg_attr(any(docsrs, docsrs_dep), feature(doc_auto_cfg, rustdoc_internals))]
|
||||
#![forbid(unsafe_code)]
|
||||
#![doc(
|
||||
html_logo_url = "https://bevyengine.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevyengine.org/assets/icon.png"
|
||||
html_logo_url = "https://bevy.org/assets/icon.png",
|
||||
html_favicon_url = "https://bevy.org/assets/icon.png"
|
||||
)]
|
||||
#![no_std]
|
||||
|
||||
@ -28,21 +28,26 @@ mod main_schedule;
|
||||
mod panic_handler;
|
||||
mod plugin;
|
||||
mod plugin_group;
|
||||
mod propagate;
|
||||
mod schedule_runner;
|
||||
mod sub_app;
|
||||
mod task_pool_plugin;
|
||||
#[cfg(all(any(unix, windows), feature = "std"))]
|
||||
#[cfg(all(any(all(unix, not(target_os = "horizon")), windows), feature = "std"))]
|
||||
mod terminal_ctrl_c_handler;
|
||||
|
||||
#[cfg(feature = "hotpatching")]
|
||||
pub mod hotpatch;
|
||||
|
||||
pub use app::*;
|
||||
pub use main_schedule::*;
|
||||
pub use panic_handler::*;
|
||||
pub use plugin::*;
|
||||
pub use plugin_group::*;
|
||||
pub use propagate::*;
|
||||
pub use schedule_runner::*;
|
||||
pub use sub_app::*;
|
||||
pub use task_pool_plugin::*;
|
||||
#[cfg(all(any(unix, windows), feature = "std"))]
|
||||
#[cfg(all(any(all(unix, not(target_os = "horizon")), windows), feature = "std"))]
|
||||
pub use terminal_ctrl_c_handler::*;
|
||||
|
||||
/// The app prelude.
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
//! This module provides panic handlers for [Bevy](https://bevyengine.org)
|
||||
//! This module provides panic handlers for [Bevy](https://bevy.org)
|
||||
//! apps, and automatically configures platform specifics (i.e. Wasm or Android).
|
||||
//!
|
||||
//! By default, the [`PanicHandlerPlugin`] from this crate is included in Bevy's `DefaultPlugins`.
|
||||
|
||||
552
crates/bevy_app/src/propagate.rs
Normal file
552
crates/bevy_app/src/propagate.rs
Normal file
@ -0,0 +1,552 @@
|
||||
use alloc::vec::Vec;
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use crate::{App, Plugin, Update};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
hierarchy::ChildOf,
|
||||
lifecycle::RemovedComponents,
|
||||
query::{Changed, Or, QueryFilter, With, Without},
|
||||
relationship::{Relationship, RelationshipTarget},
|
||||
schedule::{IntoScheduleConfigs, SystemSet},
|
||||
system::{Commands, Local, Query},
|
||||
};
|
||||
|
||||
/// Plugin to automatically propagate a component value to all direct and transient relationship
|
||||
/// targets (e.g. [`bevy_ecs::hierarchy::Children`]) of entities with a [`Propagate`] component.
|
||||
///
|
||||
/// The plugin Will maintain the target component over hierarchy changes, adding or removing
|
||||
/// `C` when a relationship `R` (e.g. [`ChildOf`]) is added to or removed from a
|
||||
/// relationship tree with a [`Propagate<C>`] source, or if the [`Propagate<C>`] component
|
||||
/// is added, changed or removed.
|
||||
///
|
||||
/// Optionally you can include a query filter `F` to restrict the entities that are updated.
|
||||
/// Note that the filter is not rechecked dynamically: changes to the filter state will not be
|
||||
/// picked up until the [`Propagate`] component is touched, or the hierarchy is changed.
|
||||
/// All members of the tree between source and target must match the filter for propagation
|
||||
/// to reach a given target.
|
||||
/// Individual entities can be skipped or terminate the propagation with the [`PropagateOver`]
|
||||
/// and [`PropagateStop`] components.
|
||||
pub struct HierarchyPropagatePlugin<
|
||||
C: Component + Clone + PartialEq,
|
||||
F: QueryFilter = (),
|
||||
R: Relationship = ChildOf,
|
||||
>(PhantomData<fn() -> (C, F, R)>);
|
||||
|
||||
/// Causes the inner component to be added to this entity and all direct and transient relationship
|
||||
/// targets. A target with a [`Propagate<C>`] component of its own will override propagation from
|
||||
/// that point in the tree.
|
||||
#[derive(Component, Clone, PartialEq)]
|
||||
pub struct Propagate<C: Component + Clone + PartialEq>(pub C);
|
||||
|
||||
/// Stops the output component being added to this entity.
|
||||
/// Relationship targets will still inherit the component from this entity or its parents.
|
||||
#[derive(Component)]
|
||||
pub struct PropagateOver<C>(PhantomData<fn() -> C>);
|
||||
|
||||
/// Stops the propagation at this entity. Children will not inherit the component.
|
||||
#[derive(Component)]
|
||||
pub struct PropagateStop<C>(PhantomData<fn() -> C>);
|
||||
|
||||
/// The set in which propagation systems are added. You can schedule your logic relative to this set.
|
||||
#[derive(SystemSet, Clone, PartialEq, PartialOrd, Ord)]
|
||||
pub struct PropagateSet<C: Component + Clone + PartialEq> {
|
||||
_p: PhantomData<fn() -> C>,
|
||||
}
|
||||
|
||||
/// Internal struct for managing propagation
|
||||
#[derive(Component, Clone, PartialEq)]
|
||||
pub struct Inherited<C: Component + Clone + PartialEq>(pub C);
|
||||
|
||||
impl<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship> Default
|
||||
for HierarchyPropagatePlugin<C, F, R>
|
||||
{
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Default for PropagateOver<C> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C> Default for PropagateStop<C> {
|
||||
fn default() -> Self {
|
||||
Self(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> core::fmt::Debug for PropagateSet<C> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
f.debug_struct("PropagateSet")
|
||||
.field("_p", &self._p)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> Eq for PropagateSet<C> {}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> core::hash::Hash for PropagateSet<C> {
|
||||
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
|
||||
self._p.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + PartialEq> Default for PropagateSet<C> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_p: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Component + Clone + PartialEq, F: QueryFilter + 'static, R: Relationship> Plugin
|
||||
for HierarchyPropagatePlugin<C, F, R>
|
||||
{
|
||||
fn build(&self, app: &mut App) {
|
||||
app.add_systems(
|
||||
Update,
|
||||
(
|
||||
update_source::<C, F>,
|
||||
update_stopped::<C, F>,
|
||||
update_reparented::<C, F, R>,
|
||||
propagate_inherited::<C, F, R>,
|
||||
propagate_output::<C, F>,
|
||||
)
|
||||
.chain()
|
||||
.in_set(PropagateSet::<C>::default()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// add/remove `Inherited::<C>` and `C` for entities with a direct `Propagate::<C>`
|
||||
pub fn update_source<C: Component + Clone + PartialEq, F: QueryFilter>(
|
||||
mut commands: Commands,
|
||||
changed: Query<
|
||||
(Entity, &Propagate<C>),
|
||||
(
|
||||
Or<(Changed<Propagate<C>>, Without<Inherited<C>>)>,
|
||||
Without<PropagateStop<C>>,
|
||||
),
|
||||
>,
|
||||
mut removed: RemovedComponents<Propagate<C>>,
|
||||
) {
|
||||
for (entity, source) in &changed {
|
||||
commands
|
||||
.entity(entity)
|
||||
.try_insert(Inherited(source.0.clone()));
|
||||
}
|
||||
|
||||
for removed in removed.read() {
|
||||
if let Ok(mut commands) = commands.get_entity(removed) {
|
||||
commands.remove::<(Inherited<C>, C)>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// remove `Inherited::<C>` and `C` for entities with a `PropagateStop::<C>`
|
||||
pub fn update_stopped<C: Component + Clone + PartialEq, F: QueryFilter>(
|
||||
mut commands: Commands,
|
||||
q: Query<Entity, (With<Inherited<C>>, With<PropagateStop<C>>, F)>,
|
||||
) {
|
||||
for entity in q.iter() {
|
||||
let mut cmds = commands.entity(entity);
|
||||
cmds.remove::<(Inherited<C>, C)>();
|
||||
}
|
||||
}
|
||||
|
||||
/// add/remove `Inherited::<C>` and `C` for entities which have changed relationship
|
||||
pub fn update_reparented<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
|
||||
mut commands: Commands,
|
||||
moved: Query<
|
||||
(Entity, &R, Option<&Inherited<C>>),
|
||||
(
|
||||
Changed<R>,
|
||||
Without<Propagate<C>>,
|
||||
Without<PropagateStop<C>>,
|
||||
F,
|
||||
),
|
||||
>,
|
||||
relations: Query<&Inherited<C>>,
|
||||
orphaned: Query<Entity, (With<Inherited<C>>, Without<Propagate<C>>, Without<R>, F)>,
|
||||
) {
|
||||
for (entity, relation, maybe_inherited) in &moved {
|
||||
if let Ok(inherited) = relations.get(relation.get()) {
|
||||
commands.entity(entity).try_insert(inherited.clone());
|
||||
} else if maybe_inherited.is_some() {
|
||||
commands.entity(entity).remove::<(Inherited<C>, C)>();
|
||||
}
|
||||
}
|
||||
|
||||
for orphan in &orphaned {
|
||||
commands.entity(orphan).remove::<(Inherited<C>, C)>();
|
||||
}
|
||||
}
|
||||
|
||||
/// add/remove `Inherited::<C>` for targets of entities with modified `Inherited::<C>`
|
||||
pub fn propagate_inherited<C: Component + Clone + PartialEq, F: QueryFilter, R: Relationship>(
|
||||
mut commands: Commands,
|
||||
changed: Query<
|
||||
(&Inherited<C>, &R::RelationshipTarget),
|
||||
(Changed<Inherited<C>>, Without<PropagateStop<C>>, F),
|
||||
>,
|
||||
recurse: Query<
|
||||
(Option<&R::RelationshipTarget>, Option<&Inherited<C>>),
|
||||
(Without<Propagate<C>>, Without<PropagateStop<C>>, F),
|
||||
>,
|
||||
mut removed: RemovedComponents<Inherited<C>>,
|
||||
mut to_process: Local<Vec<(Entity, Option<Inherited<C>>)>>,
|
||||
) {
|
||||
// gather changed
|
||||
for (inherited, targets) in &changed {
|
||||
to_process.extend(
|
||||
targets
|
||||
.iter()
|
||||
.map(|target| (target, Some(inherited.clone()))),
|
||||
);
|
||||
}
|
||||
|
||||
// and removed
|
||||
for entity in removed.read() {
|
||||
if let Ok((Some(targets), _)) = recurse.get(entity) {
|
||||
to_process.extend(targets.iter().map(|target| (target, None)));
|
||||
}
|
||||
}
|
||||
|
||||
// propagate
|
||||
while let Some((entity, maybe_inherited)) = (*to_process).pop() {
|
||||
let Ok((maybe_targets, maybe_current)) = recurse.get(entity) else {
|
||||
continue;
|
||||
};
|
||||
|
||||
if maybe_current == maybe_inherited.as_ref() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(targets) = maybe_targets {
|
||||
to_process.extend(
|
||||
targets
|
||||
.iter()
|
||||
.map(|target| (target, maybe_inherited.clone())),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(inherited) = maybe_inherited {
|
||||
commands.entity(entity).try_insert(inherited.clone());
|
||||
} else {
|
||||
commands.entity(entity).remove::<(Inherited<C>, C)>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// add `C` to entities with `Inherited::<C>`
|
||||
pub fn propagate_output<C: Component + Clone + PartialEq, F: QueryFilter>(
|
||||
mut commands: Commands,
|
||||
changed: Query<
|
||||
(Entity, &Inherited<C>, Option<&C>),
|
||||
(Changed<Inherited<C>>, Without<PropagateOver<C>>, F),
|
||||
>,
|
||||
) {
|
||||
for (entity, inherited, maybe_current) in &changed {
|
||||
if maybe_current.is_some_and(|c| &inherited.0 == c) {
|
||||
continue;
|
||||
}
|
||||
|
||||
commands.entity(entity).try_insert(inherited.0.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_ecs::schedule::Schedule;
|
||||
|
||||
use crate::{App, Update};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Component, Clone, PartialEq, Debug)]
|
||||
struct TestValue(u32);
|
||||
|
||||
#[test]
|
||||
fn test_simple_propagate() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let intermediate = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(intermediate))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reparented() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_reparented_with_prior() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator_a = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagator_b = app.world_mut().spawn(Propagate(TestValue(2))).id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator_a))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee),
|
||||
Ok(&TestValue(1))
|
||||
);
|
||||
app.world_mut()
|
||||
.commands()
|
||||
.entity(propagatee)
|
||||
.insert(ChildOf(propagator_b));
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee),
|
||||
Ok(&TestValue(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_orphan() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_ok());
|
||||
app.world_mut()
|
||||
.commands()
|
||||
.entity(propagatee)
|
||||
.remove::<ChildOf>();
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_remove_propagated() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_ok());
|
||||
app.world_mut()
|
||||
.commands()
|
||||
.entity(propagator)
|
||||
.remove::<Propagate<TestValue>>();
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_propagate_over() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagate_over = app
|
||||
.world_mut()
|
||||
.spawn(TestValue(2))
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagate_over))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee),
|
||||
Ok(&TestValue(1))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_propagate_stop() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagate_stop = app
|
||||
.world_mut()
|
||||
.spawn(PropagateStop::<TestValue>::default())
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
let no_propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagate_stop))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), no_propagatee)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intermediate_override() {
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let intermediate = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(intermediate))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee),
|
||||
Ok(&TestValue(1))
|
||||
);
|
||||
|
||||
app.world_mut()
|
||||
.entity_mut(intermediate)
|
||||
.insert(Propagate(TestValue(2)));
|
||||
app.update();
|
||||
assert_eq!(
|
||||
app.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee),
|
||||
Ok(&TestValue(2))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_filter() {
|
||||
#[derive(Component)]
|
||||
struct Marker;
|
||||
|
||||
let mut app = App::new();
|
||||
app.add_schedule(Schedule::new(Update));
|
||||
app.add_plugins(HierarchyPropagatePlugin::<TestValue, With<Marker>>::default());
|
||||
|
||||
let propagator = app.world_mut().spawn(Propagate(TestValue(1))).id();
|
||||
let propagatee = app
|
||||
.world_mut()
|
||||
.spawn_empty()
|
||||
.insert(ChildOf(propagator))
|
||||
.id();
|
||||
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_err());
|
||||
|
||||
// NOTE: changes to the filter condition are not rechecked
|
||||
app.world_mut().entity_mut(propagator).insert(Marker);
|
||||
app.world_mut().entity_mut(propagatee).insert(Marker);
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_err());
|
||||
|
||||
app.world_mut()
|
||||
.entity_mut(propagator)
|
||||
.insert(Propagate(TestValue(1)));
|
||||
app.update();
|
||||
assert!(app
|
||||
.world_mut()
|
||||
.query::<&TestValue>()
|
||||
.get(app.world(), propagatee)
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
@ -12,7 +12,7 @@ use core::fmt::Debug;
|
||||
#[cfg(feature = "trace")]
|
||||
use tracing::info_span;
|
||||
|
||||
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
|
||||
type ExtractFn = Box<dyn FnMut(&mut World, &mut World) + Send>;
|
||||
|
||||
/// A secondary application with its own [`World`]. These can run independently of each other.
|
||||
///
|
||||
@ -160,7 +160,7 @@ impl SubApp {
|
||||
/// The first argument is the `World` to extract data from, the second argument is the app `World`.
|
||||
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
|
||||
where
|
||||
F: Fn(&mut World, &mut World) + Send + 'static,
|
||||
F: FnMut(&mut World, &mut World) + Send + 'static,
|
||||
{
|
||||
self.extract = Some(Box::new(extract));
|
||||
self
|
||||
@ -177,13 +177,13 @@ impl SubApp {
|
||||
/// ```
|
||||
/// # use bevy_app::SubApp;
|
||||
/// # let mut app = SubApp::new();
|
||||
/// let default_fn = app.take_extract();
|
||||
/// let mut default_fn = app.take_extract();
|
||||
/// app.set_extract(move |main, render| {
|
||||
/// // Do pre-extract custom logic
|
||||
/// // [...]
|
||||
///
|
||||
/// // Call Bevy's default, which executes the Extract phase
|
||||
/// if let Some(f) = default_fn.as_ref() {
|
||||
/// if let Some(f) = default_fn.as_mut() {
|
||||
/// f(main, render);
|
||||
/// }
|
||||
///
|
||||
@ -338,7 +338,7 @@ impl SubApp {
|
||||
/// See [`App::add_event`].
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
T: Event,
|
||||
T: BufferedEvent,
|
||||
{
|
||||
if !self.world.contains_resource::<Events<T>>() {
|
||||
EventRegistry::register_event::<T>(self.world_mut());
|
||||
|
||||
@ -160,7 +160,7 @@ impl TaskPoolOptions {
|
||||
pub fn create_default_pools(&self) {
|
||||
let total_threads = bevy_tasks::available_parallelism()
|
||||
.clamp(self.min_total_threads, self.max_total_threads);
|
||||
trace!("Assigning {} cores to default task pools", total_threads);
|
||||
trace!("Assigning {total_threads} cores to default task pools");
|
||||
|
||||
let mut remaining_threads = total_threads;
|
||||
|
||||
@ -170,7 +170,7 @@ impl TaskPoolOptions {
|
||||
.io
|
||||
.get_number_of_threads(remaining_threads, total_threads);
|
||||
|
||||
trace!("IO Threads: {}", io_threads);
|
||||
trace!("IO Threads: {io_threads}");
|
||||
remaining_threads = remaining_threads.saturating_sub(io_threads);
|
||||
|
||||
IoTaskPool::get_or_init(|| {
|
||||
@ -200,7 +200,7 @@ impl TaskPoolOptions {
|
||||
.async_compute
|
||||
.get_number_of_threads(remaining_threads, total_threads);
|
||||
|
||||
trace!("Async Compute Threads: {}", async_compute_threads);
|
||||
trace!("Async Compute Threads: {async_compute_threads}");
|
||||
remaining_threads = remaining_threads.saturating_sub(async_compute_threads);
|
||||
|
||||
AsyncComputeTaskPool::get_or_init(|| {
|
||||
@ -231,7 +231,7 @@ impl TaskPoolOptions {
|
||||
.compute
|
||||
.get_number_of_threads(remaining_threads, total_threads);
|
||||
|
||||
trace!("Compute Threads: {}", compute_threads);
|
||||
trace!("Compute Threads: {compute_threads}");
|
||||
|
||||
ComputeTaskPool::get_or_init(|| {
|
||||
let builder = TaskPoolBuilder::default()
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_asset"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides asset functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
@ -19,19 +19,19 @@ watch = []
|
||||
trace = []
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"bevy_reflect",
|
||||
] }
|
||||
bevy_asset_macros = { path = "macros", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_asset_macros = { path = "macros", version = "0.17.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"uuid",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
|
||||
"async_executor",
|
||||
] }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
@ -54,10 +54,10 @@ parking_lot = { version = "0.12", default-features = false, features = [
|
||||
"arc_lock",
|
||||
"send_guard",
|
||||
] }
|
||||
ron = { version = "0.8", default-features = false }
|
||||
ron = { version = "0.10", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = ["derive"] }
|
||||
thiserror = { version = "2", default-features = false }
|
||||
derive_more = { version = "1", default-features = false, features = ["from"] }
|
||||
derive_more = { version = "2", default-features = false, features = ["from"] }
|
||||
uuid = { version = "1.13.1", default-features = false, features = [
|
||||
"v4",
|
||||
"serde",
|
||||
@ -65,7 +65,7 @@ uuid = { version = "1.13.1", default-features = false, features = [
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
@ -78,13 +78,13 @@ web-sys = { version = "0.3", features = [
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
[package]
|
||||
name = "bevy_asset_macros"
|
||||
version = "0.16.0-dev"
|
||||
version = "0.17.0-dev"
|
||||
edition = "2024"
|
||||
description = "Derive implementations for bevy_asset"
|
||||
homepage = "https://bevyengine.org"
|
||||
homepage = "https://bevy.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
//! Macros for deriving asset traits.
|
||||
|
||||
use bevy_macro_utils::BevyManifest;
|
||||
use proc_macro::{Span, TokenStream};
|
||||
use quote::{format_ident, quote};
|
||||
@ -12,6 +13,7 @@ pub(crate) fn bevy_asset_path() -> Path {
|
||||
|
||||
const DEPENDENCY_ATTRIBUTE: &str = "dependency";
|
||||
|
||||
/// Implement the `Asset` trait.
|
||||
#[proc_macro_derive(Asset, attributes(dependency))]
|
||||
pub fn derive_asset(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
@ -30,6 +32,7 @@ pub fn derive_asset(input: TokenStream) -> TokenStream {
|
||||
})
|
||||
}
|
||||
|
||||
/// Implement the `VisitAssetDependencies` trait.
|
||||
#[proc_macro_derive(VisitAssetDependencies, attributes(dependency))]
|
||||
pub fn derive_asset_dependency_visitor(input: TokenStream) -> TokenStream {
|
||||
let ast = parse_macro_input!(input as DeriveInput);
|
||||
|
||||
@ -106,7 +106,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
|
||||
/// - Removed assets are not detected.
|
||||
///
|
||||
/// The list of changed assets only gets updated in the [`AssetEventSystems`] system set,
|
||||
/// which runs in `Last`. Therefore, `AssetChanged` will only pick up asset changes in schedules
|
||||
/// which runs in `PostUpdate`. Therefore, `AssetChanged` will only pick up asset changes in schedules
|
||||
/// following [`AssetEventSystems`] or the next frame. Consider adding the system in the `Last` schedule
|
||||
/// after [`AssetEventSystems`] if you need to react without frame delay to asset changes.
|
||||
///
|
||||
@ -158,9 +158,9 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
|
||||
fetch
|
||||
}
|
||||
|
||||
unsafe fn init_fetch<'w>(
|
||||
unsafe fn init_fetch<'w, 's>(
|
||||
world: UnsafeWorldCell<'w>,
|
||||
state: &Self::State,
|
||||
state: &'s Self::State,
|
||||
last_run: Tick,
|
||||
this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
@ -201,9 +201,9 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
|
||||
|
||||
const IS_DENSE: bool = <&A>::IS_DENSE;
|
||||
|
||||
unsafe fn set_archetype<'w>(
|
||||
unsafe fn set_archetype<'w, 's>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
state: &'s Self::State,
|
||||
archetype: &'w Archetype,
|
||||
table: &'w Table,
|
||||
) {
|
||||
@ -215,7 +215,11 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn set_table<'w>(fetch: &mut Self::Fetch<'w>, state: &Self::State, table: &'w Table) {
|
||||
unsafe fn set_table<'w, 's>(
|
||||
fetch: &mut Self::Fetch<'w>,
|
||||
state: &Self::State,
|
||||
table: &'w Table,
|
||||
) {
|
||||
if let Some(inner) = &mut fetch.inner {
|
||||
// SAFETY: We delegate to the inner `set_table` for `A`
|
||||
unsafe {
|
||||
@ -265,6 +269,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
|
||||
|
||||
#[inline]
|
||||
unsafe fn filter_fetch(
|
||||
state: &Self::State,
|
||||
fetch: &mut Self::Fetch<'_>,
|
||||
entity: Entity,
|
||||
table_row: TableRow,
|
||||
@ -272,7 +277,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
|
||||
fetch.inner.as_mut().is_some_and(|inner| {
|
||||
// SAFETY: We delegate to the inner `fetch` for `A`
|
||||
unsafe {
|
||||
let handle = <&A>::fetch(inner, entity, table_row);
|
||||
let handle = <&A>::fetch(&state.asset_id, inner, entity, table_row);
|
||||
fetch.check.has_changed(handle)
|
||||
}
|
||||
})
|
||||
|
||||
@ -437,6 +437,18 @@ impl<A: Asset> Assets<A> {
|
||||
result
|
||||
}
|
||||
|
||||
/// Retrieves a mutable reference to the [`Asset`] with the given `id`, if it exists.
|
||||
///
|
||||
/// This is the same as [`Assets::get_mut`] except it doesn't emit [`AssetEvent::Modified`].
|
||||
#[inline]
|
||||
pub fn get_mut_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<&mut A> {
|
||||
let id: AssetId<A> = id.into();
|
||||
match id {
|
||||
AssetId::Index { index, .. } => self.dense_storage.get_mut(index),
|
||||
AssetId::Uuid { uuid } => self.hash_map.get_mut(&uuid),
|
||||
}
|
||||
}
|
||||
|
||||
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists.
|
||||
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
|
||||
pub fn remove(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
|
||||
@ -450,6 +462,8 @@ impl<A: Asset> Assets<A> {
|
||||
|
||||
/// Removes (and returns) the [`Asset`] with the given `id`, if it exists. This skips emitting [`AssetEvent::Removed`].
|
||||
/// Note that this supports anything that implements `Into<AssetId<A>>`, which includes [`Handle`] and [`AssetId`].
|
||||
///
|
||||
/// This is the same as [`Assets::remove`] except it doesn't emit [`AssetEvent::Removed`].
|
||||
pub fn remove_untracked(&mut self, id: impl Into<AssetId<A>>) -> Option<A> {
|
||||
let id: AssetId<A> = id.into();
|
||||
self.duplicate_handles.remove(&id);
|
||||
|
||||
@ -20,6 +20,7 @@ pub trait DirectAssetAccessExt {
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
) -> Handle<A>;
|
||||
}
|
||||
|
||||
impl DirectAssetAccessExt for World {
|
||||
/// Insert an asset similarly to [`Assets::add`].
|
||||
///
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
use crate::{Asset, AssetId, AssetLoadError, AssetPath, UntypedAssetId};
|
||||
use bevy_ecs::event::Event;
|
||||
use bevy_ecs::event::{BufferedEvent, Event};
|
||||
use bevy_reflect::Reflect;
|
||||
use core::fmt::Debug;
|
||||
|
||||
/// An event emitted when a specific [`Asset`] fails to load.
|
||||
/// A [`BufferedEvent`] emitted when a specific [`Asset`] fails to load.
|
||||
///
|
||||
/// For an untyped equivalent, see [`UntypedAssetLoadFailedEvent`].
|
||||
#[derive(Event, Clone, Debug)]
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
pub struct AssetLoadFailedEvent<A: Asset> {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: AssetId<A>,
|
||||
@ -24,7 +24,7 @@ impl<A: Asset> AssetLoadFailedEvent<A> {
|
||||
}
|
||||
|
||||
/// An untyped version of [`AssetLoadFailedEvent`].
|
||||
#[derive(Event, Clone, Debug)]
|
||||
#[derive(Event, BufferedEvent, Clone, Debug)]
|
||||
pub struct UntypedAssetLoadFailedEvent {
|
||||
/// The stable identifier of the asset that failed to load.
|
||||
pub id: UntypedAssetId,
|
||||
@ -44,9 +44,9 @@ impl<A: Asset> From<&AssetLoadFailedEvent<A>> for UntypedAssetLoadFailedEvent {
|
||||
}
|
||||
}
|
||||
|
||||
/// Events that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
|
||||
/// [`BufferedEvent`]s that occur for a specific loaded [`Asset`], such as "value changed" events and "dependency" events.
|
||||
#[expect(missing_docs, reason = "Documenting the id fields is unhelpful.")]
|
||||
#[derive(Event, Reflect)]
|
||||
#[derive(Event, BufferedEvent, Reflect)]
|
||||
pub enum AssetEvent<A: Asset> {
|
||||
/// Emitted whenever an [`Asset`] is added.
|
||||
Added { id: AssetId<A> },
|
||||
|
||||
@ -7,10 +7,12 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect, TypePath};
|
||||
use core::{
|
||||
any::TypeId,
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use disqualified::ShortName;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
|
||||
/// This should _only_ be used for one specific asset type.
|
||||
@ -117,7 +119,7 @@ impl core::fmt::Debug for StrongHandle {
|
||||
/// avoiding the need to store multiple copies of the same data.
|
||||
///
|
||||
/// If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
|
||||
/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
|
||||
/// alive until the [`Handle`] is dropped. If a [`Handle`] is [`Handle::Uuid`], it does not necessarily reference a live [`Asset`],
|
||||
/// nor will it keep assets alive.
|
||||
///
|
||||
/// Modifying a *handle* will change which existing asset is referenced, but modifying the *asset*
|
||||
@ -133,16 +135,16 @@ pub enum Handle<A: Asset> {
|
||||
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
|
||||
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
|
||||
Strong(Arc<StrongHandle>),
|
||||
/// A "weak" reference to an [`Asset`]. If a [`Handle`] is [`Handle::Weak`], it does not necessarily reference a live [`Asset`],
|
||||
/// nor will it keep assets alive.
|
||||
Weak(AssetId<A>),
|
||||
/// A reference to an [`Asset`] using a stable-across-runs / const identifier. Dropping this
|
||||
/// handle will not result in the asset being dropped.
|
||||
Uuid(Uuid, #[reflect(ignore, clone)] PhantomData<fn() -> A>),
|
||||
}
|
||||
|
||||
impl<T: Asset> Clone for Handle<T> {
|
||||
fn clone(&self) -> Self {
|
||||
match self {
|
||||
Handle::Strong(handle) => Handle::Strong(handle.clone()),
|
||||
Handle::Weak(id) => Handle::Weak(*id),
|
||||
Handle::Uuid(uuid, ..) => Handle::Uuid(*uuid, PhantomData),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,7 +155,7 @@ impl<A: Asset> Handle<A> {
|
||||
pub fn id(&self) -> AssetId<A> {
|
||||
match self {
|
||||
Handle::Strong(handle) => handle.id.typed_unchecked(),
|
||||
Handle::Weak(id) => *id,
|
||||
Handle::Uuid(uuid, ..) => AssetId::Uuid { uuid: *uuid },
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,14 +164,14 @@ impl<A: Asset> Handle<A> {
|
||||
pub fn path(&self) -> Option<&AssetPath<'static>> {
|
||||
match self {
|
||||
Handle::Strong(handle) => handle.path.as_ref(),
|
||||
Handle::Weak(_) => None,
|
||||
Handle::Uuid(..) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a weak handle.
|
||||
/// Returns `true` if this is a uuid handle.
|
||||
#[inline]
|
||||
pub fn is_weak(&self) -> bool {
|
||||
matches!(self, Handle::Weak(_))
|
||||
pub fn is_uuid(&self) -> bool {
|
||||
matches!(self, Handle::Uuid(..))
|
||||
}
|
||||
|
||||
/// Returns `true` if this is a strong handle.
|
||||
@ -178,18 +180,9 @@ impl<A: Asset> Handle<A> {
|
||||
matches!(self, Handle::Strong(_))
|
||||
}
|
||||
|
||||
/// Creates a [`Handle::Weak`] clone of this [`Handle`], which will not keep the referenced [`Asset`] alive.
|
||||
#[inline]
|
||||
pub fn clone_weak(&self) -> Self {
|
||||
match self {
|
||||
Handle::Strong(handle) => Handle::Weak(handle.id.typed_unchecked::<A>()),
|
||||
Handle::Weak(id) => Handle::Weak(*id),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts this [`Handle`] to an "untyped" / "generic-less" [`UntypedHandle`], which stores the [`Asset`] type information
|
||||
/// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Weak`] for
|
||||
/// [`Handle::Weak`].
|
||||
/// _inside_ [`UntypedHandle`]. This will return [`UntypedHandle::Strong`] for [`Handle::Strong`] and [`UntypedHandle::Uuid`] for
|
||||
/// [`Handle::Uuid`].
|
||||
#[inline]
|
||||
pub fn untyped(self) -> UntypedHandle {
|
||||
self.into()
|
||||
@ -198,7 +191,7 @@ impl<A: Asset> Handle<A> {
|
||||
|
||||
impl<A: Asset> Default for Handle<A> {
|
||||
fn default() -> Self {
|
||||
Handle::Weak(AssetId::default())
|
||||
Handle::Uuid(AssetId::<A>::DEFAULT_UUID, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,7 +207,7 @@ impl<A: Asset> core::fmt::Debug for Handle<A> {
|
||||
handle.path
|
||||
)
|
||||
}
|
||||
Handle::Weak(id) => write!(f, "WeakHandle<{name}>({:?})", id.internal()),
|
||||
Handle::Uuid(uuid, ..) => write!(f, "UuidHandle<{name}>({uuid:?})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -284,8 +277,13 @@ impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
|
||||
pub enum UntypedHandle {
|
||||
/// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
|
||||
Strong(Arc<StrongHandle>),
|
||||
/// A weak handle, which does not keep the referenced [`Asset`] alive.
|
||||
Weak(UntypedAssetId),
|
||||
/// A UUID handle, which does not keep the referenced [`Asset`] alive.
|
||||
Uuid {
|
||||
/// An identifier that records the underlying asset type.
|
||||
type_id: TypeId,
|
||||
/// The UUID provided during asset registration.
|
||||
uuid: Uuid,
|
||||
},
|
||||
}
|
||||
|
||||
impl UntypedHandle {
|
||||
@ -294,7 +292,10 @@ impl UntypedHandle {
|
||||
pub fn id(&self) -> UntypedAssetId {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => handle.id,
|
||||
UntypedHandle::Weak(id) => *id,
|
||||
UntypedHandle::Uuid { type_id, uuid } => UntypedAssetId::Uuid {
|
||||
uuid: *uuid,
|
||||
type_id: *type_id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -303,16 +304,7 @@ impl UntypedHandle {
|
||||
pub fn path(&self) -> Option<&AssetPath<'static>> {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => handle.path.as_ref(),
|
||||
UntypedHandle::Weak(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates an [`UntypedHandle::Weak`] clone of this [`UntypedHandle`], which will not keep the referenced [`Asset`] alive.
|
||||
#[inline]
|
||||
pub fn clone_weak(&self) -> UntypedHandle {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => UntypedHandle::Weak(handle.id),
|
||||
UntypedHandle::Weak(id) => UntypedHandle::Weak(*id),
|
||||
UntypedHandle::Uuid { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -321,7 +313,7 @@ impl UntypedHandle {
|
||||
pub fn type_id(&self) -> TypeId {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => handle.id.type_id(),
|
||||
UntypedHandle::Weak(id) => id.type_id(),
|
||||
UntypedHandle::Uuid { type_id, .. } => *type_id,
|
||||
}
|
||||
}
|
||||
|
||||
@ -330,7 +322,7 @@ impl UntypedHandle {
|
||||
pub fn typed_unchecked<A: Asset>(self) -> Handle<A> {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => Handle::Strong(handle),
|
||||
UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
|
||||
UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
|
||||
}
|
||||
}
|
||||
|
||||
@ -345,10 +337,7 @@ impl UntypedHandle {
|
||||
TypeId::of::<A>(),
|
||||
"The target Handle<A>'s TypeId does not match the TypeId of this UntypedHandle"
|
||||
);
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => Handle::Strong(handle),
|
||||
UntypedHandle::Weak(id) => Handle::Weak(id.typed_unchecked::<A>()),
|
||||
}
|
||||
self.typed_unchecked()
|
||||
}
|
||||
|
||||
/// Converts to a typed Handle. This will panic if the internal [`TypeId`] does not match the given asset type `A`
|
||||
@ -376,7 +365,7 @@ impl UntypedHandle {
|
||||
pub fn meta_transform(&self) -> Option<&MetaTransform> {
|
||||
match self {
|
||||
UntypedHandle::Strong(handle) => handle.meta_transform.as_ref(),
|
||||
UntypedHandle::Weak(_) => None,
|
||||
UntypedHandle::Uuid { .. } => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -409,12 +398,9 @@ impl core::fmt::Debug for UntypedHandle {
|
||||
handle.path
|
||||
)
|
||||
}
|
||||
UntypedHandle::Weak(id) => write!(
|
||||
f,
|
||||
"WeakHandle{{ type_id: {:?}, id: {:?} }}",
|
||||
id.type_id(),
|
||||
id.internal()
|
||||
),
|
||||
UntypedHandle::Uuid { type_id, uuid } => {
|
||||
write!(f, "UuidHandle{{ type_id: {type_id:?}, uuid: {uuid:?} }}",)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -474,7 +460,10 @@ impl<A: Asset> From<Handle<A>> for UntypedHandle {
|
||||
fn from(value: Handle<A>) -> Self {
|
||||
match value {
|
||||
Handle::Strong(handle) => UntypedHandle::Strong(handle),
|
||||
Handle::Weak(id) => UntypedHandle::Weak(id.into()),
|
||||
Handle::Uuid(uuid, _) => UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<A>(),
|
||||
uuid,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -490,36 +479,37 @@ impl<A: Asset> TryFrom<UntypedHandle> for Handle<A> {
|
||||
return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
|
||||
}
|
||||
|
||||
match value {
|
||||
UntypedHandle::Strong(handle) => Ok(Handle::Strong(handle)),
|
||||
UntypedHandle::Weak(id) => {
|
||||
let Ok(id) = id.try_into() else {
|
||||
return Err(UntypedAssetConversionError::TypeIdMismatch { expected, found });
|
||||
};
|
||||
Ok(Handle::Weak(id))
|
||||
}
|
||||
}
|
||||
Ok(match value {
|
||||
UntypedHandle::Strong(handle) => Handle::Strong(handle),
|
||||
UntypedHandle::Uuid { uuid, .. } => Handle::Uuid(uuid, PhantomData),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a weak [`Handle`] from a string literal containing a UUID.
|
||||
/// Creates a [`Handle`] from a string literal containing a UUID.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_asset::{Handle, weak_handle};
|
||||
/// # type Shader = ();
|
||||
/// const SHADER: Handle<Shader> = weak_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
|
||||
/// # use bevy_asset::{Handle, uuid_handle};
|
||||
/// # type Image = ();
|
||||
/// const IMAGE: Handle<Image> = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! weak_handle {
|
||||
macro_rules! uuid_handle {
|
||||
($uuid:expr) => {{
|
||||
$crate::Handle::Weak($crate::AssetId::Uuid {
|
||||
uuid: $crate::uuid::uuid!($uuid),
|
||||
})
|
||||
$crate::Handle::Uuid($crate::uuid::uuid!($uuid), core::marker::PhantomData)
|
||||
}};
|
||||
}
|
||||
|
||||
#[deprecated = "Use uuid_handle! instead"]
|
||||
#[macro_export]
|
||||
macro_rules! weak_handle {
|
||||
($uuid:expr) => {
|
||||
uuid_handle!($uuid)
|
||||
};
|
||||
}
|
||||
|
||||
/// Errors preventing the conversion of to/from an [`UntypedHandle`] and a [`Handle`].
|
||||
#[derive(Error, Debug, PartialEq, Clone)]
|
||||
#[non_exhaustive]
|
||||
@ -559,15 +549,12 @@ mod tests {
|
||||
/// Typed and Untyped `Handles` should be equivalent to each other and themselves
|
||||
#[test]
|
||||
fn equality() {
|
||||
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
|
||||
let untyped = UntypedAssetId::Uuid {
|
||||
let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
|
||||
let untyped = UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<TestAsset>(),
|
||||
uuid: UUID_1,
|
||||
};
|
||||
|
||||
let typed = Handle::Weak(typed);
|
||||
let untyped = UntypedHandle::Weak(untyped);
|
||||
|
||||
assert_eq!(
|
||||
Ok(typed.clone()),
|
||||
Handle::<TestAsset>::try_from(untyped.clone())
|
||||
@ -585,22 +572,17 @@ mod tests {
|
||||
fn ordering() {
|
||||
assert!(UUID_1 < UUID_2);
|
||||
|
||||
let typed_1 = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
|
||||
let typed_2 = AssetId::<TestAsset>::Uuid { uuid: UUID_2 };
|
||||
let untyped_1 = UntypedAssetId::Uuid {
|
||||
let typed_1 = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
|
||||
let typed_2 = Handle::<TestAsset>::Uuid(UUID_2, PhantomData);
|
||||
let untyped_1 = UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<TestAsset>(),
|
||||
uuid: UUID_1,
|
||||
};
|
||||
let untyped_2 = UntypedAssetId::Uuid {
|
||||
let untyped_2 = UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<TestAsset>(),
|
||||
uuid: UUID_2,
|
||||
};
|
||||
|
||||
let typed_1 = Handle::Weak(typed_1);
|
||||
let typed_2 = Handle::Weak(typed_2);
|
||||
let untyped_1 = UntypedHandle::Weak(untyped_1);
|
||||
let untyped_2 = UntypedHandle::Weak(untyped_2);
|
||||
|
||||
assert!(typed_1 < typed_2);
|
||||
assert!(untyped_1 < untyped_2);
|
||||
|
||||
@ -617,15 +599,12 @@ mod tests {
|
||||
/// Typed and Untyped `Handles` should be equivalently hashable to each other and themselves
|
||||
#[test]
|
||||
fn hashing() {
|
||||
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
|
||||
let untyped = UntypedAssetId::Uuid {
|
||||
let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
|
||||
let untyped = UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<TestAsset>(),
|
||||
uuid: UUID_1,
|
||||
};
|
||||
|
||||
let typed = Handle::Weak(typed);
|
||||
let untyped = UntypedHandle::Weak(untyped);
|
||||
|
||||
assert_eq!(
|
||||
hash(&typed),
|
||||
hash(&Handle::<TestAsset>::try_from(untyped.clone()).unwrap())
|
||||
@ -637,15 +616,12 @@ mod tests {
|
||||
/// Typed and Untyped `Handles` should be interchangeable
|
||||
#[test]
|
||||
fn conversion() {
|
||||
let typed = AssetId::<TestAsset>::Uuid { uuid: UUID_1 };
|
||||
let untyped = UntypedAssetId::Uuid {
|
||||
let typed = Handle::<TestAsset>::Uuid(UUID_1, PhantomData);
|
||||
let untyped = UntypedHandle::Uuid {
|
||||
type_id: TypeId::of::<TestAsset>(),
|
||||
uuid: UUID_1,
|
||||
};
|
||||
|
||||
let typed = Handle::Weak(typed);
|
||||
let untyped = UntypedHandle::Weak(untyped);
|
||||
|
||||
assert_eq!(typed, Handle::try_from(untyped.clone()).unwrap());
|
||||
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
|
||||
}
|
||||
|
||||
@ -56,6 +56,7 @@ pub(crate) struct EmbeddedEventHandler {
|
||||
dir: Dir,
|
||||
last_event: Option<AssetSourceEvent>,
|
||||
}
|
||||
|
||||
impl FilesystemEventHandler for EmbeddedEventHandler {
|
||||
fn begin(&mut self) {
|
||||
self.last_event = None;
|
||||
|
||||
@ -141,16 +141,19 @@ impl EmbeddedAssetRegistry {
|
||||
pub trait GetAssetServer {
|
||||
fn get_asset_server(&self) -> &AssetServer;
|
||||
}
|
||||
|
||||
impl GetAssetServer for App {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self.world().get_asset_server()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAssetServer for World {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self.resource()
|
||||
}
|
||||
}
|
||||
|
||||
impl GetAssetServer for AssetServer {
|
||||
fn get_asset_server(&self) -> &AssetServer {
|
||||
self
|
||||
|
||||
@ -47,7 +47,7 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_
|
||||
}
|
||||
};
|
||||
|
||||
std::io::Error::new(std::io::ErrorKind::Other, message)
|
||||
std::io::Error::other(message)
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,10 +62,7 @@ impl HttpWasmAssetReader {
|
||||
let worker: web_sys::WorkerGlobalScope = global.unchecked_into();
|
||||
worker.fetch_with_str(path.to_str().unwrap())
|
||||
} else {
|
||||
let error = std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
"Unsupported JavaScript global context",
|
||||
);
|
||||
let error = std::io::Error::other("Unsupported JavaScript global context");
|
||||
return Err(AssetReaderError::Io(error.into()));
|
||||
};
|
||||
let resp_value = JsFuture::from(promise)
|
||||
@ -81,7 +78,10 @@ impl HttpWasmAssetReader {
|
||||
let reader = VecReader::new(bytes);
|
||||
Ok(reader)
|
||||
}
|
||||
404 => Err(AssetReaderError::NotFound(path)),
|
||||
// Some web servers, including itch.io's CDN, return 403 when a requested file isn't present.
|
||||
// TODO: remove handling of 403 as not found when it's easier to configure
|
||||
// see https://github.com/bevyengine/bevy/pull/19268#pullrequestreview-2882410105
|
||||
403 | 404 => Err(AssetReaderError::NotFound(path)),
|
||||
status => Err(AssetReaderError::HttpError(status)),
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user