Merge remote-tracking branch 'origin/main' into frame_time_graph

This commit is contained in:
Daniel Skates 2025-07-08 22:39:13 +08:00
commit 81c242d894
1106 changed files with 55750 additions and 25108 deletions

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://bevyengine.org/donate/
custom: https://bevy.org/donate/

View File

@ -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.

View File

@ -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: |

View File

@ -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

View File

@ -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

View File

@ -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 ✨`
})

View File

@ -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!

View File

@ -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

View File

@ -1,4 +1,4 @@
# [![Bevy](assets/branding/bevy_logo_light_dark_and_dimmed.svg)](https://bevyengine.org)
# [![Bevy](assets/branding/bevy_logo_light_dark_and_dimmed.svg)](https://bevy.org)
[![License](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](https://github.com/bevyengine/bevy#license)
[![Crates.io](https://img.shields.io/crates/v/bevy.svg)](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.

View File

@ -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,
),

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 229 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 551 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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(

View File

@ -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 ...

View File

@ -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

View File

@ -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(

View File

@ -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(

View File

@ -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(

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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) {}

View File

@ -12,7 +12,7 @@ struct VoxelVisualizationIrradianceVolumeInfo {
intensity: f32,
}
@group(2) @binding(100)
@group(3) @binding(100)
var<uniform> irradiance_volume_info: VoxelVisualizationIrradianceVolumeInfo;
@fragment

View File

@ -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(

View 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);
}

View File

@ -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(

View File

@ -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(

View File

@ -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,

View File

@ -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>;

View File

@ -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> {

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -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

View 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();
}

View 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,
);

View 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();
}

View 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();
}

View 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();
}

View File

@ -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;

View File

@ -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();
}

View File

@ -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)

View File

@ -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());
});

View File

@ -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();
}

View File

@ -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));
});

View File

@ -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();
}
}

View File

@ -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());
});

View File

@ -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,

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
});

View File

@ -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);

View File

@ -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();

View File

@ -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();

View File

@ -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();

View File

@ -17,6 +17,7 @@ criterion_group!(
benches,
empty_commands,
spawn_commands,
nonempty_spawn_commands,
insert_commands,
fake_commands,
zero_sized_commands,

View File

@ -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 {

View File

@ -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,37 +160,33 @@ pub fn world_query_get(criterion: &mut Criterion) {
}
});
});
group.bench_function(
format!("{}_entities_sparse_wide", entity_count),
|bencher| {
let mut world = setup_wide::<(
WideSparse<0>,
WideSparse<1>,
WideSparse<2>,
WideSparse<3>,
WideSparse<4>,
WideSparse<5>,
)>(entity_count);
let mut query = world.query::<(
&WideSparse<0>,
&WideSparse<1>,
&WideSparse<2>,
&WideSparse<3>,
&WideSparse<4>,
&WideSparse<5>,
)>();
group.bench_function(format!("{entity_count}_entities_sparse_wide"), |bencher| {
let mut world = setup_wide::<(
WideSparse<0>,
WideSparse<1>,
WideSparse<2>,
WideSparse<3>,
WideSparse<4>,
WideSparse<5>,
)>(entity_count);
let mut query = world.query::<(
&WideSparse<0>,
&WideSparse<1>,
&WideSparse<2>,
&WideSparse<3>,
&WideSparse<4>,
&WideSparse<5>,
)>();
bencher.iter(|| {
for i in 0..entity_count {
// SAFETY: Range is exclusive.
let entity = Entity::from_raw(EntityRow::new(unsafe {
NonMaxU32::new_unchecked(i)
}));
assert!(query.get(&world, entity).is_ok());
}
});
},
);
bencher.iter(|| {
for i in 0..entity_count {
// SAFETY: Range is exclusive.
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()))

View File

@ -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>,

View File

@ -155,6 +155,7 @@ fn bench(c: &mut Criterion) {
&mesh.positions,
Some(&mesh.normals),
Some(&mesh.indices),
None,
backface_culling,
);

View File

@ -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()));

View File

@ -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, ());
}

View File

@ -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 `{}`.

View File

@ -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 }

View File

@ -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);

View File

@ -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]

View File

@ -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],

View File

@ -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,28 +793,55 @@ 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 {
node_type: match serialized_node.node_type {
SerializedAnimationNodeType::Clip(ref clip) => match clip {
SerializedAnimationClip::AssetId(asset_id) => {
AnimationNodeType::Clip(Handle::Weak(*asset_id))
// 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 {
MigrationSerializedAnimationClip::Modern(path) => {
AnimationNodeType::Clip(load_context.load(path.clone()))
}
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;
}
SerializedAnimationClip::AssetPath(asset_path) => {
AnimationNodeType::Clip(load_context.load(asset_path))
}
},
SerializedAnimationNodeType::Blend => AnimationNodeType::Blend,
SerializedAnimationNodeType::Add => AnimationNodeType::Add,
AnimationNodeType::Clip(load_context.load(path.clone()))
}
MigrationSerializedAnimationClip::Legacy(
SerializedAnimationClip::AssetId(_),
) => {
return Err(AnimationGraphLoadError::GraphContainsLegacyAssetId);
}
},
mask: serialized_node.mask,
weight: serialized_node.weight,
SerializedAnimationNodeType::Blend => AnimationNodeType::Blend,
SerializedAnimationNodeType::Add => AnimationNodeType::Add,
},
|_, _| (),
),
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 {
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()),
),
},
AnimationNodeType::Blend => SerializedAnimationNodeType::Blend,
AnimationNodeType::Add => SerializedAnimationNodeType::Add,
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(
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`].
///

View File

@ -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]

View File

@ -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"] }

View File

@ -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,44 +152,46 @@ 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>();
let texture_bind_group = render_device.create_bind_group_layout(
"sharpening_texture_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
// CAS Settings
uniform_buffer::<CasUniform>(true),
),
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(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
// CAS Settings
uniform_buffer::<CasUniform>(true),
),
);
),
);
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
let sampler = render_device.create_sampler(&SamplerDescriptor::default());
CasPipeline {
texture_bind_group,
sampler,
}
}
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()
}
}
}

View File

@ -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};
}

View File

@ -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,47 +118,46 @@ 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>();
let texture_bind_group = render_device.create_bind_group_layout(
"fxaa_texture_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
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(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
);
),
);
let sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});
let sampler = render_device.create_sampler(&SamplerDescriptor {
mipmap_filter: FilterMode::Linear,
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..default()
});
FxaaPipeline {
texture_bind_group,
sampler,
}
}
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()
}
}
}

View File

@ -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));
}
}

View File

@ -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,81 +352,79 @@ 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>();
// 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",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
uniform_buffer::<SmaaInfoUniform>(true)
.visibility(ShaderStages::VERTEX_FRAGMENT),
),
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",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
uniform_buffer::<SmaaInfoUniform>(true).visibility(ShaderStages::VERTEX_FRAGMENT),
),
);
),
);
// Create the edge detection bind group layout (pass 1, bind group 1).
let edge_detection_bind_group_layout = render_device.create_bind_group_layout(
"SMAA edge detection bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(sampler(SamplerBindingType::Filtering),),
// Create the edge detection bind group layout (pass 1, bind group 1).
let edge_detection_bind_group_layout = render_device.create_bind_group_layout(
"SMAA edge detection bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(sampler(SamplerBindingType::Filtering),),
),
);
// Create the blending weight calculation bind group layout (pass 2, bind group 1).
let blending_weight_calculation_bind_group_layout = render_device.create_bind_group_layout(
"SMAA blending weight calculation bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }), // edges texture
sampler(SamplerBindingType::Filtering), // edges sampler
texture_2d(TextureSampleType::Float { filterable: true }), // search texture
texture_2d(TextureSampleType::Float { filterable: true }), // area texture
),
);
),
);
// Create the blending weight calculation bind group layout (pass 2, bind group 1).
let blending_weight_calculation_bind_group_layout = render_device.create_bind_group_layout(
"SMAA blending weight calculation bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }), // edges texture
sampler(SamplerBindingType::Filtering), // edges sampler
texture_2d(TextureSampleType::Float { filterable: true }), // search texture
texture_2d(TextureSampleType::Float { filterable: true }), // area texture
),
// Create the neighborhood blending bind group layout (pass 3, bind group 1).
let neighborhood_blending_bind_group_layout = render_device.create_bind_group_layout(
"SMAA neighborhood blending bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
);
),
);
// Create the neighborhood blending bind group layout (pass 3, bind group 1).
let neighborhood_blending_bind_group_layout = render_device.create_bind_group_layout(
"SMAA neighborhood blending bind group layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
texture_2d(TextureSampleType::Float { filterable: true }),
sampler(SamplerBindingType::Filtering),
),
),
);
let shader = load_embedded_asset!(asset_server.as_ref(), "smaa.wgsl");
SmaaPipelines {
edge_detection: SmaaEdgeDetectionPipeline {
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
edge_detection_bind_group_layout,
},
blending_weight_calculation: SmaaBlendingWeightCalculationPipeline {
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
blending_weight_calculation_bind_group_layout,
},
neighborhood_blending: SmaaNeighborhoodBlendingPipeline {
postprocess_bind_group_layout,
neighborhood_blending_bind_group_layout,
},
}
}
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>();

View File

@ -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)

View File

@ -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,52 +232,57 @@ 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,
min_filter: FilterMode::Nearest,
..SamplerDescriptor::default()
});
let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
label: Some("taa_linear_sampler"),
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..SamplerDescriptor::default()
});
let nearest_sampler = render_device.create_sampler(&SamplerDescriptor {
label: Some("taa_nearest_sampler"),
mag_filter: FilterMode::Nearest,
min_filter: FilterMode::Nearest,
..SamplerDescriptor::default()
});
let linear_sampler = render_device.create_sampler(&SamplerDescriptor {
label: Some("taa_linear_sampler"),
mag_filter: FilterMode::Linear,
min_filter: FilterMode::Linear,
..SamplerDescriptor::default()
});
let taa_bind_group_layout = render_device.create_bind_group_layout(
"taa_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
// View target (read)
texture_2d(TextureSampleType::Float { filterable: true }),
// TAA History (read)
texture_2d(TextureSampleType::Float { filterable: true }),
// Motion Vectors
texture_2d(TextureSampleType::Float { filterable: true }),
// Depth
texture_depth_2d(),
// Nearest sampler
sampler(SamplerBindingType::NonFiltering),
// Linear sampler
sampler(SamplerBindingType::Filtering),
),
let taa_bind_group_layout = render_device.create_bind_group_layout(
"taa_bind_group_layout",
&BindGroupLayoutEntries::sequential(
ShaderStages::FRAGMENT,
(
// View target (read)
texture_2d(TextureSampleType::Float { filterable: true }),
// TAA History (read)
texture_2d(TextureSampleType::Float { filterable: true }),
// Motion Vectors
texture_2d(TextureSampleType::Float { filterable: true }),
// Depth
texture_depth_2d(),
// Nearest sampler
sampler(SamplerBindingType::NonFiltering),
// Linear sampler
sampler(SamplerBindingType::Filtering),
),
);
),
);
TaaPipeline {
taa_bind_group_layout,
nearest_sampler,
linear_sampler,
}
}
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,

View File

@ -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]

View File

@ -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

View 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();
}
},
);
}
}

View File

@ -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.

View File

@ -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`.

View 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());
}
}

View File

@ -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());

View File

@ -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()

View File

@ -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",
] }

View File

@ -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"

View File

@ -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);

View File

@ -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)
}
})

View File

@ -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);

View File

@ -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`].
///

View File

@ -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> },

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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