Merge branch 'main' into http-asset-sources

This commit is contained in:
François Mockers 2025-06-28 23:16:44 +02:00 committed by GitHub
commit de3e2b62dd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
957 changed files with 37995 additions and 16458 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.12.5"
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
@ -272,7 +272,7 @@ jobs:
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: cargo-bins/cargo-binstall@v1.12.3
- uses: cargo-bins/cargo-binstall@v1.12.5
- name: Install taplo
run: cargo binstall taplo-cli@0.9.3 --locked
- name: Run Taplo
@ -293,7 +293,7 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.32.0
uses: crate-ci/typos@v1.33.1
- 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.86.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",
@ -163,6 +164,8 @@ default = [
"vorbis",
"webgl2",
"x11",
"debug",
"zstd_rust",
]
# Recommended defaults for no_std applications
@ -245,6 +248,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",
@ -291,6 +303,12 @@ bevy_log = ["bevy_internal/bevy_log"]
# Enable input focus subsystem
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
# 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"]
@ -316,6 +334,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"]
@ -364,8 +385,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"]
@ -434,7 +458,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"]
@ -499,7 +523,10 @@ http_source = ["bevy_internal/http_source"]
http_source_cache = ["bevy_internal/http_source_cache"]
# 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"]
@ -540,30 +567,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"
@ -575,7 +613,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]
@ -588,6 +626,17 @@ ureq = { version = "3", 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"
@ -817,6 +866,17 @@ description = "Generates a texture atlas (sprite sheet) from individual sprites"
category = "2D Rendering"
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"
@ -1005,6 +1065,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"
@ -1248,6 +1319,18 @@ description = "Load a cubemap texture onto a cube like a skybox and cycle throug
category = "3D Rendering"
wasm = false
[[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"
path = "examples/3d/spherical_area_lights.rs"
@ -2076,6 +2159,7 @@ wasm = false
name = "dynamic"
path = "examples/ecs/dynamic.rs"
doc-scrape-examples = true
required-features = ["debug"]
[package.metadata.example.dynamic]
name = "Dynamic ECS"
@ -3436,6 +3520,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"
@ -3524,6 +3630,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"
@ -3907,6 +4024,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"
@ -4375,3 +4502,61 @@ 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 = "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

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

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

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

@ -19,7 +19,7 @@ struct MyExtendedMaterial {
quantize_steps: u32,
}
@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

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

View File

@ -10,19 +10,19 @@ 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| {
group.bench_function(format!("size_4_events_{count}"), |b| {
let mut bench = send::Benchmark::<4>::new(count);
b.iter(move || bench.run());
});
}
for count in [100, 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 = send::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| {
group.bench_function(format!("size_512_events_{count}"), |b| {
let mut bench = send::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> {

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,7 +37,6 @@ impl Benchmark {
let mut system = IntoSystem::into_system(query_system);
system.initialize(&mut world);
system.update_archetype_component_access(world.as_unsafe_world_cell());
Self(world, Box::new(system))
}

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

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

@ -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,43 +1,43 @@
[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_log = { path = "../bevy_log", 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"
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"] }

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,

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, 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 _;
@ -95,20 +95,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((
@ -171,6 +163,8 @@ impl Plugin for CasPlugin {
pub struct CasPipeline {
texture_bind_group: BindGroupLayout,
sampler: Sampler,
fullscreen_shader: FullscreenShader,
fragment_shader: Handle<Shader>,
}
impl FromWorld for CasPipeline {
@ -194,6 +188,11 @@ impl FromWorld for CasPipeline {
CasPipeline {
texture_bind_group,
sampler,
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
fragment_shader: load_embedded_asset!(
render_world,
"robust_contrast_adaptive_sharpening.wgsl"
),
}
}
}
@ -215,9 +214,9 @@ 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 {

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, 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 _;
@ -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());
@ -132,6 +130,8 @@ impl Plugin for FxaaPlugin {
pub struct FxaaPipeline {
texture_bind_group: BindGroupLayout,
sampler: Sampler,
fullscreen_shader: FullscreenShader,
fragment_shader: Handle<Shader>,
}
impl FromWorld for FxaaPipeline {
@ -158,6 +158,8 @@ impl FromWorld for FxaaPipeline {
FxaaPipeline {
texture_bind_group,
sampler,
fullscreen_shader: render_world.resource::<FullscreenShader>().clone(),
fragment_shader: load_embedded_asset!(render_world, "fxaa.wgsl"),
}
}
}
@ -181,9 +183,9 @@ 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(),

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

@ -32,7 +32,7 @@
use bevy_app::{App, Plugin};
#[cfg(feature = "smaa_luts")]
use bevy_asset::load_internal_binary_asset;
use bevy_asset::{load_internal_asset, weak_handle, Handle};
use bevy_asset::{embedded_asset, load_embedded_asset, weak_handle, Handle};
#[cfg(not(feature = "smaa_luts"))]
use bevy_core_pipeline::tonemapping::lut_placeholder;
use bevy_core_pipeline::{
@ -50,7 +50,7 @@ use bevy_ecs::{
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
world::{FromWorld, 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::{
@ -64,14 +64,13 @@ use bevy_render::{
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, 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,
},
renderer::{RenderContext, RenderDevice, RenderQueue},
texture::{CachedTexture, GpuImage, TextureCache},
@ -80,8 +79,6 @@ use bevy_render::{
};
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");
@ -147,6 +144,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 +154,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 +164,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,7 +290,7 @@ pub struct SmaaSpecializedRenderPipelines {
impl Plugin for SmaaPlugin {
fn build(&self, app: &mut App) {
// Load the shader.
load_internal_asset!(app, SMAA_SHADER_HANDLE, "smaa.wgsl", Shader::from_wgsl);
embedded_asset!(app, "smaa.wgsl");
// Load the two lookup textures. These are compressed textures in KTX2
// format.
@ -431,18 +434,23 @@ impl FromWorld for SmaaPipelines {
),
);
let shader = load_embedded_asset!(world, "smaa.wgsl");
SmaaPipelines {
edge_detection: SmaaEdgeDetectionPipeline {
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
edge_detection_bind_group_layout,
shader: shader.clone(),
},
blending_weight_calculation: SmaaBlendingWeightCalculationPipeline {
postprocess_bind_group_layout: postprocess_bind_group_layout.clone(),
blending_weight_calculation_bind_group_layout,
shader: shader.clone(),
},
neighborhood_blending: SmaaNeighborhoodBlendingPipeline {
postprocess_bind_group_layout,
neighborhood_blending_bind_group_layout,
shader,
},
}
}
@ -472,13 +480,13 @@ impl SpecializedRenderPipeline for SmaaEdgeDetectionPipeline {
self.edge_detection_bind_group_layout.clone(),
],
vertex: VertexState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs: shader_defs.clone(),
entry_point: "edge_detection_vertex_main".into(),
buffers: vec![],
},
fragment: Some(FragmentState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs,
entry_point: "luma_edge_detection_fragment_main".into(),
targets: vec![Some(ColorTargetState {
@ -532,13 +540,13 @@ impl SpecializedRenderPipeline for SmaaBlendingWeightCalculationPipeline {
self.blending_weight_calculation_bind_group_layout.clone(),
],
vertex: VertexState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs: shader_defs.clone(),
entry_point: "blending_weight_calculation_vertex_main".into(),
buffers: vec![],
},
fragment: Some(FragmentState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs,
entry_point: "blending_weight_calculation_fragment_main".into(),
targets: vec![Some(ColorTargetState {
@ -580,13 +588,13 @@ impl SpecializedRenderPipeline for SmaaNeighborhoodBlendingPipeline {
self.neighborhood_blending_bind_group_layout.clone(),
],
vertex: VertexState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs: shader_defs.clone(),
entry_point: "neighborhood_blending_vertex_main".into(),
buffers: vec![],
},
fragment: Some(FragmentState {
shader: SMAA_SHADER_HANDLE,
shader: self.shader.clone(),
shader_defs,
entry_point: "neighborhood_blending_fragment_main".into(),
targets: vec![Some(ColorTargetState {
@ -695,18 +703,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 +723,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 +739,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,
@ -838,7 +840,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

@ -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, 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::{
@ -15,7 +15,7 @@ use bevy_ecs::{
system::{Commands, Query, Res, ResMut},
world::{FromWorld, 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::{
@ -25,8 +25,8 @@ use bevy_render::{
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,
ColorTargetState, ColorWrites, FilterMode, FragmentState, MultisampleState, Operations,
PipelineCache, PrimitiveState, RenderPassColorAttachment, RenderPassDescriptor,
RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, Shader,
ShaderStages, SpecializedRenderPipeline, SpecializedRenderPipelines, TextureDescriptor,
TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
@ -40,8 +40,6 @@ use bevy_render::{
};
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 +47,7 @@ pub struct TemporalAntiAliasPlugin;
impl Plugin for TemporalAntiAliasPlugin {
fn build(&self, app: &mut App) {
load_internal_asset!(app, TAA_SHADER_HANDLE, "taa.wgsl", Shader::from_wgsl);
embedded_asset!(app, "taa.wgsl");
app.register_type::<TemporalAntiAliasing>();
@ -64,7 +62,7 @@ impl Plugin for TemporalAntiAliasPlugin {
.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),
),
@ -115,7 +113,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 +125,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,6 +238,8 @@ struct TaaPipeline {
taa_bind_group_layout: BindGroupLayout,
nearest_sampler: Sampler,
linear_sampler: Sampler,
fullscreen_shader: FullscreenShader,
fragment_shader: Handle<Shader>,
}
impl FromWorld for TaaPipeline {
@ -287,6 +284,8 @@ impl FromWorld for TaaPipeline {
taa_bind_group_layout,
nearest_sampler,
linear_sampler,
fullscreen_shader: world.resource::<FullscreenShader>().clone(),
fragment_shader: load_embedded_asset!(world, "taa.wgsl"),
}
}
}
@ -317,9 +316,9 @@ 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![
@ -345,16 +344,11 @@ impl SpecializedRenderPipeline for TaaPipeline {
}
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 +358,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 +371,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 +394,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 +420,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

@ -106,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")]
@ -341,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.
@ -352,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();
/// #
@ -360,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
@ -1306,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
@ -1320,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);
@ -1402,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
@ -1412,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]
@ -1480,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},
@ -1577,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]
@ -1846,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();

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

@ -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"]
@ -21,19 +21,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",
] }
@ -56,10 +56,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",
@ -67,7 +67,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.
@ -80,13 +80,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

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

@ -82,7 +82,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)),
}
}

View File

@ -141,8 +141,8 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
#![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]
@ -267,7 +267,7 @@ pub struct AssetPlugin {
/// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid.
///
/// It is strongly discouraged to use [`Allow`](UnapprovedPathMode::Allow) if your
/// app will include scripts or modding support, as it could allow allow arbitrary file
/// app will include scripts or modding support, as it could allow arbitrary file
/// access for malicious code.
///
/// See [`AssetPath::is_unapproved`](crate::AssetPath::is_unapproved)
@ -275,10 +275,10 @@ pub struct AssetPlugin {
pub enum UnapprovedPathMode {
/// Unapproved asset loading is allowed. This is strongly discouraged.
Allow,
/// Fails to load any asset that is is unapproved, unless an override method is used, like
/// Fails to load any asset that is unapproved, unless an override method is used, like
/// [`AssetServer::load_override`].
Deny,
/// Fails to load any asset that is is unapproved.
/// Fails to load any asset that is unapproved.
#[default]
Forbid,
}

View File

@ -12,7 +12,7 @@ use alloc::{
vec::Vec,
};
use atomicow::CowArc;
use bevy_ecs::world::World;
use bevy_ecs::{error::BevyError, world::World};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
use core::any::{Any, TypeId};
@ -34,7 +34,7 @@ pub trait AssetLoader: Send + Sync + 'static {
/// The settings type used by this [`AssetLoader`].
type Settings: Settings + Default + Serialize + for<'a> Deserialize<'a>;
/// The type of [error](`std::error::Error`) which could be encountered by this loader.
type Error: Into<Box<dyn core::error::Error + Send + Sync + 'static>>;
type Error: Into<BevyError>;
/// Asynchronously loads [`AssetLoader::Asset`] (and any other labeled assets) from the bytes provided by [`Reader`].
fn load(
&self,
@ -58,10 +58,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
>;
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>>;
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
fn extensions(&self) -> &[&str];
@ -89,10 +86,7 @@ where
reader: &'a mut dyn Reader,
meta: &'a dyn AssetMetaDyn,
mut load_context: LoadContext<'a>,
) -> BoxedFuture<
'a,
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
> {
) -> BoxedFuture<'a, Result<ErasedLoadedAsset, BevyError>> {
Box::pin(async move {
let settings = meta
.loader_settings()
@ -350,7 +344,7 @@ impl<'a> LoadContext<'a> {
/// Begins a new labeled asset load. Use the returned [`LoadContext`] to load
/// dependencies for the new asset and call [`LoadContext::finish`] to finalize the asset load.
/// When finished, make sure you call [`LoadContext::add_labeled_asset`] to add the results back to the parent
/// When finished, make sure you call [`LoadContext::add_loaded_labeled_asset`] to add the results back to the parent
/// context.
/// Prefer [`LoadContext::labeled_asset_scope`] when possible, which will automatically add
/// the labeled [`LoadContext`] back to the parent context.
@ -366,7 +360,7 @@ impl<'a> LoadContext<'a> {
/// # let load_context: LoadContext = panic!();
/// let mut handles = Vec::new();
/// for i in 0..2 {
/// let mut labeled = load_context.begin_labeled_asset();
/// let labeled = load_context.begin_labeled_asset();
/// handles.push(std::thread::spawn(move || {
/// (i.to_string(), labeled.finish(Image::default()))
/// }));
@ -391,18 +385,18 @@ impl<'a> LoadContext<'a> {
/// [`LoadedAsset`], which is registered under the `label` label.
///
/// This exists to remove the need to manually call [`LoadContext::begin_labeled_asset`] and then manually register the
/// result with [`LoadContext::add_labeled_asset`].
/// result with [`LoadContext::add_loaded_labeled_asset`].
///
/// See [`AssetPath`] for more on labeled assets.
pub fn labeled_asset_scope<A: Asset>(
pub fn labeled_asset_scope<A: Asset, E>(
&mut self,
label: String,
load: impl FnOnce(&mut LoadContext) -> A,
) -> Handle<A> {
load: impl FnOnce(&mut LoadContext) -> Result<A, E>,
) -> Result<Handle<A>, E> {
let mut context = self.begin_labeled_asset();
let asset = load(&mut context);
let asset = load(&mut context)?;
let loaded_asset = context.finish(asset);
self.add_loaded_labeled_asset(label, loaded_asset)
Ok(self.add_loaded_labeled_asset(label, loaded_asset))
}
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
@ -416,7 +410,8 @@ impl<'a> LoadContext<'a> {
///
/// See [`AssetPath`] for more on labeled assets.
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
self.labeled_asset_scope(label, |_| asset)
self.labeled_asset_scope(label, |_| Ok::<_, ()>(asset))
.expect("the closure returns Ok")
}
/// Add a [`LoadedAsset`] that is a "labeled sub asset" of the root path of this load context.

View File

@ -490,7 +490,7 @@ impl<'a> AssetPath<'a> {
}
/// Returns `true` if this [`AssetPath`] points to a file that is
/// outside of it's [`AssetSource`](crate::io::AssetSource) folder.
/// outside of its [`AssetSource`](crate::io::AssetSource) folder.
///
/// ## Example
/// ```

View File

@ -18,16 +18,16 @@ pub struct ReflectAsset {
handle_type_id: TypeId,
assets_resource_type_id: TypeId,
get: fn(&World, UntypedHandle) -> Option<&dyn Reflect>,
get: fn(&World, UntypedAssetId) -> Option<&dyn Reflect>,
// SAFETY:
// - may only be called with an [`UnsafeWorldCell`] which can be used to access the corresponding `Assets<T>` resource mutably
// - may only be used to access **at most one** access at once
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedHandle) -> Option<&mut dyn Reflect>,
get_unchecked_mut: unsafe fn(UnsafeWorldCell<'_>, UntypedAssetId) -> Option<&mut dyn Reflect>,
add: fn(&mut World, &dyn PartialReflect) -> UntypedHandle,
insert: fn(&mut World, UntypedHandle, &dyn PartialReflect),
insert: fn(&mut World, UntypedAssetId, &dyn PartialReflect),
len: fn(&World) -> usize,
ids: for<'w> fn(&'w World) -> Box<dyn Iterator<Item = UntypedAssetId> + 'w>,
remove: fn(&mut World, UntypedHandle) -> Option<Box<dyn Reflect>>,
remove: fn(&mut World, UntypedAssetId) -> Option<Box<dyn Reflect>>,
}
impl ReflectAsset {
@ -42,15 +42,19 @@ impl ReflectAsset {
}
/// Equivalent of [`Assets::get`]
pub fn get<'w>(&self, world: &'w World, handle: UntypedHandle) -> Option<&'w dyn Reflect> {
(self.get)(world, handle)
pub fn get<'w>(
&self,
world: &'w World,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w dyn Reflect> {
(self.get)(world, asset_id.into())
}
/// Equivalent of [`Assets::get_mut`]
pub fn get_mut<'w>(
&self,
world: &'w mut World,
handle: UntypedHandle,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: unique world access
#[expect(
@ -58,7 +62,7 @@ impl ReflectAsset {
reason = "Use of unsafe `Self::get_unchecked_mut()` function."
)]
unsafe {
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), handle)
(self.get_unchecked_mut)(world.as_unsafe_world_cell(), asset_id.into())
}
}
@ -76,8 +80,8 @@ impl ReflectAsset {
/// # let handle_1: UntypedHandle = unimplemented!();
/// # let handle_2: UntypedHandle = unimplemented!();
/// let unsafe_world_cell = world.as_unsafe_world_cell();
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_1).unwrap() };
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, handle_2).unwrap() };
/// let a = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_1).unwrap() };
/// let b = unsafe { reflect_asset.get_unchecked_mut(unsafe_world_cell, &handle_2).unwrap() };
/// // ^ not allowed, two mutable references through the same asset resource, even though the
/// // handles are distinct
///
@ -96,10 +100,10 @@ impl ReflectAsset {
pub unsafe fn get_unchecked_mut<'w>(
&self,
world: UnsafeWorldCell<'w>,
handle: UntypedHandle,
asset_id: impl Into<UntypedAssetId>,
) -> Option<&'w mut dyn Reflect> {
// SAFETY: requirements are deferred to the caller
unsafe { (self.get_unchecked_mut)(world, handle) }
unsafe { (self.get_unchecked_mut)(world, asset_id.into()) }
}
/// Equivalent of [`Assets::add`]
@ -107,13 +111,22 @@ impl ReflectAsset {
(self.add)(world, value)
}
/// Equivalent of [`Assets::insert`]
pub fn insert(&self, world: &mut World, handle: UntypedHandle, value: &dyn PartialReflect) {
(self.insert)(world, handle, value);
pub fn insert(
&self,
world: &mut World,
asset_id: impl Into<UntypedAssetId>,
value: &dyn PartialReflect,
) {
(self.insert)(world, asset_id.into(), value);
}
/// Equivalent of [`Assets::remove`]
pub fn remove(&self, world: &mut World, handle: UntypedHandle) -> Option<Box<dyn Reflect>> {
(self.remove)(world, handle)
pub fn remove(
&self,
world: &mut World,
asset_id: impl Into<UntypedAssetId>,
) -> Option<Box<dyn Reflect>> {
(self.remove)(world, asset_id.into())
}
/// Equivalent of [`Assets::len`]
@ -137,17 +150,17 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
ReflectAsset {
handle_type_id: TypeId::of::<Handle<A>>(),
assets_resource_type_id: TypeId::of::<Assets<A>>(),
get: |world, handle| {
get: |world, asset_id| {
let assets = world.resource::<Assets<A>>();
let asset = assets.get(&handle.typed_debug_checked());
let asset = assets.get(asset_id.typed_debug_checked());
asset.map(|asset| asset as &dyn Reflect)
},
get_unchecked_mut: |world, handle| {
get_unchecked_mut: |world, asset_id| {
// SAFETY: `get_unchecked_mut` must be called with `UnsafeWorldCell` having access to `Assets<A>`,
// and must ensure to only have at most one reference to it live at all times.
#[expect(unsafe_code, reason = "Uses `UnsafeWorldCell::get_resource_mut()`.")]
let assets = unsafe { world.get_resource_mut::<Assets<A>>().unwrap().into_inner() };
let asset = assets.get_mut(&handle.typed_debug_checked());
let asset = assets.get_mut(asset_id.typed_debug_checked());
asset.map(|asset| asset as &mut dyn Reflect)
},
add: |world, value| {
@ -156,11 +169,11 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::add`");
assets.add(value).untyped()
},
insert: |world, handle, value| {
insert: |world, asset_id, value| {
let mut assets = world.resource_mut::<Assets<A>>();
let value: A = FromReflect::from_reflect(value)
.expect("could not call `FromReflect::from_reflect` in `ReflectAsset::set`");
assets.insert(&handle.typed_debug_checked(), value);
assets.insert(asset_id.typed_debug_checked(), value);
},
len: |world| {
let assets = world.resource::<Assets<A>>();
@ -170,9 +183,9 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
let assets = world.resource::<Assets<A>>();
Box::new(assets.ids().map(AssetId::untyped))
},
remove: |world, handle| {
remove: |world, asset_id| {
let mut assets = world.resource_mut::<Assets<A>>();
let value = assets.remove(&handle.typed_debug_checked());
let value = assets.remove(asset_id.typed_debug_checked());
value.map(|value| Box::new(value) as Box<dyn Reflect>)
},
}
@ -200,7 +213,7 @@ impl<A: Asset + FromReflect> FromType<A> for ReflectAsset {
/// let reflect_asset = type_registry.get_type_data::<ReflectAsset>(reflect_handle.asset_type_id()).unwrap();
///
/// let handle = reflect_handle.downcast_handle_untyped(handle.as_any()).unwrap();
/// let value = reflect_asset.get(world, handle).unwrap();
/// let value = reflect_asset.get(world, &handle).unwrap();
/// println!("{value:?}");
/// }
/// ```
@ -210,6 +223,7 @@ pub struct ReflectHandle {
downcast_handle_untyped: fn(&dyn Any) -> Option<UntypedHandle>,
typed: fn(UntypedHandle) -> Box<dyn Reflect>,
}
impl ReflectHandle {
/// The [`TypeId`] of the asset
pub fn asset_type_id(&self) -> TypeId {
@ -247,7 +261,7 @@ mod tests {
use alloc::{string::String, vec::Vec};
use core::any::TypeId;
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset, UntypedHandle};
use crate::{Asset, AssetApp, AssetPlugin, ReflectAsset};
use bevy_app::App;
use bevy_ecs::reflect::AppTypeRegistry;
use bevy_reflect::Reflect;
@ -281,7 +295,7 @@ mod tests {
let handle = reflect_asset.add(app.world_mut(), &value);
// struct is a reserved keyword, so we can't use it here
let strukt = reflect_asset
.get_mut(app.world_mut(), handle)
.get_mut(app.world_mut(), &handle)
.unwrap()
.reflect_mut()
.as_struct()
@ -294,16 +308,12 @@ mod tests {
assert_eq!(reflect_asset.len(app.world()), 1);
let ids: Vec<_> = reflect_asset.ids(app.world()).collect();
assert_eq!(ids.len(), 1);
let id = ids[0];
let fetched_handle = UntypedHandle::Weak(ids[0]);
let asset = reflect_asset
.get(app.world(), fetched_handle.clone_weak())
.unwrap();
let asset = reflect_asset.get(app.world(), id).unwrap();
assert_eq!(asset.downcast_ref::<AssetType>().unwrap().field, "edited");
reflect_asset
.remove(app.world_mut(), fetched_handle)
.unwrap();
reflect_asset.remove(app.world_mut(), id).unwrap();
assert_eq!(reflect_asset.len(app.world()), 0);
}
}

View File

@ -1931,7 +1931,7 @@ pub enum AssetLoadError {
base_path,
label,
all_labels.len(),
all_labels.iter().map(|l| format!("'{}'", l)).collect::<Vec<_>>().join(", "))]
all_labels.iter().map(|l| format!("'{l}'")).collect::<Vec<_>>().join(", "))]
MissingLabel {
base_path: AssetPath<'static>,
label: String,
@ -1945,7 +1945,7 @@ pub enum AssetLoadError {
pub struct AssetLoaderError {
path: AssetPath<'static>,
loader_name: &'static str,
error: Arc<dyn core::error::Error + Send + Sync + 'static>,
error: Arc<BevyError>,
}
impl AssetLoaderError {
@ -1953,6 +1953,14 @@ impl AssetLoaderError {
pub fn path(&self) -> &AssetPath<'static> {
&self.path
}
/// The error the loader reported when attempting to load the asset.
///
/// If you know the type of the error the asset loader returned, you can use
/// [`BevyError::downcast_ref()`] to get it.
pub fn error(&self) -> &BevyError {
&self.error
}
}
/// An error that occurs while resolving an asset added by `add_async`.

View File

@ -1,39 +1,45 @@
[package]
name = "bevy_audio"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Provides audio 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_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
# other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.
rodio = { version = "0.20", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["std"] }
[target.'cfg(target_os = "android")'.dependencies]
cpal = { version = "0.15", optional = true }
[target.'cfg(target_vendor = "apple")'.dependencies]
# NOTE: Explicitly depend on this patch version to fix:
# https://github.com/bevyengine/bevy/issues/18893
coreaudio-sys = { version = "0.2.17", default-features = false }
[target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
rodio = { version = "0.20", default-features = false, features = [
"wasm-bindgen",
] }
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_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

@ -57,6 +57,16 @@ pub struct PlaybackSettings {
/// Optional scale factor applied to the positions of this audio source and the listener,
/// overriding the default value configured on [`AudioPlugin::default_spatial_scale`](crate::AudioPlugin::default_spatial_scale).
pub spatial_scale: Option<SpatialScale>,
/// The point in time in the audio clip where playback should start. If set to `None`, it will
/// play from the beginning of the clip.
///
/// If the playback mode is set to `Loop`, each loop will start from this position.
pub start_position: Option<core::time::Duration>,
/// How long the audio should play before stopping. If set, the clip will play for at most
/// the specified duration. If set to `None`, it will play for as long as it can.
///
/// If the playback mode is set to `Loop`, each loop will last for this duration.
pub duration: Option<core::time::Duration>,
}
impl Default for PlaybackSettings {
@ -81,6 +91,8 @@ impl PlaybackSettings {
muted: false,
spatial: false,
spatial_scale: None,
start_position: None,
duration: None,
};
/// Will play the associated audio source in a loop.
@ -136,6 +148,18 @@ impl PlaybackSettings {
self.spatial_scale = Some(spatial_scale);
self
}
/// Helper to use a custom playback start position.
pub const fn with_start_position(mut self, start_position: core::time::Duration) -> Self {
self.start_position = Some(start_position);
self
}
/// Helper to use a custom playback duration.
pub const fn with_duration(mut self, duration: core::time::Duration) -> Self {
self.duration = Some(duration);
self
}
}
/// Settings for the listener for spatial audio sources.

View File

@ -57,6 +57,7 @@ pub struct PlaybackRemoveMarker;
pub(crate) struct EarPositions<'w, 's> {
pub(crate) query: Query<'w, 's, (Entity, &'static GlobalTransform, &'static SpatialListener)>,
}
impl<'w, 's> EarPositions<'w, 's> {
/// Gets a set of transformed ear positions.
///
@ -156,12 +157,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
}
};
let decoder = audio_source.decoder();
match settings.mode {
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
// custom start position and duration
(Some(start_position), Some(duration)) => sink.append(
decoder
.skip_duration(start_position)
.take_duration(duration)
.repeat_infinite(),
),
// custom start position
(Some(start_position), None) => {
sink.append(decoder.skip_duration(start_position).repeat_infinite());
}
// custom duration
(None, Some(duration)) => {
sink.append(decoder.take_duration(duration).repeat_infinite());
}
// full clip
(None, None) => sink.append(decoder.repeat_infinite()),
},
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
sink.append(audio_source.decoder());
match (settings.start_position, settings.duration) {
(Some(start_position), Some(duration)) => sink.append(
decoder
.skip_duration(start_position)
.take_duration(duration),
),
(Some(start_position), None) => {
sink.append(decoder.skip_duration(start_position));
}
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
(None, None) => sink.append(decoder),
}
}
};
}
let mut sink = SpatialAudioSink::new(sink);
@ -196,12 +234,49 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
}
};
let decoder = audio_source.decoder();
match settings.mode {
PlaybackMode::Loop => sink.append(audio_source.decoder().repeat_infinite()),
PlaybackMode::Loop => match (settings.start_position, settings.duration) {
// custom start position and duration
(Some(start_position), Some(duration)) => sink.append(
decoder
.skip_duration(start_position)
.take_duration(duration)
.repeat_infinite(),
),
// custom start position
(Some(start_position), None) => {
sink.append(decoder.skip_duration(start_position).repeat_infinite());
}
// custom duration
(None, Some(duration)) => {
sink.append(decoder.take_duration(duration).repeat_infinite());
}
// full clip
(None, None) => sink.append(decoder.repeat_infinite()),
},
PlaybackMode::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
sink.append(audio_source.decoder());
match (settings.start_position, settings.duration) {
(Some(start_position), Some(duration)) => sink.append(
decoder
.skip_duration(start_position)
.take_duration(duration),
),
(Some(start_position), None) => {
sink.append(decoder.skip_duration(start_position));
}
(None, Some(duration)) => sink.append(decoder.take_duration(duration)),
(None, None) => sink.append(decoder),
}
}
};
}
let mut sink = AudioSink::new(sink);

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"
)]
//! Audio support for the game engine Bevy

View File

@ -42,6 +42,14 @@ pub trait AudioSinkPlayback {
/// No effect if not paused.
fn play(&self);
/// Returns the position of the sound that's being played.
///
/// This takes into account any speedup or delay applied.
///
/// Example: if you [`set_speed(2.0)`](Self::set_speed) and [`position()`](Self::position) returns *5s*,
/// then the position in the recording is *10s* from its start.
fn position(&self) -> Duration;
/// Attempts to seek to a given position in the current source.
///
/// This blocks between 0 and ~5 milliseconds.
@ -181,6 +189,10 @@ impl AudioSinkPlayback for AudioSink {
self.sink.play();
}
fn position(&self) -> Duration {
self.sink.get_pos()
}
fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
self.sink.try_seek(pos)
}
@ -281,6 +293,10 @@ impl AudioSinkPlayback for SpatialAudioSink {
self.sink.play();
}
fn position(&self) -> Duration {
self.sink.get_pos()
}
fn try_seek(&self, pos: Duration) -> Result<(), SeekError> {
self.sink.try_seek(pos)
}

View File

@ -34,7 +34,7 @@ impl GlobalVolume {
#[derive(Clone, Copy, Debug, Reflect)]
#[reflect(Clone, Debug, PartialEq)]
pub enum Volume {
/// Create a new [`Volume`] from the given volume in linear scale.
/// Create a new [`Volume`] from the given volume in the linear scale.
///
/// In a linear scale, the value `1.0` represents the "normal" volume,
/// meaning the audio is played at its original level. Values greater than
@ -144,7 +144,7 @@ impl Volume {
/// Returns the volume in decibels as a float.
///
/// If the volume is silent / off / muted, i.e. it's underlying linear scale
/// If the volume is silent / off / muted, i.e., its underlying linear scale
/// is `0.0`, this method returns negative infinity.
pub fn to_decibels(&self) -> f32 {
match self {
@ -155,57 +155,95 @@ impl Volume {
/// The silent volume. Also known as "off" or "muted".
pub const SILENT: Self = Volume::Linear(0.0);
}
impl core::ops::Add<Self> for Volume {
type Output = Self;
fn add(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a + b),
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
decibels_to_linear(a) + decibels_to_linear(b),
)),
// {Linear, Decibels} favors the left hand side of the operation by
// first converting the right hand side to the same type as the left
// hand side and then performing the operation.
(Linear(..), Decibels(db)) => self + Linear(decibels_to_linear(db)),
(Decibels(..), Linear(l)) => self + Decibels(linear_to_decibels(l)),
}
/// Increases the volume by the specified percentage.
///
/// This method works in the linear domain, where a 100% increase
/// means doubling the volume (equivalent to +6.02dB).
///
/// # Arguments
/// * `percentage` - The percentage to increase (50.0 means 50% increase)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(1.0);
/// let increased = volume.increase_by_percentage(100.0);
/// assert_eq!(increased.to_linear(), 2.0);
/// ```
pub fn increase_by_percentage(&self, percentage: f32) -> Self {
let factor = 1.0 + (percentage / 100.0);
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::AddAssign<Self> for Volume {
fn add_assign(&mut self, rhs: Self) {
*self = *self + rhs;
/// Decreases the volume by the specified percentage.
///
/// This method works in the linear domain, where a 50% decrease
/// means halving the volume (equivalent to -6.02dB).
///
/// # Arguments
/// * `percentage` - The percentage to decrease (50.0 means 50% decrease)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(1.0);
/// let decreased = volume.decrease_by_percentage(50.0);
/// assert_eq!(decreased.to_linear(), 0.5);
/// ```
pub fn decrease_by_percentage(&self, percentage: f32) -> Self {
let factor = 1.0 - (percentage / 100.0).clamp(0.0, 1.0);
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::Sub<Self> for Volume {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
use Volume::{Decibels, Linear};
match (self, rhs) {
(Linear(a), Linear(b)) => Linear(a - b),
(Decibels(a), Decibels(b)) => Decibels(linear_to_decibels(
decibels_to_linear(a) - decibels_to_linear(b),
)),
// {Linear, Decibels} favors the left hand side of the operation by
// first converting the right hand side to the same type as the left
// hand side and then performing the operation.
(Linear(..), Decibels(db)) => self - Linear(decibels_to_linear(db)),
(Decibels(..), Linear(l)) => self - Decibels(linear_to_decibels(l)),
}
/// Scales the volume to a specific linear factor relative to the current volume.
///
/// This is different from `adjust_by_linear` as it sets the volume to be
/// exactly the factor times the original volume, rather than applying
/// the factor to the current volume.
///
/// # Arguments
/// * `factor` - The scaling factor (2.0 = twice as loud, 0.5 = half as loud)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let volume = Volume::Linear(0.8);
/// let scaled = volume.scale_to_factor(1.25);
/// assert_eq!(scaled.to_linear(), 1.0);
/// ```
pub fn scale_to_factor(&self, factor: f32) -> Self {
Volume::Linear(self.to_linear() * factor)
}
}
impl core::ops::SubAssign<Self> for Volume {
fn sub_assign(&mut self, rhs: Self) {
*self = *self - rhs;
/// Creates a fade effect by interpolating between current volume and target volume.
///
/// This method performs linear interpolation in the linear domain, which
/// provides a more natural-sounding fade effect.
///
/// # Arguments
/// * `target` - The target volume to fade towards
/// * `factor` - The interpolation factor (0.0 = current volume, 1.0 = target volume)
///
/// # Examples
/// ```
/// use bevy_audio::Volume;
///
/// let current = Volume::Linear(1.0);
/// let target = Volume::Linear(0.0);
/// let faded = current.fade_towards(target, 0.5);
/// assert_eq!(faded.to_linear(), 0.5);
/// ```
pub fn fade_towards(&self, target: Volume, factor: f32) -> Self {
let current_linear = self.to_linear();
let target_linear = target.to_linear();
let factor_clamped = factor.clamp(0.0, 1.0);
let interpolated = current_linear + (target_linear - current_linear) * factor_clamped;
Volume::Linear(interpolated)
}
}
@ -306,17 +344,11 @@ mod tests {
assert!(
db_delta.abs() < 1e-2,
"Expected ~{}dB, got {}dB (delta {})",
db,
db_test,
db_delta
"Expected ~{db}dB, got {db_test}dB (delta {db_delta})",
);
assert!(
linear_relative_delta.abs() < 1e-3,
"Expected ~{}, got {} (relative delta {})",
linear,
linear_test,
linear_relative_delta
"Expected ~{linear}, got {linear_test} (relative delta {linear_relative_delta})",
);
}
}
@ -337,8 +369,9 @@ mod tests {
Linear(f32::NEG_INFINITY).to_decibels().is_infinite(),
"Negative infinite linear scale is equivalent to infinite decibels"
);
assert!(
Decibels(f32::NEG_INFINITY).to_linear().abs() == 0.0,
assert_eq!(
Decibels(f32::NEG_INFINITY).to_linear().abs(),
0.0,
"Negative infinity decibels is equivalent to zero linear scale"
);
@ -361,71 +394,89 @@ mod tests {
);
}
#[test]
fn test_increase_by_percentage() {
let volume = Linear(1.0);
// 100% increase should double the volume
let increased = volume.increase_by_percentage(100.0);
assert_eq!(increased.to_linear(), 2.0);
// 50% increase
let increased = volume.increase_by_percentage(50.0);
assert_eq!(increased.to_linear(), 1.5);
}
#[test]
fn test_decrease_by_percentage() {
let volume = Linear(1.0);
// 50% decrease should halve the volume
let decreased = volume.decrease_by_percentage(50.0);
assert_eq!(decreased.to_linear(), 0.5);
// 25% decrease
let decreased = volume.decrease_by_percentage(25.0);
assert_eq!(decreased.to_linear(), 0.75);
// 100% decrease should result in silence
let decreased = volume.decrease_by_percentage(100.0);
assert_eq!(decreased.to_linear(), 0.0);
}
#[test]
fn test_scale_to_factor() {
let volume = Linear(0.8);
let scaled = volume.scale_to_factor(1.25);
assert_eq!(scaled.to_linear(), 1.0);
}
#[test]
fn test_fade_towards() {
let current = Linear(1.0);
let target = Linear(0.0);
// 50% fade should result in 0.5 linear volume
let faded = current.fade_towards(target, 0.5);
assert_eq!(faded.to_linear(), 0.5);
// 0% fade should keep current volume
let faded = current.fade_towards(target, 0.0);
assert_eq!(faded.to_linear(), 1.0);
// 100% fade should reach target volume
let faded = current.fade_towards(target, 1.0);
assert_eq!(faded.to_linear(), 0.0);
}
#[test]
fn test_decibel_math_properties() {
let volume = Linear(1.0);
// Adding 20dB should multiply linear volume by 10
let adjusted = volume * Decibels(20.0);
assert_approx_eq(adjusted, Linear(10.0));
// Subtracting 20dB should divide linear volume by 10
let adjusted = volume / Decibels(20.0);
assert_approx_eq(adjusted, Linear(0.1));
}
fn assert_approx_eq(a: Volume, b: Volume) {
const EPSILON: f32 = 0.0001;
match (a, b) {
(Decibels(a), Decibels(b)) | (Linear(a), Linear(b)) => assert!(
(a - b).abs() < EPSILON,
"Expected {:?} to be approximately equal to {:?}",
a,
b
"Expected {a:?} to be approximately equal to {b:?}",
),
(a, b) => assert!(
(a.to_decibels() - b.to_decibels()).abs() < EPSILON,
"Expected {:?} to be approximately equal to {:?}",
a,
b
"Expected {a:?} to be approximately equal to {b:?}",
),
}
}
#[test]
fn volume_ops_add() {
// Linear to Linear.
assert_approx_eq(Linear(0.5) + Linear(0.5), Linear(1.0));
assert_approx_eq(Linear(0.5) + Linear(0.1), Linear(0.6));
assert_approx_eq(Linear(0.5) + Linear(-0.5), Linear(0.0));
// Decibels to Decibels.
assert_approx_eq(Decibels(0.0) + Decibels(0.0), Decibels(6.0206003));
assert_approx_eq(Decibels(6.0) + Decibels(6.0), Decibels(12.020599));
assert_approx_eq(Decibels(-6.0) + Decibels(-6.0), Decibels(0.020599423));
// {Linear, Decibels} favors the left hand side of the operation.
assert_approx_eq(Linear(0.5) + Decibels(0.0), Linear(1.5));
assert_approx_eq(Decibels(0.0) + Linear(0.5), Decibels(3.521825));
}
#[test]
fn volume_ops_add_assign() {
// Linear to Linear.
let mut volume = Linear(0.5);
volume += Linear(0.5);
assert_approx_eq(volume, Linear(1.0));
}
#[test]
fn volume_ops_sub() {
// Linear to Linear.
assert_approx_eq(Linear(0.5) - Linear(0.5), Linear(0.0));
assert_approx_eq(Linear(0.5) - Linear(0.1), Linear(0.4));
assert_approx_eq(Linear(0.5) - Linear(-0.5), Linear(1.0));
// Decibels to Decibels.
assert_eq!(Decibels(0.0) - Decibels(0.0), Decibels(f32::NEG_INFINITY));
assert_approx_eq(Decibels(6.0) - Decibels(4.0), Decibels(-7.736506));
assert_eq!(Decibels(-6.0) - Decibels(-6.0), Decibels(f32::NEG_INFINITY));
}
#[test]
fn volume_ops_sub_assign() {
// Linear to Linear.
let mut volume = Linear(0.5);
volume -= Linear(0.5);
assert_approx_eq(volume, Linear(0.0));
}
#[test]
fn volume_ops_mul() {
// Linear to Linear.

View File

@ -1,26 +1,26 @@
[package]
name = "bevy_color"
version = "0.16.0-dev"
version = "0.17.0-dev"
edition = "2024"
description = "Types for representing and manipulating color values"
homepage = "https://bevyengine.org"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy", "color"]
rust-version = "1.85.0"
[dependencies]
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false, features = [
"curve",
] }
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, optional = true }
bytemuck = { version = "1", features = ["derive"] }
serde = { version = "1.0", features = [
"derive",
], default-features = false, optional = true }
thiserror = { version = "2", default-features = false }
derive_more = { version = "1", default-features = false, features = ["from"] }
wgpu-types = { version = "24", default-features = false, optional = true }
derive_more = { version = "2", default-features = false, features = ["from"] }
wgpu-types = { version = "25", default-features = false, optional = true }
encase = { version = "0.10", default-features = false, optional = true }
[features]

View File

@ -92,7 +92,7 @@ impl Hsla {
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Hsla::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
pub const fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;

View File

@ -96,7 +96,7 @@ impl Lcha {
/// // Palette with 5 distinct hues
/// let palette = (0..5).map(Lcha::sequential_dispersed).collect::<Vec<_>>();
/// ```
pub fn sequential_dispersed(index: u32) -> Self {
pub const fn sequential_dispersed(index: u32) -> Self {
const FRAC_U32MAX_GOLDEN_RATIO: u32 = 2654435769; // (u32::MAX / Φ) rounded up
const RATIO_360: f32 = 360.0 / u32::MAX as f32;

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"
)]
#![no_std]
@ -262,6 +262,7 @@ macro_rules! impl_componentwise_vector_space {
}
impl bevy_math::VectorSpace for $ty {
type Scalar = f32;
const ZERO: Self = Self {
$($element: 0.0,)+
};

Some files were not shown because too many files have changed in this diff Show More