Merge branch 'main' into 17451-multi-window-camera-ui-example
This commit is contained in:
commit
6e140afe51
8
.github/pull_request_template.md
vendored
8
.github/pull_request_template.md
vendored
@ -36,11 +36,3 @@ println!("My super cool code.");
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Migration Guide
|
||||
|
||||
> This section is optional. If there are no breaking changes, you can delete this section.
|
||||
|
||||
- If this PR is a breaking change (relative to the last release of Bevy), describe how a user might need to migrate their code to support these changes
|
||||
- Simply adding new functionality is not a breaking change.
|
||||
- Fixing behavior that was definitely a bug, rather than a questionable design choice is not a breaking change.
|
||||
|
||||
54
.github/workflows/action-on-PR-labeled.yml
vendored
54
.github/workflows/action-on-PR-labeled.yml
vendored
@ -12,19 +12,63 @@ permissions:
|
||||
pull-requests: 'write'
|
||||
|
||||
jobs:
|
||||
comment-on-breaking-change-label:
|
||||
comment-on-migration-guide-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'M-Needs-Migration-Guide' && !contains(github.event.pull_request.body, '## Migration Guide')
|
||||
if: github.event.label.name == 'M-Needs-Migration-Guide'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
- name: Get changes
|
||||
id: get_changes
|
||||
shell: bash {0}
|
||||
run: |
|
||||
git fetch --depth=1 origin $BASE_SHA
|
||||
git diff --exit-code $BASE_SHA $HEAD_SHA -- ./release-content/migration-guides
|
||||
echo "found_changes=$?" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
if: steps.get_changes.outputs.found_changes == '0'
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `It looks like your PR is a breaking change, but you didn't provide a migration guide.
|
||||
body: `It looks like your PR is a breaking change, but **you didn't provide a migration guide**.
|
||||
|
||||
Could you add some context on what users should update when this change get released in a new version of Bevy?
|
||||
It will be used to help writing the migration guide for the version. Putting it after a \`## Migration Guide\` will help it get automatically picked up by our tooling.`
|
||||
Please review the [instructions for writing migration guides](https://github.com/bevyengine/bevy/tree/main/release-content/migration_guides.md), then expand or revise the content in the [migration guides directory](https://github.com/bevyengine/bevy/tree/main/release-content/migration-guides) to reflect your changes.`
|
||||
})
|
||||
comment-on-release-note-label:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'M-Needs-Release-Note'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
fetch-depth: 2
|
||||
- name: Get changes
|
||||
id: get_changes
|
||||
shell: bash {0}
|
||||
run: |
|
||||
git fetch --depth=1 origin $BASE_SHA
|
||||
git diff --exit-code $BASE_SHA $HEAD_SHA -- ./release-content/release-notes
|
||||
echo "found_changes=$?" >> $GITHUB_OUTPUT
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: actions/github-script@v7
|
||||
if: steps.get_changes.outputs.found_changes == '0'
|
||||
with:
|
||||
script: |
|
||||
await github.rest.issues.createComment({
|
||||
issue_number: context.issue.number,
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
body: `It looks like your PR has been selected for a highlight in the next release blog post, but **you didn't provide a release note**.
|
||||
|
||||
Please review the [instructions for writing release notes](https://github.com/bevyengine/bevy/tree/main/release-content/release_notes.md), then expand or revise the content in the [release notes directory](https://github.com/bevyengine/bevy/tree/main/release-content/release_notes) to showcase your changes.`
|
||||
})
|
||||
|
||||
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -15,6 +15,7 @@ env:
|
||||
# If nightly is breaking CI, modify this variable to target a specific nightly version.
|
||||
NIGHTLY_TOOLCHAIN: nightly
|
||||
RUSTFLAGS: "-D warnings"
|
||||
BINSTALL_VERSION: "v1.12.3"
|
||||
|
||||
concurrency:
|
||||
group: ${{github.workflow}}-${{github.ref}}
|
||||
@ -271,9 +272,9 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: cargo-bins/cargo-binstall@v1.12.3
|
||||
- name: Install taplo
|
||||
run: cargo install taplo-cli --locked
|
||||
run: cargo binstall taplo-cli@0.9.3 --locked
|
||||
- name: Run Taplo
|
||||
id: taplo
|
||||
run: taplo fmt --check --diff
|
||||
@ -292,7 +293,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.30.2
|
||||
uses: crate-ci/typos@v1.32.0
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
|
||||
116
Cargo.toml
116
Cargo.toml
@ -10,7 +10,7 @@ keywords = ["game", "engine", "gamedev", "graphics", "bevy"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
documentation = "https://docs.rs/bevy"
|
||||
rust-version = "1.85.0"
|
||||
rust-version = "1.86.0"
|
||||
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
@ -133,6 +133,7 @@ default = [
|
||||
"bevy_audio",
|
||||
"bevy_color",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_anti_aliasing",
|
||||
"bevy_gilrs",
|
||||
"bevy_gizmos",
|
||||
"bevy_gltf",
|
||||
@ -213,6 +214,13 @@ bevy_core_pipeline = [
|
||||
"bevy_render",
|
||||
]
|
||||
|
||||
# Provides various anti aliasing solutions
|
||||
bevy_anti_aliasing = [
|
||||
"bevy_internal/bevy_anti_aliasing",
|
||||
"bevy_asset",
|
||||
"bevy_render",
|
||||
]
|
||||
|
||||
# Adds gamepad support
|
||||
bevy_gilrs = ["bevy_internal/bevy_gilrs"]
|
||||
|
||||
@ -225,6 +233,7 @@ bevy_pbr = [
|
||||
"bevy_asset",
|
||||
"bevy_render",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_anti_aliasing",
|
||||
]
|
||||
|
||||
# Provides picking functionality
|
||||
@ -242,6 +251,7 @@ bevy_sprite = [
|
||||
"bevy_render",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_color",
|
||||
"bevy_anti_aliasing",
|
||||
]
|
||||
|
||||
# Provides text functionality
|
||||
@ -254,6 +264,7 @@ bevy_ui = [
|
||||
"bevy_text",
|
||||
"bevy_sprite",
|
||||
"bevy_color",
|
||||
"bevy_anti_aliasing",
|
||||
]
|
||||
|
||||
# Windowing layer
|
||||
@ -537,7 +548,7 @@ rand_chacha = "0.3.1"
|
||||
ron = "0.8.0"
|
||||
flate2 = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
serde_json = "1.0.140"
|
||||
bytemuck = "1.7"
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.16.0-dev", default-features = false }
|
||||
# The following explicit dependencies are needed for proc macros to work inside of examples as they are part of the bevy crate itself.
|
||||
@ -558,7 +569,7 @@ hyper = { version = "1", features = ["server", "http1"] }
|
||||
http-body-util = "0.1"
|
||||
anyhow = "1"
|
||||
macro_rules_attribute = "0.2"
|
||||
accesskit = "0.17"
|
||||
accesskit = "0.19"
|
||||
nonmax = "0.5"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
@ -571,6 +582,17 @@ ureq = { version = "3.0.8", features = ["json"] }
|
||||
wasm-bindgen = { version = "0.2" }
|
||||
web-sys = { version = "0.3", features = ["Window"] }
|
||||
|
||||
[[example]]
|
||||
name = "context_menu"
|
||||
path = "examples/usages/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"
|
||||
@ -1578,6 +1600,7 @@ wasm = true
|
||||
name = "headless"
|
||||
path = "examples/app/headless.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_log"]
|
||||
|
||||
[package.metadata.example.headless]
|
||||
name = "Headless"
|
||||
@ -2208,14 +2231,14 @@ category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "fallible_systems"
|
||||
path = "examples/ecs/fallible_systems.rs"
|
||||
name = "error_handling"
|
||||
path = "examples/ecs/error_handling.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_mesh_picking_backend"]
|
||||
|
||||
[package.metadata.example.fallible_systems]
|
||||
name = "Fallible Systems"
|
||||
description = "Systems that return results to handle errors"
|
||||
[package.metadata.example.error_handling]
|
||||
name = "Error handling"
|
||||
description = "How to return and handle errors across the ECS"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
@ -2289,6 +2312,17 @@ description = "Pipe the output of one system into a second, allowing you to hand
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "state_scoped"
|
||||
path = "examples/ecs/state_scoped.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.state_scoped]
|
||||
name = "State Scoped"
|
||||
description = "Shows how to spawn entities that are automatically despawned either when entering or exiting specific game states."
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "system_closure"
|
||||
path = "examples/ecs/system_closure.rs"
|
||||
@ -3328,6 +3362,17 @@ description = "Illustrates creating and updating text"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "text_background_colors"
|
||||
path = "examples/ui/text_background_colors.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.text_background_colors]
|
||||
name = "Text Background Colors"
|
||||
description = "Demonstrates text background colors"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "text_debug"
|
||||
path = "examples/ui/text_debug.rs"
|
||||
@ -3384,6 +3429,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"
|
||||
@ -3483,6 +3550,17 @@ description = "An example for debugging viewport coordinates"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "viewport_node"
|
||||
path = "examples/ui/viewport_node.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.viewport_node]
|
||||
name = "Viewport Node"
|
||||
description = "Demonstrates how to create a viewport node with picking support"
|
||||
category = "UI (User Interface)"
|
||||
wasm = true
|
||||
|
||||
# Window
|
||||
[[example]]
|
||||
name = "clear_color"
|
||||
@ -4312,3 +4390,25 @@ name = "`no_std` Compatible Library"
|
||||
description = "Example library compatible with `std` and `no_std` targets"
|
||||
category = "Embedded"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "extended_material_bindless"
|
||||
path = "examples/shader/extended_material_bindless.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.extended_material_bindless]
|
||||
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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
),
|
||||
},
|
||||
entities: {
|
||||
4294967296: (
|
||||
4294967297: (
|
||||
components: {
|
||||
"bevy_ecs::name::Name": "joe",
|
||||
"bevy_transform::components::global_transform::GlobalTransform": ((1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0)),
|
||||
@ -23,7 +23,7 @@
|
||||
),
|
||||
},
|
||||
),
|
||||
4294967297: (
|
||||
4294967298: (
|
||||
components: {
|
||||
"scene::ComponentA": (
|
||||
x: 3.0,
|
||||
|
||||
@ -6,7 +6,8 @@ struct VertexOutput {
|
||||
}
|
||||
|
||||
struct CustomMaterial {
|
||||
time: f32,
|
||||
// Needed for 16-bit alignment on WebGL2
|
||||
time: vec4<f32>,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
@ -15,5 +16,5 @@ struct CustomMaterial {
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return make_polka_dots(mesh.uv, material.time);
|
||||
return make_polka_dots(mesh.uv, material.time.x);
|
||||
}
|
||||
107
assets/shaders/extended_material_bindless.wgsl
Normal file
107
assets/shaders/extended_material_bindless.wgsl
Normal file
@ -0,0 +1,107 @@
|
||||
// The shader that goes with `extended_material_bindless.rs`.
|
||||
//
|
||||
// This code demonstrates how to write shaders that are compatible with both
|
||||
// bindless and non-bindless mode. See the `#ifdef BINDLESS` blocks.
|
||||
|
||||
#import bevy_pbr::{
|
||||
forward_io::{FragmentOutput, VertexOutput},
|
||||
mesh_bindings::mesh,
|
||||
pbr_fragment::pbr_input_from_standard_material,
|
||||
pbr_functions::{apply_pbr_lighting, main_pass_post_lighting_processing},
|
||||
}
|
||||
#import bevy_render::bindless::{bindless_samplers_filtering, bindless_textures_2d}
|
||||
|
||||
#ifdef BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::{material_array, material_indices}
|
||||
#else // BINDLESS
|
||||
#import bevy_pbr::pbr_bindings::material
|
||||
#endif // BINDLESS
|
||||
|
||||
// Stores the indices of the bindless resources in the bindless resource arrays,
|
||||
// for the `ExampleBindlessExtension` fields.
|
||||
struct ExampleBindlessExtendedMaterialIndices {
|
||||
// The index of the `ExampleBindlessExtendedMaterial` data in
|
||||
// `example_extended_material`.
|
||||
material: u32,
|
||||
// The index of the texture we're going to modulate the base color with in
|
||||
// the `bindless_textures_2d` array.
|
||||
modulate_texture: u32,
|
||||
// The index of the sampler we're going to sample the modulated texture with
|
||||
// in the `bindless_samplers_filtering` array.
|
||||
modulate_texture_sampler: u32,
|
||||
}
|
||||
|
||||
// Plain data associated with this example material.
|
||||
struct ExampleBindlessExtendedMaterial {
|
||||
// The color that we multiply the base color, base color texture, and
|
||||
// modulated texture with.
|
||||
modulate_color: vec4<f32>,
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
|
||||
// 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:
|
||||
array<ExampleBindlessExtendedMaterialIndices>;
|
||||
// An array that holds the `ExampleBindlessExtendedMaterial` plain old data,
|
||||
// indexed by `ExampleBindlessExtendedMaterialIndices.material`.
|
||||
@group(2) @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;
|
||||
|
||||
#endif // BINDLESS
|
||||
|
||||
@fragment
|
||||
fn fragment(
|
||||
in: VertexOutput,
|
||||
@builtin(front_facing) is_front: bool,
|
||||
) -> FragmentOutput {
|
||||
#ifdef BINDLESS
|
||||
// Fetch the material slot. We'll use this in turn to fetch the bindless
|
||||
// indices from `example_extended_material_indices`.
|
||||
let slot = mesh[in.instance_index].material_and_lightmap_bind_group_slot & 0xffffu;
|
||||
#endif // BINDLESS
|
||||
|
||||
// Generate a `PbrInput` struct from the `StandardMaterial` bindings.
|
||||
var pbr_input = pbr_input_from_standard_material(in, is_front);
|
||||
|
||||
// Calculate the UV for the texture we're about to sample.
|
||||
#ifdef BINDLESS
|
||||
let uv_transform = material_array[material_indices[slot].material].uv_transform;
|
||||
#else // BINDLESS
|
||||
let uv_transform = material.uv_transform;
|
||||
#endif // BINDLESS
|
||||
let uv = (uv_transform * vec3(in.uv, 1.0)).xy;
|
||||
|
||||
// Multiply the base color by the `modulate_texture` and `modulate_color`.
|
||||
#ifdef BINDLESS
|
||||
// Notice how we fetch the texture, sampler, and plain extended material
|
||||
// data from the appropriate arrays.
|
||||
pbr_input.material.base_color *= textureSample(
|
||||
bindless_textures_2d[example_extended_material_indices[slot].modulate_texture],
|
||||
bindless_samplers_filtering[
|
||||
example_extended_material_indices[slot].modulate_texture_sampler
|
||||
],
|
||||
uv
|
||||
) * example_extended_material[example_extended_material_indices[slot].material].modulate_color;
|
||||
#else // BINDLESS
|
||||
pbr_input.material.base_color *= textureSample(modulate_texture, modulate_sampler, uv) *
|
||||
example_extended_material.modulate_color;
|
||||
#endif // BINDLESS
|
||||
|
||||
var out: FragmentOutput;
|
||||
// Apply lighting.
|
||||
out.color = apply_pbr_lighting(pbr_input);
|
||||
// Apply in-shader post processing (fog, alpha-premultiply, and also
|
||||
// tonemapping, debanding if the camera is non-HDR). Note this does not
|
||||
// include fullscreen postprocessing effects like bloom.
|
||||
out.color = main_pass_post_lighting_processing(pbr_input, out.color);
|
||||
return out;
|
||||
}
|
||||
@ -12,9 +12,9 @@ fn hash(value: u32) -> u32 {
|
||||
var state = value;
|
||||
state = state ^ 2747636419u;
|
||||
state = state * 2654435769u;
|
||||
state = state ^ state >> 16u;
|
||||
state = state ^ (state >> 16u);
|
||||
state = state * 2654435769u;
|
||||
state = state ^ state >> 16u;
|
||||
state = state ^ (state >> 16u);
|
||||
state = state * 2654435769u;
|
||||
return state;
|
||||
}
|
||||
@ -27,7 +27,7 @@ fn randomFloat(value: u32) -> f32 {
|
||||
fn init(@builtin(global_invocation_id) invocation_id: vec3<u32>, @builtin(num_workgroups) num_workgroups: vec3<u32>) {
|
||||
let location = vec2<i32>(i32(invocation_id.x), i32(invocation_id.y));
|
||||
|
||||
let randomNumber = randomFloat(invocation_id.y << 16u | invocation_id.x);
|
||||
let randomNumber = randomFloat((invocation_id.y << 16u) | invocation_id.x);
|
||||
let alive = randomNumber > 0.9;
|
||||
let color = vec4<f32>(f32(alive));
|
||||
|
||||
|
||||
@ -40,5 +40,5 @@ fn make_polka_dots(pos: vec2<f32>, time: f32) -> vec4<f32> {
|
||||
is_dot = step(dist_from_center, 0.3 + wave_normalized * 0.2);
|
||||
}
|
||||
|
||||
return vec4<f32>(dot_color * is_dot, is_dot);
|
||||
return vec4<f32>(dot_color * is_dot, 1.0);
|
||||
}
|
||||
BIN
assets/textures/food_kenney.png
Normal file
BIN
assets/textures/food_kenney.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 66 KiB |
@ -24,7 +24,7 @@ bevy_reflect = { path = "../crates/bevy_reflect", features = ["functions"] }
|
||||
bevy_render = { path = "../crates/bevy_render" }
|
||||
bevy_tasks = { path = "../crates/bevy_tasks" }
|
||||
bevy_utils = { path = "../crates/bevy_utils" }
|
||||
bevy_platform_support = { path = "../crates/bevy_platform_support", default-features = false, features = [
|
||||
bevy_platform = { path = "../crates/bevy_platform", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
@ -32,6 +32,7 @@ bevy_platform_support = { path = "../crates/bevy_platform_support", default-feat
|
||||
glam = "0.29"
|
||||
rand = "0.8"
|
||||
rand_chacha = "0.3"
|
||||
nonmax = { version = "0.5", default-features = false }
|
||||
|
||||
# Make `bevy_render` compile on Linux with x11 windowing. x11 vs. Wayland does not matter here
|
||||
# because the benches do not actually open any windows.
|
||||
|
||||
@ -25,10 +25,10 @@ cargo bench -p benches -- name_fragment
|
||||
cargo bench -p benches -- --list
|
||||
|
||||
# Save a baseline to be compared against later.
|
||||
cargo bench -p benches --save-baseline before
|
||||
cargo bench -p benches -- --save-baseline before
|
||||
|
||||
# Compare the current benchmarks against a baseline to find performance gains and regressions.
|
||||
cargo bench -p benches --baseline before
|
||||
cargo bench -p benches -- --baseline before
|
||||
```
|
||||
|
||||
## Criterion
|
||||
|
||||
@ -51,8 +51,7 @@ fn add_archetypes(world: &mut World, count: u16) {
|
||||
|
||||
pub fn no_archetypes(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("no_archetypes");
|
||||
for i in 0..=5 {
|
||||
let system_count = i * 20;
|
||||
for system_count in [0, 10, 100] {
|
||||
let (mut world, mut schedule) = setup(system_count);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("system_count", system_count),
|
||||
@ -69,7 +68,7 @@ pub fn no_archetypes(criterion: &mut Criterion) {
|
||||
pub fn added_archetypes(criterion: &mut Criterion) {
|
||||
const SYSTEM_COUNT: usize = 100;
|
||||
let mut group = criterion.benchmark_group("added_archetypes");
|
||||
for archetype_count in [100, 200, 500, 1000, 2000, 5000, 10000] {
|
||||
for archetype_count in [100, 1_000, 10_000] {
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("archetype_count", archetype_count),
|
||||
&archetype_count,
|
||||
|
||||
@ -155,7 +155,7 @@ fn add_archetypes(world: &mut World, count: u16) {
|
||||
|
||||
fn empty_archetypes(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("empty_archetypes");
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
for archetype_count in [10, 100, 1_000, 10_000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_systems(iter);
|
||||
});
|
||||
@ -186,7 +186,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
},
|
||||
);
|
||||
}
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
for archetype_count in [10, 100, 1_000, 10_000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_systems(for_each);
|
||||
});
|
||||
@ -217,7 +217,7 @@ fn empty_archetypes(criterion: &mut Criterion) {
|
||||
},
|
||||
);
|
||||
}
|
||||
for archetype_count in [10, 100, 500, 1000, 2000, 5000, 10000] {
|
||||
for archetype_count in [10, 100, 1_000, 10_000] {
|
||||
let (mut world, mut schedule) = setup(true, |schedule| {
|
||||
schedule.add_systems(par_for_each);
|
||||
});
|
||||
|
||||
@ -155,7 +155,7 @@ fn bench_clone_hierarchy<B: Bundle + Default + GetTypeRegistration>(
|
||||
|
||||
for parent in current_hierarchy_level {
|
||||
for _ in 0..children {
|
||||
let child_id = world.spawn((B::default(), ChildOf { parent })).id();
|
||||
let child_id = world.spawn((B::default(), ChildOf(parent))).id();
|
||||
hierarchy_level.push(child_id);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,19 +9,19 @@ fn send(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("events_send");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_4_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<4>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_16_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<16>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
});
|
||||
}
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_512_events_{}", count), |b| {
|
||||
let mut bench = send::Benchmark::<512>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
@ -34,19 +34,19 @@ fn iter(c: &mut Criterion) {
|
||||
let mut group = c.benchmark_group("events_iter");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
for count in [100, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
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, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
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, 1000, 10000, 50000] {
|
||||
for count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("size_512_events_{}", count), |b| {
|
||||
let mut bench = iter::Benchmark::<512>::new(count);
|
||||
b.iter(move || bench.run());
|
||||
|
||||
@ -45,7 +45,6 @@ pub fn heavy_compute(c: &mut Criterion) {
|
||||
|
||||
let mut system = IntoSystem::into_system(sys);
|
||||
system.initialize(&mut world);
|
||||
system.update_archetype_component_access(world.as_unsafe_world_cell());
|
||||
|
||||
b.iter(move || system.run((), &mut world));
|
||||
});
|
||||
|
||||
@ -37,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))
|
||||
}
|
||||
|
||||
|
||||
@ -17,15 +17,14 @@ pub fn run_condition_yes(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(3));
|
||||
fn empty() {}
|
||||
for amount in 0..21 {
|
||||
for amount in [10, 100, 1_000] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(empty.run_if(yes));
|
||||
for _ in 0..amount {
|
||||
for _ in 0..(amount / 5) {
|
||||
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(yes));
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -40,15 +39,14 @@ pub fn run_condition_no(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(3));
|
||||
fn empty() {}
|
||||
for amount in 0..21 {
|
||||
for amount in [10, 100, 1_000] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(empty.run_if(no));
|
||||
for _ in 0..amount {
|
||||
for _ in 0..(amount / 5) {
|
||||
schedule.add_systems((empty, empty, empty, empty, empty).distributive_run_if(no));
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -70,17 +68,16 @@ pub fn run_condition_yes_with_query(criterion: &mut Criterion) {
|
||||
fn yes_with_query(query: Single<&TestBool>) -> bool {
|
||||
query.0
|
||||
}
|
||||
for amount in 0..21 {
|
||||
for amount in [10, 100, 1_000] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(empty.run_if(yes_with_query));
|
||||
for _ in 0..amount {
|
||||
for _ in 0..(amount / 5) {
|
||||
schedule.add_systems(
|
||||
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_query),
|
||||
);
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -99,17 +96,16 @@ pub fn run_condition_yes_with_resource(criterion: &mut Criterion) {
|
||||
fn yes_with_resource(res: Res<TestBool>) -> bool {
|
||||
res.0
|
||||
}
|
||||
for amount in 0..21 {
|
||||
for amount in [10, 100, 1_000] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems(empty.run_if(yes_with_resource));
|
||||
for _ in 0..amount {
|
||||
for _ in 0..(amount / 5) {
|
||||
schedule.add_systems(
|
||||
(empty, empty, empty, empty, empty).distributive_run_if(yes_with_resource),
|
||||
);
|
||||
}
|
||||
// run once to initialize systems
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", 5 * amount + 1), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
|
||||
@ -20,25 +20,25 @@ pub fn empty_systems(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(3));
|
||||
fn empty() {}
|
||||
for amount in 0..5 {
|
||||
for amount in [0, 2, 4] {
|
||||
let mut schedule = Schedule::default();
|
||||
for _ in 0..amount {
|
||||
schedule.add_systems(empty);
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", amount), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
});
|
||||
}
|
||||
for amount in 1..21 {
|
||||
for amount in [10, 100, 1_000] {
|
||||
let mut schedule = Schedule::default();
|
||||
for _ in 0..amount {
|
||||
for _ in 0..(amount / 5) {
|
||||
schedule.add_systems((empty, empty, empty, empty, empty));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(format!("{:03}_systems", 5 * amount), |bencher| {
|
||||
group.bench_function(format!("{}_systems", amount), |bencher| {
|
||||
bencher.iter(|| {
|
||||
schedule.run(&mut world);
|
||||
});
|
||||
@ -67,23 +67,21 @@ pub fn busy_systems(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("busy_systems");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(3));
|
||||
for entity_bunches in 1..6 {
|
||||
for entity_bunches in [1, 3, 5] {
|
||||
world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0))));
|
||||
world.spawn_batch((0..4 * ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0))));
|
||||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0))));
|
||||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), E(0.0))));
|
||||
for system_amount in 0..5 {
|
||||
for system_amount in [3, 9, 15] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems((ab, cd, ce));
|
||||
for _ in 0..system_amount {
|
||||
for _ in 0..(system_amount / 3) {
|
||||
schedule.add_systems((ab, cd, ce));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
entity_bunches,
|
||||
3 * system_amount + 3
|
||||
entity_bunches, system_amount
|
||||
),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
@ -119,22 +117,20 @@ pub fn contrived(criterion: &mut Criterion) {
|
||||
let mut group = criterion.benchmark_group("contrived");
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(3));
|
||||
for entity_bunches in 1..6 {
|
||||
for entity_bunches in [1, 3, 5] {
|
||||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0), C(0.0), D(0.0))));
|
||||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (A(0.0), B(0.0))));
|
||||
world.spawn_batch((0..ENTITY_BUNCH).map(|_| (C(0.0), D(0.0))));
|
||||
for system_amount in 0..5 {
|
||||
for system_amount in [3, 9, 15] {
|
||||
let mut schedule = Schedule::default();
|
||||
schedule.add_systems((s_0, s_1, s_2));
|
||||
for _ in 0..system_amount {
|
||||
for _ in 0..(system_amount / 3) {
|
||||
schedule.add_systems((s_0, s_1, s_2));
|
||||
}
|
||||
schedule.run(&mut world);
|
||||
group.bench_function(
|
||||
format!(
|
||||
"{:02}x_entities_{:02}_systems",
|
||||
entity_bunches,
|
||||
3 * system_amount + 3
|
||||
entity_bunches, system_amount
|
||||
),
|
||||
|bencher| {
|
||||
bencher.iter(|| {
|
||||
|
||||
@ -137,6 +137,7 @@ pub fn empty_schedule_run(criterion: &mut Criterion) {
|
||||
});
|
||||
|
||||
let mut schedule = Schedule::default();
|
||||
#[expect(deprecated, reason = "We still need to test/bench this.")]
|
||||
schedule.set_executor_kind(bevy_ecs::schedule::ExecutorKind::Simple);
|
||||
group.bench_function("Simple", |bencher| {
|
||||
bencher.iter(|| schedule.run(app.world_mut()));
|
||||
|
||||
@ -36,7 +36,7 @@ pub fn spawn_commands(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in (1..5).map(|i| i * 2 * 1000) {
|
||||
for entity_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |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!("{}_entities", entity_count), |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]);
|
||||
|
||||
@ -92,28 +117,6 @@ pub fn insert_commands(criterion: &mut Criterion) {
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
group.bench_function("insert_or_spawn_batch", |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
let mut entities = Vec::new();
|
||||
for _ in 0..entity_count {
|
||||
entities.push(world.spawn_empty().id());
|
||||
}
|
||||
|
||||
bencher.iter(|| {
|
||||
let mut commands = Commands::new(&mut command_queue, &world);
|
||||
let mut values = Vec::with_capacity(entity_count);
|
||||
for entity in &entities {
|
||||
values.push((*entity, (Matrix::default(), Vec3::default())));
|
||||
}
|
||||
#[expect(
|
||||
deprecated,
|
||||
reason = "This needs to be supported for now, and therefore still needs the benchmark."
|
||||
)]
|
||||
commands.insert_or_spawn_batch(values);
|
||||
command_queue.apply(&mut world);
|
||||
});
|
||||
});
|
||||
group.bench_function("insert_batch", |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
@ -158,7 +161,7 @@ pub fn fake_commands(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for command_count in (1..5).map(|i| i * 2 * 1000) {
|
||||
for command_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_commands", command_count), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
@ -203,7 +206,7 @@ pub fn sized_commands_impl<T: Default + Command>(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for command_count in (1..5).map(|i| i * 2 * 1000) {
|
||||
for command_count in [100, 1_000, 10_000] {
|
||||
group.bench_function(format!("{}_commands", command_count), |bencher| {
|
||||
let mut world = World::default();
|
||||
let mut command_queue = CommandQueue::default();
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use criterion::Criterion;
|
||||
use criterion::{BatchSize, Criterion};
|
||||
use glam::*;
|
||||
|
||||
#[derive(Component)]
|
||||
@ -12,19 +12,24 @@ pub fn world_despawn(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = World::default();
|
||||
for _ in 0..entity_count {
|
||||
world.spawn((A(Mat4::default()), B(Vec4::default())));
|
||||
}
|
||||
|
||||
let ents = world.iter_entities().map(|e| e.id()).collect::<Vec<_>>();
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
bencher.iter(|| {
|
||||
(world, ents)
|
||||
},
|
||||
|(world, ents)| {
|
||||
ents.iter().for_each(|e| {
|
||||
world.despawn(*e);
|
||||
});
|
||||
});
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use criterion::Criterion;
|
||||
use criterion::{BatchSize, Criterion};
|
||||
use glam::*;
|
||||
|
||||
#[derive(Component)]
|
||||
@ -12,23 +12,31 @@ pub fn world_despawn_recursive(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
bencher.iter_batched_ref(
|
||||
|| {
|
||||
let mut world = World::default();
|
||||
for _ in 0..entity_count {
|
||||
let parent_ents = (0..entity_count)
|
||||
.map(|_| {
|
||||
world
|
||||
.spawn((A(Mat4::default()), B(Vec4::default())))
|
||||
.with_children(|parent| {
|
||||
parent.spawn((A(Mat4::default()), B(Vec4::default())));
|
||||
});
|
||||
}
|
||||
})
|
||||
.id()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let ents = world.iter_entities().map(|e| e.id()).collect::<Vec<_>>();
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
bencher.iter(|| {
|
||||
ents.iter().for_each(|e| {
|
||||
world.entity_mut(*e).despawn();
|
||||
});
|
||||
(world, parent_ents)
|
||||
},
|
||||
|(world, parent_ents)| {
|
||||
parent_ents.iter().for_each(|e| {
|
||||
world.despawn(*e);
|
||||
});
|
||||
},
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use bevy_ecs::entity::{hash_set::EntityHashSet, Entity};
|
||||
use bevy_ecs::entity::{Entity, EntityGeneration, EntityHashSet};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
@ -17,10 +17,14 @@ fn make_entity(rng: &mut impl Rng, size: usize) -> Entity {
|
||||
let generation = 1.0 + -(1.0 - x).log2() * 2.0;
|
||||
|
||||
// this is not reliable, but we're internal so a hack is ok
|
||||
let bits = ((generation as u64) << 32) | (id as u64);
|
||||
let id = id as u64 + 1;
|
||||
let bits = ((generation as u64) << 32) | id;
|
||||
let e = Entity::from_bits(bits);
|
||||
assert_eq!(e.index(), id as u32);
|
||||
assert_eq!(e.generation(), generation as u32);
|
||||
assert_eq!(e.index(), !(id as u32));
|
||||
assert_eq!(
|
||||
e.generation(),
|
||||
EntityGeneration::FIRST.after_versions(generation as u32)
|
||||
);
|
||||
e
|
||||
}
|
||||
|
||||
|
||||
@ -17,6 +17,7 @@ criterion_group!(
|
||||
benches,
|
||||
empty_commands,
|
||||
spawn_commands,
|
||||
nonempty_spawn_commands,
|
||||
insert_commands,
|
||||
fake_commands,
|
||||
zero_sized_commands,
|
||||
|
||||
@ -12,7 +12,7 @@ pub fn world_spawn(criterion: &mut Criterion) {
|
||||
group.warm_up_time(core::time::Duration::from_millis(500));
|
||||
group.measurement_time(core::time::Duration::from_secs(4));
|
||||
|
||||
for entity_count in (0..5).map(|i| 10_u32.pow(i)) {
|
||||
for entity_count in [1, 100, 10_000] {
|
||||
group.bench_function(format!("{}_entities", entity_count), |bencher| {
|
||||
let mut world = World::default();
|
||||
bencher.iter(|| {
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
use core::hint::black_box;
|
||||
use nonmax::NonMaxU32;
|
||||
|
||||
use bevy_ecs::{
|
||||
bundle::{Bundle, NoBundleEffect},
|
||||
component::Component,
|
||||
entity::Entity,
|
||||
entity::{Entity, EntityRow},
|
||||
system::{Query, SystemState},
|
||||
world::World,
|
||||
};
|
||||
@ -53,7 +54,9 @@ pub fn world_entity(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
black_box(world.entity(entity));
|
||||
}
|
||||
});
|
||||
@ -74,7 +77,9 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(world.get::<Table>(entity).is_some());
|
||||
}
|
||||
});
|
||||
@ -84,7 +89,9 @@ pub fn world_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(world.get::<Sparse>(entity).is_some());
|
||||
}
|
||||
});
|
||||
@ -106,7 +113,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(query.get(&world, entity).is_ok());
|
||||
}
|
||||
});
|
||||
@ -131,7 +140,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(query.get(&world, entity).is_ok());
|
||||
}
|
||||
});
|
||||
@ -142,7 +153,9 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
let entity =
|
||||
// SAFETY: Range is exclusive.
|
||||
Entity::from_raw(EntityRow::new(unsafe { NonMaxU32::new_unchecked(i) }));
|
||||
assert!(query.get(&world, entity).is_ok());
|
||||
}
|
||||
});
|
||||
@ -169,7 +182,10 @@ pub fn world_query_get(criterion: &mut Criterion) {
|
||||
|
||||
bencher.iter(|| {
|
||||
for i in 0..entity_count {
|
||||
let entity = Entity::from_raw(i);
|
||||
// SAFETY: Range is exclusive.
|
||||
let entity = Entity::from_raw(EntityRow::new(unsafe {
|
||||
NonMaxU32::new_unchecked(i)
|
||||
}));
|
||||
assert!(query.get(&world, entity).is_ok());
|
||||
}
|
||||
});
|
||||
|
||||
@ -37,7 +37,7 @@ fn create_mesh(vertices_per_side: u32) -> SimpleMesh {
|
||||
for p in 0..vertices_per_side.pow(2) {
|
||||
let (x, z) = p_to_xz_norm(p, vertices_per_side);
|
||||
|
||||
// Push a new vertice to the mesh. We translate all vertices so the final square is
|
||||
// Push a new vertex to the mesh. We translate all vertices so the final square is
|
||||
// centered at (0, 0), instead of (0.5, 0.5).
|
||||
positions.push([x - 0.5, 0.0, z - 0.5]);
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
use core::{fmt::Write, hint::black_box, iter, time::Duration};
|
||||
|
||||
use benches::bench;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{DynamicMap, Map};
|
||||
use criterion::{
|
||||
criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup, BenchmarkId,
|
||||
|
||||
96
benches/benches/bevy_render/compute_normals.rs
Normal file
96
benches/benches/bevy_render/compute_normals.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use core::hint::black_box;
|
||||
|
||||
use criterion::{criterion_group, Criterion};
|
||||
use rand::random;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use bevy_render::{
|
||||
mesh::{Indices, Mesh, PrimitiveTopology},
|
||||
render_asset::RenderAssetUsages,
|
||||
};
|
||||
|
||||
const GRID_SIZE: usize = 256;
|
||||
|
||||
fn compute_normals(c: &mut Criterion) {
|
||||
let indices = Indices::U32(
|
||||
(0..GRID_SIZE - 1)
|
||||
.flat_map(|i| std::iter::repeat(i).zip(0..GRID_SIZE - 1))
|
||||
.flat_map(|(i, j)| {
|
||||
let tl = ((GRID_SIZE * j) + i) as u32;
|
||||
let tr = tl + 1;
|
||||
let bl = ((GRID_SIZE * (j + 1)) + i) as u32;
|
||||
let br = bl + 1;
|
||||
[tl, bl, tr, tr, bl, br]
|
||||
})
|
||||
.collect(),
|
||||
);
|
||||
|
||||
let new_mesh = || {
|
||||
let positions = (0..GRID_SIZE)
|
||||
.flat_map(|i| std::iter::repeat(i).zip(0..GRID_SIZE))
|
||||
.map(|(i, j)| [i as f32, j as f32, random::<f32>()])
|
||||
.collect::<Vec<_>>();
|
||||
Mesh::new(
|
||||
PrimitiveTopology::TriangleList,
|
||||
RenderAssetUsages::MAIN_WORLD,
|
||||
)
|
||||
.with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, positions)
|
||||
.with_inserted_indices(indices.clone())
|
||||
};
|
||||
|
||||
c.bench_function("smooth_normals", |b| {
|
||||
b.iter_custom(|iters| {
|
||||
let mut total = Duration::default();
|
||||
for _ in 0..iters {
|
||||
let mut mesh = new_mesh();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
let start = Instant::now();
|
||||
mesh.compute_smooth_normals();
|
||||
let end = Instant::now();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
total += end.duration_since(start);
|
||||
}
|
||||
total
|
||||
});
|
||||
});
|
||||
|
||||
c.bench_function("face_weighted_normals", |b| {
|
||||
b.iter_custom(|iters| {
|
||||
let mut total = Duration::default();
|
||||
for _ in 0..iters {
|
||||
let mut mesh = new_mesh();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
let start = Instant::now();
|
||||
mesh.compute_smooth_normals();
|
||||
let end = Instant::now();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
total += end.duration_since(start);
|
||||
}
|
||||
total
|
||||
});
|
||||
});
|
||||
|
||||
let new_mesh = || {
|
||||
new_mesh()
|
||||
.with_duplicated_vertices()
|
||||
.with_computed_flat_normals()
|
||||
};
|
||||
|
||||
c.bench_function("flat_normals", |b| {
|
||||
b.iter_custom(|iters| {
|
||||
let mut total = Duration::default();
|
||||
for _ in 0..iters {
|
||||
let mut mesh = new_mesh();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
let start = Instant::now();
|
||||
mesh.compute_flat_normals();
|
||||
let end = Instant::now();
|
||||
black_box(mesh.attribute(Mesh::ATTRIBUTE_NORMAL));
|
||||
total += end.duration_since(start);
|
||||
}
|
||||
total
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, compute_normals);
|
||||
@ -1,6 +1,11 @@
|
||||
use criterion::criterion_main;
|
||||
|
||||
mod compute_normals;
|
||||
mod render_layers;
|
||||
mod torus;
|
||||
|
||||
criterion_main!(render_layers::benches, torus::benches);
|
||||
criterion_main!(
|
||||
render_layers::benches,
|
||||
compute_normals::benches,
|
||||
torus::benches
|
||||
);
|
||||
|
||||
@ -41,7 +41,6 @@ disallowed-methods = [
|
||||
{ path = "f32::asinh", reason = "use bevy_math::ops::asinh instead for libm determinism" },
|
||||
{ path = "f32::acosh", reason = "use bevy_math::ops::acosh instead for libm determinism" },
|
||||
{ path = "f32::atanh", reason = "use bevy_math::ops::atanh instead for libm determinism" },
|
||||
{ path = "criterion::black_box", reason = "use core::hint::black_box instead" },
|
||||
]
|
||||
|
||||
# Require `bevy_ecs::children!` to use `[]` braces, instead of `()` or `{}`.
|
||||
|
||||
@ -18,28 +18,17 @@ bevy_reflect = [
|
||||
"dep:bevy_reflect",
|
||||
"bevy_app/bevy_reflect",
|
||||
"bevy_ecs/bevy_reflect",
|
||||
"bevy_input_focus/bevy_reflect",
|
||||
]
|
||||
|
||||
## Adds serialization support through `serde`.
|
||||
serialize = [
|
||||
"dep:serde",
|
||||
"bevy_ecs/serialize",
|
||||
"bevy_input_focus/serialize",
|
||||
"accesskit/serde",
|
||||
]
|
||||
serialize = ["dep:serde", "bevy_ecs/serialize", "accesskit/serde"]
|
||||
|
||||
# Platform Compatibility
|
||||
|
||||
## Allows access to the `std` crate. Enabling this feature will prevent compilation
|
||||
## on `no_std` targets, but provides access to certain additional features on
|
||||
## supported platforms.
|
||||
std = [
|
||||
"bevy_app/std",
|
||||
"bevy_ecs/std",
|
||||
"bevy_reflect/std",
|
||||
"bevy_input_focus/std",
|
||||
]
|
||||
std = ["bevy_app/std", "bevy_ecs/std", "bevy_reflect/std"]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
## on all platforms, including `no_std`.
|
||||
@ -47,22 +36,17 @@ critical-section = [
|
||||
"bevy_app/critical-section",
|
||||
"bevy_ecs/critical-section",
|
||||
"bevy_reflect?/critical-section",
|
||||
"bevy_input_focus/critical-section",
|
||||
]
|
||||
|
||||
## Uses the `libm` maths library instead of the one provided in `std` and `core`.
|
||||
libm = ["bevy_input_focus/libm"]
|
||||
|
||||
[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_input_focus = { path = "../bevy_input_focus", version = "0.16.0-dev", default-features = false }
|
||||
|
||||
# other
|
||||
accesskit = { version = "0.17", default-features = false }
|
||||
accesskit = { version = "0.19", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = [
|
||||
"alloc",
|
||||
], optional = true }
|
||||
|
||||
@ -137,11 +137,15 @@ impl From<Node> for AccessibilityNode {
|
||||
all(feature = "bevy_reflect", feature = "serialize"),
|
||||
reflect(Serialize, Deserialize, Clone)
|
||||
)]
|
||||
pub enum AccessibilitySystem {
|
||||
pub enum AccessibilitySystems {
|
||||
/// Update the accessibility tree
|
||||
Update,
|
||||
}
|
||||
|
||||
/// Deprecated alias for [`AccessibilitySystems`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `AccessibilitySystems`.")]
|
||||
pub type AccessibilitySystem = AccessibilitySystems;
|
||||
|
||||
/// Plugin managing non-GUI aspects of integrating with accessibility APIs.
|
||||
#[derive(Default)]
|
||||
pub struct AccessibilityPlugin;
|
||||
|
||||
@ -16,6 +16,7 @@ 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 = [
|
||||
"petgraph",
|
||||
] }
|
||||
@ -24,7 +25,7 @@ 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_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
||||
@ -100,43 +100,48 @@ use bevy_math::curve::{
|
||||
iterable::IterableCurve,
|
||||
Curve, Interval,
|
||||
};
|
||||
use bevy_platform_support::hash::Hashed;
|
||||
use bevy_mesh::morph::MorphWeights;
|
||||
use bevy_platform::hash::Hashed;
|
||||
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed};
|
||||
use bevy_render::mesh::morph::MorphWeights;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
|
||||
/// A value on a component that Bevy can animate.
|
||||
/// A trait for exposing a value in an entity so that it can be animated.
|
||||
///
|
||||
/// You can implement this trait on a unit struct in order to support animating
|
||||
/// custom components other than transforms and morph weights. Use that type in
|
||||
/// conjunction with [`AnimatableCurve`] (and perhaps [`AnimatableKeyframeCurve`]
|
||||
/// to define the animation itself).
|
||||
/// For example, in order to animate field of view, you might use:
|
||||
/// `AnimatableProperty` allows any value contained in an entity to be animated
|
||||
/// as long as it can be obtained by mutable reference. This makes it more
|
||||
/// flexible than [`animated_field`].
|
||||
///
|
||||
/// [`animated_field`]: crate::animated_field
|
||||
///
|
||||
/// Here, `AnimatableProperty` is used to animate a value inside an `Option`,
|
||||
/// returning an error if the option is `None`.
|
||||
///
|
||||
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use std::any::TypeId;
|
||||
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||
/// #[derive(Reflect)]
|
||||
/// struct FieldOfViewProperty;
|
||||
///
|
||||
/// impl AnimatableProperty for FieldOfViewProperty {
|
||||
/// type Property = f32;
|
||||
/// fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||
/// let component = entity
|
||||
/// .get_mut::<Projection>()
|
||||
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||
/// Projection,
|
||||
/// >(
|
||||
/// )))?
|
||||
/// .into_inner();
|
||||
/// match component {
|
||||
/// Projection::Perspective(perspective) => Ok(&mut perspective.fov),
|
||||
/// _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
|
||||
/// PerspectiveProjection,
|
||||
/// >(
|
||||
/// ))),
|
||||
/// #[derive(Component)]
|
||||
/// struct ExampleComponent {
|
||||
/// power_level: Option<f32>
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct PowerLevelProperty;
|
||||
///
|
||||
/// impl AnimatableProperty for PowerLevelProperty {
|
||||
/// type Property = f32;
|
||||
/// fn get_mut<'a>(
|
||||
/// &self,
|
||||
/// entity: &'a mut AnimationEntityMut
|
||||
/// ) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||
/// let component = entity
|
||||
/// .get_mut::<ExampleComponent>()
|
||||
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(
|
||||
/// TypeId::of::<ExampleComponent>()
|
||||
/// ))?
|
||||
/// .into_inner();
|
||||
/// component.power_level.as_mut().ok_or(AnimationEvaluationError::PropertyNotPresent(
|
||||
/// TypeId::of::<Option<f32>>()
|
||||
/// ))
|
||||
/// }
|
||||
///
|
||||
/// fn evaluator_id(&self) -> EvaluatorId {
|
||||
@ -144,58 +149,44 @@ use downcast_rs::{impl_downcast, Downcast};
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// You can then create an [`AnimationClip`] to animate this property like so:
|
||||
///
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// You can then create an [`AnimatableCurve`] to animate this property like so:
|
||||
///
|
||||
/// # use bevy_animation::{VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
||||
/// # use bevy_ecs::name::Name;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||
/// # use bevy_ecs::{name::Name, component::Component};
|
||||
/// # use std::any::TypeId;
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// # #[derive(Reflect, Clone)]
|
||||
/// # struct FieldOfViewProperty;
|
||||
/// # impl AnimatableProperty for FieldOfViewProperty {
|
||||
/// # #[derive(Component)]
|
||||
/// # struct ExampleComponent { power_level: Option<f32> }
|
||||
/// # #[derive(Clone)]
|
||||
/// # struct PowerLevelProperty;
|
||||
/// # impl AnimatableProperty for PowerLevelProperty {
|
||||
/// # type Property = f32;
|
||||
/// # fn get_mut<'a>(&self, entity: &'a mut AnimationEntityMut) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||
/// # fn get_mut<'a>(
|
||||
/// # &self,
|
||||
/// # entity: &'a mut AnimationEntityMut
|
||||
/// # ) -> Result<&'a mut Self::Property, AnimationEvaluationError> {
|
||||
/// # let component = entity
|
||||
/// # .get_mut::<Projection>()
|
||||
/// # .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||
/// # Projection,
|
||||
/// # >(
|
||||
/// # )))?
|
||||
/// # .get_mut::<ExampleComponent>()
|
||||
/// # .ok_or(AnimationEvaluationError::ComponentNotPresent(
|
||||
/// # TypeId::of::<ExampleComponent>()
|
||||
/// # ))?
|
||||
/// # .into_inner();
|
||||
/// # match component {
|
||||
/// # Projection::Perspective(perspective) => Ok(&mut perspective.fov),
|
||||
/// # _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
|
||||
/// # PerspectiveProjection,
|
||||
/// # >(
|
||||
/// # ))),
|
||||
/// # }
|
||||
/// # component.power_level.as_mut().ok_or(AnimationEvaluationError::PropertyNotPresent(
|
||||
/// # TypeId::of::<Option<f32>>()
|
||||
/// # ))
|
||||
/// # }
|
||||
/// # fn evaluator_id(&self) -> EvaluatorId {
|
||||
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
||||
/// # }
|
||||
/// # }
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// AnimatableCurve::new(
|
||||
/// FieldOfViewProperty,
|
||||
/// PowerLevelProperty,
|
||||
/// AnimatableKeyframeCurve::new([
|
||||
/// (0.0, core::f32::consts::PI / 4.0),
|
||||
/// (1.0, core::f32::consts::PI / 3.0),
|
||||
/// ]).expect("Failed to create font size curve")
|
||||
/// )
|
||||
/// (0.0, 0.0),
|
||||
/// (1.0, 9001.0),
|
||||
/// ]).expect("Failed to create power level curve")
|
||||
/// );
|
||||
///
|
||||
/// Here, the use of [`AnimatableKeyframeCurve`] creates a curve out of the given keyframe time-value
|
||||
/// pairs, using the [`Animatable`] implementation of `f32` to interpolate between them. The
|
||||
/// invocation of [`AnimatableCurve::new`] with `FieldOfViewProperty` indicates that the `f32`
|
||||
/// output from that curve is to be used to animate the font size of a `PerspectiveProjection` component (as
|
||||
/// configured above).
|
||||
///
|
||||
/// [`AnimationClip`]: crate::AnimationClip
|
||||
pub trait AnimatableProperty: Send + Sync + 'static {
|
||||
/// The animated property type.
|
||||
type Property: Animatable;
|
||||
|
||||
@ -373,7 +373,7 @@ impl<T> WideCubicKeyframeCurve<T> {
|
||||
/// recommended to use its implementation of the [`IterableCurve`] trait, which allows iterating
|
||||
/// directly over information derived from the curve without allocating.
|
||||
///
|
||||
/// [`MorphWeights`]: bevy_render::prelude::MorphWeights
|
||||
/// [`MorphWeights`]: bevy_mesh::morph::MorphWeights
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub enum WeightsCurve {
|
||||
|
||||
@ -17,7 +17,7 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
system::{Res, ResMut},
|
||||
};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectSerialize};
|
||||
use derive_more::derive::From;
|
||||
use petgraph::{
|
||||
|
||||
@ -31,14 +31,14 @@ use crate::{
|
||||
prelude::EvaluatorId,
|
||||
};
|
||||
|
||||
use bevy_app::{Animation, App, Plugin, PostUpdate};
|
||||
use bevy_asset::{Asset, AssetApp, AssetEvents, Assets};
|
||||
use bevy_app::{AnimationSystems, App, Plugin, PostUpdate};
|
||||
use bevy_asset::{Asset, AssetApp, AssetEventSystems, Assets};
|
||||
use bevy_ecs::{prelude::*, world::EntityMutExcept};
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_platform_support::{collections::HashMap, hash::NoOpHash};
|
||||
use bevy_platform::{collections::HashMap, hash::NoOpHash};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_time::Time;
|
||||
use bevy_transform::TransformSystem;
|
||||
use bevy_transform::TransformSystems;
|
||||
use bevy_utils::{PreHashMap, PreHashMapExt, TypeIdMap};
|
||||
use petgraph::graph::NodeIndex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -685,7 +685,6 @@ impl ActiveAnimation {
|
||||
#[reflect(Component, Default, Clone)]
|
||||
pub struct AnimationPlayer {
|
||||
active_animations: HashMap<AnimationNodeIndex, ActiveAnimation>,
|
||||
blend_weights: HashMap<AnimationNodeIndex, f32>,
|
||||
}
|
||||
|
||||
// This is needed since `#[derive(Clone)]` does not generate optimized `clone_from`.
|
||||
@ -693,13 +692,11 @@ impl Clone for AnimationPlayer {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
active_animations: self.active_animations.clone(),
|
||||
blend_weights: self.blend_weights.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn clone_from(&mut self, source: &Self) {
|
||||
self.active_animations.clone_from(&source.active_animations);
|
||||
self.blend_weights.clone_from(&source.blend_weights);
|
||||
}
|
||||
}
|
||||
|
||||
@ -758,10 +755,10 @@ impl AnimationCurveEvaluators {
|
||||
.component_property_curve_evaluators
|
||||
.get_or_insert_with(component_property, func),
|
||||
EvaluatorId::Type(type_id) => match self.type_id_curve_evaluators.entry(type_id) {
|
||||
bevy_platform_support::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
||||
bevy_platform::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
||||
&mut **occupied_entry.into_mut()
|
||||
}
|
||||
bevy_platform_support::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||
bevy_platform::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||
&mut **vacant_entry.insert(func())
|
||||
}
|
||||
},
|
||||
@ -1247,7 +1244,7 @@ impl Plugin for AnimationPlugin {
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(
|
||||
graph::thread_animation_graphs.before(AssetEvents),
|
||||
graph::thread_animation_graphs.before(AssetEventSystems),
|
||||
advance_transitions,
|
||||
advance_animations,
|
||||
// TODO: `animate_targets` can animate anything, so
|
||||
@ -1263,8 +1260,8 @@ impl Plugin for AnimationPlugin {
|
||||
expire_completed_transitions,
|
||||
)
|
||||
.chain()
|
||||
.in_set(Animation)
|
||||
.before(TransformSystem::TransformPropagate),
|
||||
.in_set(AnimationSystems)
|
||||
.before(TransformSystems::Propagate),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -1533,6 +1530,8 @@ impl<'a> Iterator for TriggeredEventsIter<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_reflect::{DynamicMap, Map};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
@ -1664,4 +1663,13 @@ mod tests {
|
||||
active_animation.update(clip.duration, clip.duration); // 0.3 : 0.0
|
||||
assert_triggered_events_with(&active_animation, &clip, [0.3, 0.2]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_animation_node_index_as_key_of_dynamic_map() {
|
||||
let mut map = DynamicMap::default();
|
||||
map.insert_boxed(
|
||||
Box::new(AnimationNodeIndex::new(0)),
|
||||
Box::new(ActiveAnimation::default()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,8 +118,9 @@ pub fn advance_transitions(
|
||||
// is divided between all the other layers, eventually culminating in the
|
||||
// currently-playing animation receiving whatever's left. This results in a
|
||||
// nicely normalized weight.
|
||||
let mut remaining_weight = 1.0;
|
||||
for (mut animation_transitions, mut player) in query.iter_mut() {
|
||||
let mut remaining_weight = 1.0;
|
||||
|
||||
for transition in &mut animation_transitions.transitions.iter_mut().rev() {
|
||||
// Decrease weight.
|
||||
transition.current_weight = (transition.current_weight
|
||||
|
||||
39
crates/bevy_anti_aliasing/Cargo.toml
Normal file
39
crates/bevy_anti_aliasing/Cargo.toml
Normal file
@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "bevy_anti_aliasing"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides various anti aliasing implementations for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
repository = "https://github.com/bevyengine/bevy"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["bevy"]
|
||||
|
||||
[features]
|
||||
trace = []
|
||||
webgl = []
|
||||
webgpu = []
|
||||
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" }
|
||||
|
||||
# other
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
|
||||
all-features = true
|
||||
7
crates/bevy_anti_aliasing/README.md
Normal file
7
crates/bevy_anti_aliasing/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Bevy Anti Aliasing
|
||||
|
||||
[](https://github.com/bevyengine/bevy#license)
|
||||
[](https://crates.io/crates/bevy_core_pipeline)
|
||||
[](https://crates.io/crates/bevy_core_pipeline)
|
||||
[](https://docs.rs/bevy_core_pipeline/latest/bevy_core_pipeline/)
|
||||
[](https://discord.gg/bevy)
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
use bevy_app::prelude::*;
|
||||
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,
|
||||
};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
@ -18,7 +18,7 @@ use bevy_render::{
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
Render, RenderApp, RenderSystems,
|
||||
};
|
||||
|
||||
mod node;
|
||||
@ -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((
|
||||
@ -121,7 +113,7 @@ impl Plugin for CasPlugin {
|
||||
};
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<CasPipeline>>()
|
||||
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSet::Prepare));
|
||||
.add_systems(Render, prepare_cas_pipelines.in_set(RenderSystems::Prepare));
|
||||
|
||||
{
|
||||
render_app
|
||||
@ -171,6 +163,7 @@ impl Plugin for CasPlugin {
|
||||
pub struct CasPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for CasPipeline {
|
||||
@ -194,6 +187,7 @@ impl FromWorld for CasPipeline {
|
||||
CasPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "robust_contrast_adaptive_sharpening.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -217,7 +211,7 @@ impl SpecializedRenderPipeline for CasPipeline {
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: CONTRAST_ADAPTIVE_SHARPENING_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "fragment".into(),
|
||||
targets: vec![Some(ColorTargetState {
|
||||
9
crates/bevy_anti_aliasing/src/experimental/mod.rs
Normal file
9
crates/bevy_anti_aliasing/src/experimental/mod.rs
Normal file
@ -0,0 +1,9 @@
|
||||
//! Experimental rendering features.
|
||||
//!
|
||||
//! Experimental features are features with known problems, missing features,
|
||||
//! compatibility issues, low performance, and/or future breaking changes, but
|
||||
//! are included nonetheless for testing purposes.
|
||||
|
||||
pub mod taa {
|
||||
pub use crate::taa::{TemporalAntiAliasNode, TemporalAntiAliasPlugin, TemporalAntiAliasing};
|
||||
}
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
use bevy_app::prelude::*;
|
||||
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,
|
||||
};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::BevyDefault as _;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
@ -18,7 +18,7 @@ use bevy_render::{
|
||||
},
|
||||
renderer::RenderDevice,
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_utils::default;
|
||||
|
||||
@ -80,13 +80,11 @@ impl Default for Fxaa {
|
||||
}
|
||||
}
|
||||
|
||||
const FXAA_SHADER_HANDLE: Handle<Shader> = weak_handle!("fc58c0a8-01c0-46e9-94cc-83a794bae7b0");
|
||||
|
||||
/// Adds support for Fast Approximate Anti-Aliasing (FXAA)
|
||||
pub struct FxaaPlugin;
|
||||
impl Plugin for FxaaPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, FXAA_SHADER_HANDLE, "fxaa.wgsl", Shader::from_wgsl);
|
||||
embedded_asset!(app, "fxaa.wgsl");
|
||||
|
||||
app.register_type::<Fxaa>();
|
||||
app.add_plugins(ExtractComponentPlugin::<Fxaa>::default());
|
||||
@ -96,7 +94,10 @@ impl Plugin for FxaaPlugin {
|
||||
};
|
||||
render_app
|
||||
.init_resource::<SpecializedRenderPipelines<FxaaPipeline>>()
|
||||
.add_systems(Render, prepare_fxaa_pipelines.in_set(RenderSet::Prepare))
|
||||
.add_systems(
|
||||
Render,
|
||||
prepare_fxaa_pipelines.in_set(RenderSystems::Prepare),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<FxaaNode>>(Core3d, Node3d::Fxaa)
|
||||
.add_render_graph_edges(
|
||||
Core3d,
|
||||
@ -129,6 +130,7 @@ impl Plugin for FxaaPlugin {
|
||||
pub struct FxaaPipeline {
|
||||
texture_bind_group: BindGroupLayout,
|
||||
sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for FxaaPipeline {
|
||||
@ -155,6 +157,7 @@ impl FromWorld for FxaaPipeline {
|
||||
FxaaPipeline {
|
||||
texture_bind_group,
|
||||
sampler,
|
||||
shader: load_embedded_asset!(render_world, "fxaa.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -180,7 +183,7 @@ impl SpecializedRenderPipeline for FxaaPipeline {
|
||||
layout: vec![self.texture_bind_group.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: FXAA_SHADER_HANDLE,
|
||||
shader: self.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(),
|
||||
27
crates/bevy_anti_aliasing/src/lib.rs
Normal file
27
crates/bevy_anti_aliasing/src/lib.rs
Normal file
@ -0,0 +1,27 @@
|
||||
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
|
||||
#![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"
|
||||
)]
|
||||
|
||||
use bevy_app::Plugin;
|
||||
use contrast_adaptive_sharpening::CasPlugin;
|
||||
use fxaa::FxaaPlugin;
|
||||
use smaa::SmaaPlugin;
|
||||
|
||||
pub mod contrast_adaptive_sharpening;
|
||||
pub mod experimental;
|
||||
pub mod fxaa;
|
||||
pub mod smaa;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -29,16 +29,16 @@
|
||||
//! * Compatibility with SSAA and MSAA.
|
||||
//!
|
||||
//! [SMAA]: https://www.iryoku.com/smaa/
|
||||
#[cfg(not(feature = "smaa_luts"))]
|
||||
use crate::tonemapping::lut_placeholder;
|
||||
use crate::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
};
|
||||
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::{
|
||||
core_2d::graph::{Core2d, Node2d},
|
||||
core_3d::graph::{Core3d, Node3d},
|
||||
};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
@ -76,12 +76,10 @@ use bevy_render::{
|
||||
renderer::{RenderContext, RenderDevice, RenderQueue},
|
||||
texture::{CachedTexture, GpuImage, TextureCache},
|
||||
view::{ExtractedView, ViewTarget},
|
||||
Render, RenderApp, RenderSet,
|
||||
Render, RenderApp, RenderSystems,
|
||||
};
|
||||
use bevy_utils::prelude::default;
|
||||
|
||||
/// The handle of the `smaa.wgsl` shader.
|
||||
const SMAA_SHADER_HANDLE: Handle<Shader> = weak_handle!("fdd9839f-1ab4-4e0d-88a0-240b67da2ddf");
|
||||
/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally.
|
||||
const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle<Image> =
|
||||
weak_handle!("569c4d67-c7fa-4958-b1af-0836023603c0");
|
||||
@ -147,6 +145,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 +155,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 +165,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 +291,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.
|
||||
@ -297,8 +301,6 @@ impl Plugin for SmaaPlugin {
|
||||
SMAA_AREA_LUT_TEXTURE_HANDLE,
|
||||
"SMAAAreaLUT.ktx2",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
#[cfg(all(debug_assertions, feature = "dds"))]
|
||||
"SMAAAreaLUT".to_owned(),
|
||||
bytes,
|
||||
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
|
||||
bevy_image::CompressedImageFormats::NONE,
|
||||
@ -315,8 +317,6 @@ impl Plugin for SmaaPlugin {
|
||||
SMAA_SEARCH_LUT_TEXTURE_HANDLE,
|
||||
"SMAASearchLUT.ktx2",
|
||||
|bytes, _: String| Image::from_buffer(
|
||||
#[cfg(all(debug_assertions, feature = "dds"))]
|
||||
"SMAASearchLUT".to_owned(),
|
||||
bytes,
|
||||
bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2),
|
||||
bevy_image::CompressedImageFormats::NONE,
|
||||
@ -350,10 +350,10 @@ impl Plugin for SmaaPlugin {
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_smaa_pipelines.in_set(RenderSet::Prepare),
|
||||
prepare_smaa_uniforms.in_set(RenderSet::PrepareResources),
|
||||
prepare_smaa_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_smaa_bind_groups.in_set(RenderSet::PrepareBindGroups),
|
||||
prepare_smaa_pipelines.in_set(RenderSystems::Prepare),
|
||||
prepare_smaa_uniforms.in_set(RenderSystems::PrepareResources),
|
||||
prepare_smaa_textures.in_set(RenderSystems::PrepareResources),
|
||||
prepare_smaa_bind_groups.in_set(RenderSystems::PrepareBindGroups),
|
||||
),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<SmaaNode>>(Core3d, Node3d::Smaa)
|
||||
@ -435,18 +435,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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -476,13 +481,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 {
|
||||
@ -536,13 +541,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 {
|
||||
@ -584,13 +589,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 {
|
||||
@ -1,11 +1,11 @@
|
||||
use crate::{
|
||||
use bevy_app::{App, Plugin};
|
||||
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},
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_diagnostic::FrameCount;
|
||||
use bevy_ecs::{
|
||||
prelude::{Component, Entity, ReflectComponent},
|
||||
@ -36,12 +36,10 @@ use bevy_render::{
|
||||
sync_world::RenderEntity,
|
||||
texture::{CachedTexture, TextureCache},
|
||||
view::{ExtractedView, Msaa, ViewTarget},
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSet,
|
||||
ExtractSchedule, MainWorld, Render, RenderApp, RenderSystems,
|
||||
};
|
||||
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,9 +62,9 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
.add_systems(
|
||||
Render,
|
||||
(
|
||||
prepare_taa_jitter_and_mip_bias.in_set(RenderSet::ManageViews),
|
||||
prepare_taa_pipelines.in_set(RenderSet::Prepare),
|
||||
prepare_taa_history_textures.in_set(RenderSet::PrepareResources),
|
||||
prepare_taa_jitter_and_mip_bias.in_set(RenderSystems::ManageViews),
|
||||
prepare_taa_pipelines.in_set(RenderSystems::Prepare),
|
||||
prepare_taa_history_textures.in_set(RenderSystems::PrepareResources),
|
||||
),
|
||||
)
|
||||
.add_render_graph_node::<ViewNodeRunner<TemporalAntiAliasNode>>(Core3d, Node3d::Taa)
|
||||
@ -243,6 +241,7 @@ struct TaaPipeline {
|
||||
taa_bind_group_layout: BindGroupLayout,
|
||||
nearest_sampler: Sampler,
|
||||
linear_sampler: Sampler,
|
||||
shader: Handle<Shader>,
|
||||
}
|
||||
|
||||
impl FromWorld for TaaPipeline {
|
||||
@ -287,6 +286,7 @@ impl FromWorld for TaaPipeline {
|
||||
taa_bind_group_layout,
|
||||
nearest_sampler,
|
||||
linear_sampler,
|
||||
shader: load_embedded_asset!(world, "taa.wgsl"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -319,7 +319,7 @@ impl SpecializedRenderPipeline for TaaPipeline {
|
||||
layout: vec![self.taa_bind_group_layout.clone()],
|
||||
vertex: fullscreen_shader_vertex_state(),
|
||||
fragment: Some(FragmentState {
|
||||
shader: TAA_SHADER_HANDLE,
|
||||
shader: self.shader.clone(),
|
||||
shader_defs,
|
||||
entry_point: "taa".into(),
|
||||
targets: vec![
|
||||
@ -47,9 +47,8 @@ std = [
|
||||
"bevy_ecs/std",
|
||||
"dep:ctrlc",
|
||||
"downcast-rs/std",
|
||||
"bevy_utils/std",
|
||||
"bevy_tasks/std",
|
||||
"bevy_platform_support/std",
|
||||
"bevy_platform/std",
|
||||
]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
@ -57,14 +56,14 @@ std = [
|
||||
critical-section = [
|
||||
"bevy_tasks/critical-section",
|
||||
"bevy_ecs/critical-section",
|
||||
"bevy_platform_support/critical-section",
|
||||
"bevy_platform/critical-section",
|
||||
"bevy_reflect?/critical-section",
|
||||
]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = [
|
||||
"bevy_platform_support/web",
|
||||
"bevy_platform/web",
|
||||
"bevy_tasks/web",
|
||||
"bevy_reflect?/web",
|
||||
"dep:wasm-bindgen",
|
||||
@ -77,11 +76,9 @@ web = [
|
||||
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_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-features = false }
|
||||
|
||||
# other
|
||||
downcast-rs = { version = "2", default-features = false }
|
||||
|
||||
@ -10,14 +10,14 @@ use alloc::{
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
component::RequiredComponentsError,
|
||||
error::{BevyError, SystemErrorContext},
|
||||
error::{DefaultErrorHandler, ErrorHandler},
|
||||
event::{event_update_system, EventCursor},
|
||||
intern::Interned,
|
||||
prelude::*,
|
||||
schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
|
||||
system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput},
|
||||
};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe};
|
||||
use log::debug;
|
||||
|
||||
@ -86,6 +86,7 @@ pub struct App {
|
||||
/// [`WinitPlugin`]: https://docs.rs/bevy/latest/bevy/winit/struct.WinitPlugin.html
|
||||
/// [`ScheduleRunnerPlugin`]: https://docs.rs/bevy/latest/bevy/app/struct.ScheduleRunnerPlugin.html
|
||||
pub(crate) runner: RunnerFn,
|
||||
default_error_handler: Option<ErrorHandler>,
|
||||
}
|
||||
|
||||
impl Debug for App {
|
||||
@ -118,7 +119,7 @@ impl Default for App {
|
||||
app.add_systems(
|
||||
First,
|
||||
event_update_system
|
||||
.in_set(bevy_ecs::event::EventUpdates)
|
||||
.in_set(bevy_ecs::event::EventUpdateSystems)
|
||||
.run_if(bevy_ecs::event::event_update_condition),
|
||||
);
|
||||
app.add_event::<AppExit>();
|
||||
@ -144,6 +145,7 @@ impl App {
|
||||
sub_apps: HashMap::default(),
|
||||
},
|
||||
runner: Box::new(run_once),
|
||||
default_error_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -1116,7 +1118,12 @@ impl App {
|
||||
}
|
||||
|
||||
/// Inserts a [`SubApp`] with the given label.
|
||||
pub fn insert_sub_app(&mut self, label: impl AppLabel, sub_app: SubApp) {
|
||||
pub fn insert_sub_app(&mut self, label: impl AppLabel, mut sub_app: SubApp) {
|
||||
if let Some(handler) = self.default_error_handler {
|
||||
sub_app
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
|
||||
}
|
||||
self.sub_apps.sub_apps.insert(label.intern(), sub_app);
|
||||
}
|
||||
|
||||
@ -1274,18 +1281,6 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the global system error handler to use for systems that return a [`Result`].
|
||||
///
|
||||
/// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error)
|
||||
/// for more information.
|
||||
pub fn set_system_error_handler(
|
||||
&mut self,
|
||||
error_handler: fn(BevyError, SystemErrorContext),
|
||||
) -> &mut Self {
|
||||
self.main_mut().set_system_error_handler(error_handler);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attempts to determine if an [`AppExit`] was raised since the last update.
|
||||
///
|
||||
/// Will attempt to return the first [`Error`](AppExit::Error) it encounters.
|
||||
@ -1311,6 +1306,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 a [`Trigger`].
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
@ -1331,7 +1328,7 @@ impl App {
|
||||
/// # #[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| {
|
||||
/// if trigger.event().friends_allowed {
|
||||
/// for friend in friends.iter() {
|
||||
@ -1347,6 +1344,49 @@ impl App {
|
||||
self.world_mut().add_observer(observer);
|
||||
self
|
||||
}
|
||||
|
||||
/// Gets the error handler to set for new supapps.
|
||||
///
|
||||
/// Note that the error handler of existing subapps may differ.
|
||||
pub fn get_error_handler(&self) -> Option<ErrorHandler> {
|
||||
self.default_error_handler
|
||||
}
|
||||
|
||||
/// Set the [default error handler] for the all subapps (including the main one and future ones)
|
||||
/// that do not have one.
|
||||
///
|
||||
/// May only be called once and should be set by the application, not by libraries.
|
||||
///
|
||||
/// The handler will be called when an error is produced and not otherwise handled.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if called multiple times.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// # use bevy_app::*;
|
||||
/// # use bevy_ecs::error::warn;
|
||||
/// # fn MyPlugins(_: &mut App) {}
|
||||
/// App::new()
|
||||
/// .set_error_handler(warn)
|
||||
/// .add_plugins(MyPlugins)
|
||||
/// .run();
|
||||
/// ```
|
||||
///
|
||||
/// [default error handler]: bevy_ecs::error::DefaultErrorHandler
|
||||
pub fn set_error_handler(&mut self, handler: ErrorHandler) -> &mut Self {
|
||||
assert!(
|
||||
self.default_error_handler.is_none(),
|
||||
"`set_error_handler` called multiple times on same `App`"
|
||||
);
|
||||
self.default_error_handler = Some(handler);
|
||||
for sub_app in self.sub_apps.iter_mut() {
|
||||
sub_app
|
||||
.world_mut()
|
||||
.get_resource_or_insert_with(|| DefaultErrorHandler(handler));
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
type RunnerFn = Box<dyn FnOnce(App) -> AppExit>;
|
||||
|
||||
@ -55,9 +55,9 @@ pub mod prelude {
|
||||
main_schedule::{
|
||||
First, FixedFirst, FixedLast, FixedPostUpdate, FixedPreUpdate, FixedUpdate, Last, Main,
|
||||
PostStartup, PostUpdate, PreStartup, PreUpdate, RunFixedMainLoop,
|
||||
RunFixedMainLoopSystem, SpawnScene, Startup, Update,
|
||||
RunFixedMainLoopSystems, SpawnScene, Startup, Update,
|
||||
},
|
||||
sub_app::SubApp,
|
||||
NonSendMarker, Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin,
|
||||
Plugin, PluginGroup, TaskPoolOptions, TaskPoolPlugin,
|
||||
};
|
||||
}
|
||||
|
||||
@ -15,6 +15,13 @@ use bevy_ecs::{
|
||||
/// By default, it will run the following schedules in the given order:
|
||||
///
|
||||
/// On the first run of the schedule (and only on the first run), it will run:
|
||||
/// * [`StateTransition`] [^1]
|
||||
/// * This means that [`OnEnter(MyState::Foo)`] will be called *before* [`PreStartup`]
|
||||
/// if `MyState` was added to the app with `MyState::Foo` as the initial state,
|
||||
/// as well as [`OnEnter(MyComputedState)`] if it `compute`s to `Some(Self)` in `MyState::Foo`.
|
||||
/// * If you want to run systems before any state transitions, regardless of which state is the starting state,
|
||||
/// for example, for registering required components, you can add your own custom startup schedule
|
||||
/// before [`StateTransition`]. See [`MainScheduleOrder::insert_startup_before`] for more details.
|
||||
/// * [`PreStartup`]
|
||||
/// * [`Startup`]
|
||||
/// * [`PostStartup`]
|
||||
@ -22,7 +29,7 @@ use bevy_ecs::{
|
||||
/// Then it will run:
|
||||
/// * [`First`]
|
||||
/// * [`PreUpdate`]
|
||||
/// * [`StateTransition`]
|
||||
/// * [`StateTransition`] [^1]
|
||||
/// * [`RunFixedMainLoop`]
|
||||
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
|
||||
/// * [`Update`]
|
||||
@ -37,35 +44,39 @@ use bevy_ecs::{
|
||||
///
|
||||
/// See [`RenderPlugin`] and [`PipelinedRenderingPlugin`] for more details.
|
||||
///
|
||||
/// [^1]: [`StateTransition`] is inserted only if you have `bevy_state` feature enabled. It is enabled in `default` features.
|
||||
///
|
||||
/// [`StateTransition`]: https://docs.rs/bevy/latest/bevy/prelude/struct.StateTransition.html
|
||||
/// [`OnEnter(MyState::Foo)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html
|
||||
/// [`OnEnter(MyComputedState)`]: https://docs.rs/bevy/latest/bevy/prelude/struct.OnEnter.html
|
||||
/// [`RenderPlugin`]: https://docs.rs/bevy/latest/bevy/render/struct.RenderPlugin.html
|
||||
/// [`PipelinedRenderingPlugin`]: https://docs.rs/bevy/latest/bevy/render/pipelined_rendering/struct.PipelinedRenderingPlugin.html
|
||||
/// [`SubApp`]: crate::SubApp
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Main;
|
||||
|
||||
/// The schedule that runs before [`Startup`].
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct PreStartup;
|
||||
|
||||
/// The schedule that runs once when the app starts.
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Startup;
|
||||
|
||||
/// The schedule that runs once after [`Startup`].
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct PostStartup;
|
||||
|
||||
/// Runs first in the schedule.
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct First;
|
||||
|
||||
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
|
||||
@ -76,33 +87,33 @@ pub struct First;
|
||||
/// [`PreUpdate`] abstracts out "pre work implementation details".
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct PreUpdate;
|
||||
|
||||
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
|
||||
///
|
||||
/// If you need to order your variable timestep systems
|
||||
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
|
||||
/// If you need to order your variable timestep systems before or after
|
||||
/// the fixed update logic, use the [`RunFixedMainLoopSystems`] system set.
|
||||
///
|
||||
/// Note that in contrast to most other Bevy schedules, systems added directly to
|
||||
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct RunFixedMainLoop;
|
||||
|
||||
/// Runs first in the [`FixedMain`] schedule.
|
||||
///
|
||||
/// See the [`FixedMain`] schedule for details on how fixed updates work.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedFirst;
|
||||
|
||||
/// The schedule that contains logic that must run before [`FixedUpdate`].
|
||||
///
|
||||
/// See the [`FixedMain`] schedule for details on how fixed updates work.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedPreUpdate;
|
||||
|
||||
/// The schedule that contains most gameplay logic, which runs at a fixed rate rather than every render frame.
|
||||
@ -117,7 +128,7 @@ pub struct FixedPreUpdate;
|
||||
/// See the [`Update`] schedule for examples of systems that *should not* use this schedule.
|
||||
/// See the [`FixedMain`] schedule for details on how fixed updates work.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedUpdate;
|
||||
|
||||
/// The schedule that runs after the [`FixedUpdate`] schedule, for reacting
|
||||
@ -125,26 +136,26 @@ pub struct FixedUpdate;
|
||||
///
|
||||
/// See the [`FixedMain`] schedule for details on how fixed updates work.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedPostUpdate;
|
||||
|
||||
/// The schedule that runs last in [`FixedMain`]
|
||||
///
|
||||
/// See the [`FixedMain`] schedule for details on how fixed updates work.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedLast;
|
||||
|
||||
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
||||
///
|
||||
/// This is run by the [`RunFixedMainLoop`] schedule. If you need to order your variable timestep systems
|
||||
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystem`] system set.
|
||||
/// before or after the fixed update logic, use the [`RunFixedMainLoopSystems`] system set.
|
||||
///
|
||||
/// Frequency of execution is configured by inserting `Time<Fixed>` resource, 64 Hz by default.
|
||||
/// See [this example](https://github.com/bevyengine/bevy/blob/latest/examples/time/time.rs).
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct FixedMain;
|
||||
|
||||
/// The schedule that contains any app logic that must run once per render frame.
|
||||
@ -157,13 +168,13 @@ pub struct FixedMain;
|
||||
///
|
||||
/// See the [`FixedUpdate`] schedule for examples of systems that *should not* use this schedule.
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Update;
|
||||
|
||||
/// The schedule that contains scene spawning.
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct SpawnScene;
|
||||
|
||||
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
|
||||
@ -174,18 +185,22 @@ pub struct SpawnScene;
|
||||
/// [`PostUpdate`] abstracts out "implementation details" from users defining systems in [`Update`].
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct PostUpdate;
|
||||
|
||||
/// Runs last in the schedule.
|
||||
///
|
||||
/// See the [`Main`] schedule for some details about how schedules are run.
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)]
|
||||
pub struct Last;
|
||||
|
||||
/// Animation system set. This exists in [`PostUpdate`].
|
||||
#[derive(SystemSet, Debug, Hash, PartialEq, Eq, Clone)]
|
||||
pub struct Animation;
|
||||
pub struct AnimationSystems;
|
||||
|
||||
/// Deprecated alias for [`AnimationSystems`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `AnimationSystems`.")]
|
||||
pub type Animation = AnimationSystems;
|
||||
|
||||
/// Defines the schedules to be run for the [`Main`] schedule, including
|
||||
/// their order.
|
||||
@ -307,9 +322,9 @@ impl Plugin for MainSchedulePlugin {
|
||||
.configure_sets(
|
||||
RunFixedMainLoop,
|
||||
(
|
||||
RunFixedMainLoopSystem::BeforeFixedMainLoop,
|
||||
RunFixedMainLoopSystem::FixedMainLoop,
|
||||
RunFixedMainLoopSystem::AfterFixedMainLoop,
|
||||
RunFixedMainLoopSystems::BeforeFixedMainLoop,
|
||||
RunFixedMainLoopSystems::FixedMainLoop,
|
||||
RunFixedMainLoopSystems::AfterFixedMainLoop,
|
||||
)
|
||||
.chain(),
|
||||
);
|
||||
@ -389,7 +404,7 @@ impl FixedMain {
|
||||
/// Note that in contrast to most other Bevy schedules, systems added directly to
|
||||
/// [`RunFixedMainLoop`] will *not* be parallelized between each other.
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Copy, Clone, SystemSet)]
|
||||
pub enum RunFixedMainLoopSystem {
|
||||
pub enum RunFixedMainLoopSystems {
|
||||
/// Runs before the fixed update logic.
|
||||
///
|
||||
/// A good example of a system that fits here
|
||||
@ -408,7 +423,7 @@ pub enum RunFixedMainLoopSystem {
|
||||
/// App::new()
|
||||
/// .add_systems(
|
||||
/// RunFixedMainLoop,
|
||||
/// update_camera_rotation.in_set(RunFixedMainLoopSystem::BeforeFixedMainLoop))
|
||||
/// update_camera_rotation.in_set(RunFixedMainLoopSystems::BeforeFixedMainLoop))
|
||||
/// .add_systems(FixedUpdate, update_physics);
|
||||
///
|
||||
/// # fn update_camera_rotation() {}
|
||||
@ -421,7 +436,7 @@ pub enum RunFixedMainLoopSystem {
|
||||
///
|
||||
/// Don't place systems here, use [`FixedUpdate`] and friends instead.
|
||||
/// Use this system instead to order your systems to run specifically inbetween the fixed update logic and all
|
||||
/// other systems that run in [`RunFixedMainLoopSystem::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystem::AfterFixedMainLoop`].
|
||||
/// other systems that run in [`RunFixedMainLoopSystems::BeforeFixedMainLoop`] or [`RunFixedMainLoopSystems::AfterFixedMainLoop`].
|
||||
///
|
||||
/// [`Time<Virtual>`]: https://docs.rs/bevy/latest/bevy/prelude/struct.Virtual.html
|
||||
/// [`Time::overstep`]: https://docs.rs/bevy/latest/bevy/time/struct.Time.html#method.overstep
|
||||
@ -437,8 +452,8 @@ pub enum RunFixedMainLoopSystem {
|
||||
/// // This system will be called before all interpolation systems
|
||||
/// // that third-party plugins might add.
|
||||
/// prepare_for_interpolation
|
||||
/// .after(RunFixedMainLoopSystem::FixedMainLoop)
|
||||
/// .before(RunFixedMainLoopSystem::AfterFixedMainLoop),
|
||||
/// .after(RunFixedMainLoopSystems::FixedMainLoop)
|
||||
/// .before(RunFixedMainLoopSystems::AfterFixedMainLoop),
|
||||
/// )
|
||||
/// );
|
||||
///
|
||||
@ -462,10 +477,14 @@ pub enum RunFixedMainLoopSystem {
|
||||
/// .add_systems(FixedUpdate, update_physics)
|
||||
/// .add_systems(
|
||||
/// RunFixedMainLoop,
|
||||
/// interpolate_transforms.in_set(RunFixedMainLoopSystem::AfterFixedMainLoop));
|
||||
/// interpolate_transforms.in_set(RunFixedMainLoopSystems::AfterFixedMainLoop));
|
||||
///
|
||||
/// # fn interpolate_transforms() {}
|
||||
/// # fn update_physics() {}
|
||||
/// ```
|
||||
AfterFixedMainLoop,
|
||||
}
|
||||
|
||||
/// Deprecated alias for [`RunFixedMainLoopSystems`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `RunFixedMainLoopSystems`.")]
|
||||
pub type RunFixedMainLoopSystem = RunFixedMainLoopSystems;
|
||||
|
||||
@ -4,7 +4,7 @@ use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use bevy_platform_support::collections::hash_map::Entry;
|
||||
use bevy_platform::collections::hash_map::Entry;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::any::TypeId;
|
||||
use log::{debug, warn};
|
||||
|
||||
@ -3,7 +3,7 @@ use crate::{
|
||||
plugin::Plugin,
|
||||
PluginsState,
|
||||
};
|
||||
use bevy_platform_support::time::Instant;
|
||||
use bevy_platform::time::Instant;
|
||||
use core::time::Duration;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@ -159,9 +159,8 @@ impl Plugin for ScheduleRunnerPlugin {
|
||||
} else {
|
||||
loop {
|
||||
match tick(&mut app, wait) {
|
||||
Ok(Some(_delay)) => {
|
||||
#[cfg(feature = "std")]
|
||||
std::thread::sleep(_delay);
|
||||
Ok(Some(delay)) => {
|
||||
bevy_platform::thread::sleep(delay);
|
||||
}
|
||||
Ok(None) => continue,
|
||||
Err(exit) => return exit,
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
use crate::{App, AppLabel, InternedAppLabel, Plugin, Plugins, PluginsState};
|
||||
use alloc::{boxed::Box, string::String, vec::Vec};
|
||||
use bevy_ecs::{
|
||||
error::{DefaultSystemErrorHandler, SystemErrorContext},
|
||||
event::EventRegistry,
|
||||
prelude::*,
|
||||
schedule::{InternedScheduleLabel, InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
|
||||
system::{ScheduleSystem, SystemId, SystemInput},
|
||||
};
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
@ -336,22 +335,6 @@ impl SubApp {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the global error handler to use for systems that return a [`Result`].
|
||||
///
|
||||
/// See the [`bevy_ecs::error` module-level documentation](bevy_ecs::error)
|
||||
/// for more information.
|
||||
pub fn set_system_error_handler(
|
||||
&mut self,
|
||||
error_handler: fn(BevyError, SystemErrorContext),
|
||||
) -> &mut Self {
|
||||
let mut default_handler = self
|
||||
.world_mut()
|
||||
.get_resource_or_init::<DefaultSystemErrorHandler>();
|
||||
|
||||
default_handler.0 = error_handler;
|
||||
self
|
||||
}
|
||||
|
||||
/// See [`App::add_event`].
|
||||
pub fn add_event<T>(&mut self) -> &mut Self
|
||||
where
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
use crate::{App, Plugin};
|
||||
|
||||
use alloc::string::ToString;
|
||||
use bevy_platform_support::sync::Arc;
|
||||
use bevy_platform::sync::Arc;
|
||||
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use core::fmt::Debug;
|
||||
use log::trace;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(all(target_arch = "wasm32", feature = "web")))] {
|
||||
use {crate::Last, bevy_ecs::prelude::NonSend, bevy_tasks::tick_global_task_pools_on_main_thread};
|
||||
use {crate::Last, bevy_tasks::tick_global_task_pools_on_main_thread};
|
||||
use bevy_ecs::system::NonSendMarker;
|
||||
|
||||
/// A system used to check and advanced our task pools.
|
||||
///
|
||||
/// Calls [`tick_global_task_pools_on_main_thread`],
|
||||
/// and uses [`NonSendMarker`] to ensure that this system runs on the main thread
|
||||
fn tick_global_task_pools(_main_thread_marker: Option<NonSend<NonSendMarker>>) {
|
||||
fn tick_global_task_pools(_main_thread_marker: NonSendMarker) {
|
||||
tick_global_task_pools_on_main_thread();
|
||||
}
|
||||
}
|
||||
@ -36,8 +37,6 @@ impl Plugin for TaskPoolPlugin {
|
||||
_app.add_systems(Last, tick_global_task_pools);
|
||||
}
|
||||
}
|
||||
/// A dummy type that is [`!Send`](Send), to force systems to run on the main thread.
|
||||
pub struct NonSendMarker(PhantomData<*mut ()>);
|
||||
|
||||
/// Defines a simple way to determine how many threads to use given the number of remaining cores
|
||||
/// and number of total cores
|
||||
|
||||
@ -11,7 +11,7 @@ keywords = ["bevy"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
file_watcher = ["notify-debouncer-full", "watch"]
|
||||
file_watcher = ["notify-debouncer-full", "watch", "multi_threaded"]
|
||||
embedded_watcher = ["file_watcher"]
|
||||
multi_threaded = ["bevy_tasks/multi_threaded"]
|
||||
asset_processor = []
|
||||
@ -19,39 +19,50 @@ watch = []
|
||||
trace = []
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.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" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
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 = [
|
||||
"uuid",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.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 = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
stackfuture = "0.3"
|
||||
atomicow = "1.0"
|
||||
async-broadcast = "0.7.2"
|
||||
async-fs = "2.0"
|
||||
async-lock = "3.0"
|
||||
bitflags = { version = "2.3", features = ["serde"] }
|
||||
crossbeam-channel = "0.5"
|
||||
downcast-rs = { version = "2", default-features = false, features = ["std"] }
|
||||
disqualified = "1.0"
|
||||
either = "1.13"
|
||||
futures-io = "0.3"
|
||||
futures-lite = "2.0.1"
|
||||
blake3 = "1.5"
|
||||
parking_lot = { version = "0.12", features = ["arc_lock", "send_guard"] }
|
||||
ron = "0.8"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
stackfuture = { version = "0.3", default-features = false }
|
||||
atomicow = { version = "1.1", default-features = false, features = ["std"] }
|
||||
async-broadcast = { version = "0.7.2", default-features = false }
|
||||
async-fs = { version = "2.0", default-features = false }
|
||||
async-lock = { version = "3.0", default-features = false }
|
||||
bitflags = { version = "2.3", default-features = false }
|
||||
crossbeam-channel = { version = "0.5", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
downcast-rs = { version = "2", default-features = false }
|
||||
disqualified = { version = "1.0", default-features = false }
|
||||
either = { version = "1.13", default-features = false }
|
||||
futures-io = { version = "0.3", default-features = false }
|
||||
futures-lite = { version = "2.0.1", default-features = false }
|
||||
blake3 = { version = "1.5", default-features = false }
|
||||
parking_lot = { version = "0.12", default-features = false, features = [
|
||||
"arc_lock",
|
||||
"send_guard",
|
||||
] }
|
||||
ron = { version = "0.8", 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"] }
|
||||
uuid = { version = "1.13.1", features = ["v4"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
uuid = { version = "1.13.1", default-features = false, features = [
|
||||
"v4",
|
||||
"serde",
|
||||
] }
|
||||
tracing = { version = "0.1", default-features = false }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
@ -78,7 +89,7 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-featu
|
||||
] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
notify-debouncer-full = { version = "0.5.0", optional = true }
|
||||
notify-debouncer-full = { version = "0.5.0", default-features = false, optional = true }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -13,7 +13,7 @@ use bevy_ecs::{
|
||||
storage::{Table, TableRow},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use core::marker::PhantomData;
|
||||
use disqualified::ShortName;
|
||||
use tracing::error;
|
||||
@ -22,10 +22,10 @@ use tracing::error;
|
||||
/// the [`AssetChanged`] filter to determine if an asset has changed since the last time
|
||||
/// a query ran.
|
||||
///
|
||||
/// This resource is automatically managed by the [`AssetEvents`](crate::AssetEvents) schedule and
|
||||
/// should not be exposed to the user in order to maintain safety guarantees. Any additional uses of
|
||||
/// this resource should be carefully audited to ensure that they do not introduce any safety
|
||||
/// issues.
|
||||
/// This resource is automatically managed by the [`AssetEventSystems`](crate::AssetEventSystems)
|
||||
/// system set and should not be exposed to the user in order to maintain safety guarantees.
|
||||
/// Any additional uses of this resource should be carefully audited to ensure that they do not
|
||||
/// introduce any safety issues.
|
||||
#[derive(Resource)]
|
||||
pub(crate) struct AssetChanges<A: Asset> {
|
||||
change_ticks: HashMap<AssetId<A>, Tick>,
|
||||
@ -102,14 +102,13 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
|
||||
///
|
||||
/// # Quirks
|
||||
///
|
||||
/// - Asset changes are registered in the [`AssetEvents`] schedule.
|
||||
/// - Asset changes are registered in the [`AssetEventSystems`] system set.
|
||||
/// - Removed assets are not detected.
|
||||
///
|
||||
/// The list of changed assets only gets updated in the
|
||||
/// [`AssetEvents`] schedule which runs in `Last`. Therefore, `AssetChanged`
|
||||
/// will only pick up asset changes in schedules following `AssetEvents` or the
|
||||
/// next frame. Consider adding the system in the `Last` schedule after [`AssetEvents`] if you need
|
||||
/// to react without frame delay to asset changes.
|
||||
/// The list of changed assets only gets updated in the [`AssetEventSystems`] system set,
|
||||
/// 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.
|
||||
///
|
||||
/// # Performance
|
||||
///
|
||||
@ -120,7 +119,7 @@ impl<'w, A: AsAssetId> AssetChangeCheck<'w, A> {
|
||||
///
|
||||
/// If no `A` asset updated since the last time the system ran, then no lookups occur.
|
||||
///
|
||||
/// [`AssetEvents`]: crate::AssetEvents
|
||||
/// [`AssetEventSystems`]: crate::AssetEventSystems
|
||||
/// [`Assets<Mesh>::get_mut`]: crate::Assets::get_mut
|
||||
pub struct AssetChanged<A: AsAssetId>(PhantomData<A>);
|
||||
|
||||
@ -166,7 +165,7 @@ unsafe impl<A: AsAssetId> WorldQuery for AssetChanged<A> {
|
||||
this_run: Tick,
|
||||
) -> Self::Fetch<'w> {
|
||||
// SAFETY:
|
||||
// - `AssetChanges` is private and only accessed mutably in the `AssetEvents` schedule
|
||||
// - `AssetChanges` is private and only accessed mutably in the `AssetEventSystems` system set.
|
||||
// - `resource_id` was obtained from the type ID of `AssetChanges<A::Asset>`.
|
||||
let Some(changes) = (unsafe {
|
||||
world
|
||||
@ -283,7 +282,7 @@ unsafe impl<A: AsAssetId> QueryFilter for AssetChanged<A> {
|
||||
#[cfg(test)]
|
||||
#[expect(clippy::print_stdout, reason = "Allowed in tests.")]
|
||||
mod tests {
|
||||
use crate::{AssetEvents, AssetPlugin, Handle};
|
||||
use crate::{AssetEventSystems, AssetPlugin, Handle};
|
||||
use alloc::{vec, vec::Vec};
|
||||
use core::num::NonZero;
|
||||
use std::println;
|
||||
@ -406,7 +405,7 @@ mod tests {
|
||||
.init_asset::<MyAsset>()
|
||||
.insert_resource(Counter(vec![0, 0, 0, 0]))
|
||||
.add_systems(Update, add_some)
|
||||
.add_systems(PostUpdate, count_update.after(AssetEvents));
|
||||
.add_systems(PostUpdate, count_update.after(AssetEventSystems));
|
||||
|
||||
// First run of the app, `add_systems(Startup…)` runs.
|
||||
app.update(); // run_count == 0
|
||||
@ -441,7 +440,7 @@ mod tests {
|
||||
},
|
||||
)
|
||||
.add_systems(Update, update_some)
|
||||
.add_systems(PostUpdate, count_update.after(AssetEvents));
|
||||
.add_systems(PostUpdate, count_update.after(AssetEventSystems));
|
||||
|
||||
// First run of the app, `add_systems(Startup…)` runs.
|
||||
app.update(); // run_count == 0
|
||||
|
||||
@ -6,7 +6,7 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
system::{Res, ResMut, SystemChangeTick},
|
||||
};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::{Reflect, TypePath};
|
||||
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
@ -462,16 +462,22 @@ impl<A: Asset> Assets<A> {
|
||||
/// Removes the [`Asset`] with the given `id`.
|
||||
pub(crate) fn remove_dropped(&mut self, id: AssetId<A>) {
|
||||
match self.duplicate_handles.get_mut(&id) {
|
||||
None | Some(0) => {}
|
||||
None => {}
|
||||
Some(0) => {
|
||||
self.duplicate_handles.remove(&id);
|
||||
}
|
||||
Some(value) => {
|
||||
*value -= 1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let existed = match id {
|
||||
AssetId::Index { index, .. } => self.dense_storage.remove_dropped(index).is_some(),
|
||||
AssetId::Uuid { uuid } => self.hash_map.remove(&uuid).is_some(),
|
||||
};
|
||||
|
||||
self.queued_events.push(AssetEvent::Unused { id });
|
||||
if existed {
|
||||
self.queued_events.push(AssetEvent::Removed { id });
|
||||
}
|
||||
@ -553,7 +559,6 @@ impl<A: Asset> Assets<A> {
|
||||
}
|
||||
}
|
||||
|
||||
assets.queued_events.push(AssetEvent::Unused { id });
|
||||
assets.remove_dropped(id);
|
||||
}
|
||||
}
|
||||
@ -595,7 +600,7 @@ impl<A: Asset> Assets<A> {
|
||||
pub struct AssetsMutIterator<'a, A: Asset> {
|
||||
queued_events: &'a mut Vec<AssetEvent<A>>,
|
||||
dense_storage: Enumerate<core::slice::IterMut<'a, Entry<A>>>,
|
||||
hash_map: bevy_platform_support::collections::hash_map::IterMut<'a, Uuid, A>,
|
||||
hash_map: bevy_platform::collections::hash_map::IterMut<'a, Uuid, A>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
|
||||
|
||||
@ -11,7 +11,6 @@ use core::{
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use disqualified::ShortName;
|
||||
use thiserror::Error;
|
||||
use uuid::Uuid;
|
||||
|
||||
/// Provides [`Handle`] and [`UntypedHandle`] _for a specific asset type_.
|
||||
/// This should _only_ be used for one specific asset type.
|
||||
@ -149,14 +148,6 @@ impl<T: Asset> Clone for Handle<T> {
|
||||
}
|
||||
|
||||
impl<A: Asset> Handle<A> {
|
||||
/// Create a new [`Handle::Weak`] with the given [`u128`] encoding of a [`Uuid`].
|
||||
#[deprecated = "use the `weak_handle!` macro with a UUID string instead"]
|
||||
pub const fn weak_from_u128(value: u128) -> Self {
|
||||
Handle::Weak(AssetId::Uuid {
|
||||
uuid: Uuid::from_u128(value),
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the [`AssetId`] of this [`Asset`].
|
||||
#[inline]
|
||||
pub fn id(&self) -> AssetId<A> {
|
||||
@ -289,7 +280,7 @@ impl<A: Asset> From<&mut Handle<A>> for UntypedAssetId {
|
||||
/// to be stored together and compared.
|
||||
///
|
||||
/// See [`Handle`] for more information.
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Reflect)]
|
||||
pub enum UntypedHandle {
|
||||
/// A strong handle, which will keep the referenced [`Asset`] alive until all strong handles are dropped.
|
||||
Strong(Arc<StrongHandle>),
|
||||
@ -548,9 +539,10 @@ pub enum UntypedAssetConversionError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::boxed::Box;
|
||||
use bevy_platform_support::hash::FixedHasher;
|
||||
use bevy_platform::hash::FixedHasher;
|
||||
use bevy_reflect::PartialReflect;
|
||||
use core::hash::BuildHasher;
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::*;
|
||||
|
||||
|
||||
@ -164,7 +164,7 @@ impl<A: Asset> From<AssetIndex> for AssetId<A> {
|
||||
/// An "untyped" / "generic-less" [`Asset`] identifier that behaves much like [`AssetId`], but stores the [`Asset`] type
|
||||
/// information at runtime instead of compile-time. This increases the size of the type, but it enables storing asset ids
|
||||
/// across asset types together and enables comparisons between them.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[derive(Debug, Copy, Clone, Reflect)]
|
||||
pub enum UntypedAssetId {
|
||||
/// A small / efficient runtime identifier that can be used to efficiently look up an asset stored in [`Assets`]. This is
|
||||
/// the "default" identifier used for assets. The alternative(s) (ex: [`UntypedAssetId::Uuid`]) will only be used if assets are
|
||||
@ -441,7 +441,7 @@ mod tests {
|
||||
fn hash<T: Hash>(data: &T) -> u64 {
|
||||
use core::hash::BuildHasher;
|
||||
|
||||
bevy_platform_support::hash::FixedHasher.hash_one(data)
|
||||
bevy_platform::hash::FixedHasher.hash_one(data)
|
||||
}
|
||||
|
||||
/// Typed and Untyped `AssetIds` should be equivalent to each other and themselves
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::io::{
|
||||
AssetSourceEvent, AssetWatcher,
|
||||
};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use core::time::Duration;
|
||||
use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache};
|
||||
use parking_lot::RwLock;
|
||||
|
||||
@ -8,8 +8,10 @@ use crate::io::{
|
||||
memory::{Dir, MemoryAssetReader, Value},
|
||||
AssetSource, AssetSourceBuilders,
|
||||
};
|
||||
use crate::AssetServer;
|
||||
use alloc::boxed::Box;
|
||||
use bevy_ecs::resource::Resource;
|
||||
use bevy_app::App;
|
||||
use bevy_ecs::{resource::Resource, world::World};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
#[cfg(feature = "embedded_watcher")]
|
||||
@ -29,7 +31,7 @@ pub struct EmbeddedAssetRegistry {
|
||||
dir: Dir,
|
||||
#[cfg(feature = "embedded_watcher")]
|
||||
root_paths: alloc::sync::Arc<
|
||||
parking_lot::RwLock<bevy_platform_support::collections::HashMap<Box<Path>, PathBuf>>,
|
||||
parking_lot::RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>,
|
||||
>,
|
||||
}
|
||||
|
||||
@ -132,6 +134,71 @@ impl EmbeddedAssetRegistry {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for the [`load_embedded_asset!`] macro, to access [`AssetServer`]
|
||||
/// from arbitrary things.
|
||||
///
|
||||
/// [`load_embedded_asset!`]: crate::load_embedded_asset
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
/// Load an [embedded asset](crate::embedded_asset).
|
||||
///
|
||||
/// This is useful if the embedded asset in question is not publicly exposed, but
|
||||
/// you need to use it internally.
|
||||
///
|
||||
/// # Syntax
|
||||
///
|
||||
/// This macro takes two arguments and an optional third one:
|
||||
/// 1. The asset source. It may be `AssetServer`, `World` or `App`.
|
||||
/// 2. The path to the asset to embed, as a string literal.
|
||||
/// 3. Optionally, a closure of the same type as in [`AssetServer::load_with_settings`].
|
||||
/// Consider explicitly typing the closure argument in case of type error.
|
||||
///
|
||||
/// # Usage
|
||||
///
|
||||
/// The advantage compared to using directly [`AssetServer::load`] is:
|
||||
/// - This also accepts [`World`] and [`App`] arguments.
|
||||
/// - This uses the exact same path as `embedded_asset!`, so you can keep it
|
||||
/// consistent.
|
||||
///
|
||||
/// As a rule of thumb:
|
||||
/// - If the asset in used in the same module as it is declared using `embedded_asset!`,
|
||||
/// use this macro.
|
||||
/// - Otherwise, use `AssetServer::load`.
|
||||
#[macro_export]
|
||||
macro_rules! load_embedded_asset {
|
||||
(@get: $path: literal, $provider: expr) => {{
|
||||
let path = $crate::embedded_path!($path);
|
||||
let path = $crate::AssetPath::from_path_buf(path).with_source("embedded");
|
||||
let asset_server = $crate::io::embedded::GetAssetServer::get_asset_server($provider);
|
||||
(path, asset_server)
|
||||
}};
|
||||
($provider: expr, $path: literal, $settings: expr) => {{
|
||||
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
|
||||
asset_server.load_with_settings(path, $settings)
|
||||
}};
|
||||
($provider: expr, $path: literal) => {{
|
||||
let (path, asset_server) = $crate::load_embedded_asset!(@get: $path, $provider);
|
||||
asset_server.load(path)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Returns the [`Path`] for a given `embedded` asset.
|
||||
/// This is used internally by [`embedded_asset`] and can be used to get a [`Path`]
|
||||
/// that matches the [`AssetPath`](crate::AssetPath) used by that asset.
|
||||
@ -140,7 +207,7 @@ impl EmbeddedAssetRegistry {
|
||||
#[macro_export]
|
||||
macro_rules! embedded_path {
|
||||
($path_str: expr) => {{
|
||||
embedded_path!("src", $path_str)
|
||||
$crate::embedded_path!("src", $path_str)
|
||||
}};
|
||||
|
||||
($source_path: expr, $path_str: expr) => {{
|
||||
@ -168,6 +235,13 @@ pub fn _embedded_asset_path(
|
||||
file_path: &Path,
|
||||
asset_path: &Path,
|
||||
) -> PathBuf {
|
||||
let file_path = if cfg!(not(target_family = "windows")) {
|
||||
// Work around bug: https://github.com/bevyengine/bevy/issues/14246
|
||||
// Note, this will break any paths on Linux/Mac containing "\"
|
||||
PathBuf::from(file_path.to_str().unwrap().replace("\\", "/"))
|
||||
} else {
|
||||
PathBuf::from(file_path)
|
||||
};
|
||||
let mut maybe_parent = file_path.parent();
|
||||
let after_src = loop {
|
||||
let Some(parent) = maybe_parent else {
|
||||
@ -185,7 +259,7 @@ pub fn _embedded_asset_path(
|
||||
/// Creates a new `embedded` asset by embedding the bytes of the given path into the current binary
|
||||
/// and registering those bytes with the `embedded` [`AssetSource`].
|
||||
///
|
||||
/// This accepts the current [`App`](bevy_app::App) as the first parameter and a path `&str` (relative to the current file) as the second.
|
||||
/// This accepts the current [`App`] as the first parameter and a path `&str` (relative to the current file) as the second.
|
||||
///
|
||||
/// By default this will generate an [`AssetPath`] using the following rules:
|
||||
///
|
||||
@ -210,14 +284,19 @@ pub fn _embedded_asset_path(
|
||||
///
|
||||
/// `embedded_asset!(app, "rock.wgsl")`
|
||||
///
|
||||
/// `rock.wgsl` can now be loaded by the [`AssetServer`](crate::AssetServer) with the following path:
|
||||
/// `rock.wgsl` can now be loaded by the [`AssetServer`] as follows:
|
||||
///
|
||||
/// ```no_run
|
||||
/// # use bevy_asset::{Asset, AssetServer};
|
||||
/// # use bevy_asset::{Asset, AssetServer, load_embedded_asset};
|
||||
/// # use bevy_reflect::TypePath;
|
||||
/// # let asset_server: AssetServer = panic!();
|
||||
/// # #[derive(Asset, TypePath)]
|
||||
/// # struct Shader;
|
||||
/// // If we are loading the shader in the same module we used `embedded_asset!`:
|
||||
/// let shader = load_embedded_asset!(&asset_server, "rock.wgsl");
|
||||
/// # let _: bevy_asset::Handle<Shader> = shader;
|
||||
///
|
||||
/// // If the goal is to expose the asset **to the end user**:
|
||||
/// let shader = asset_server.load::<Shader>("embedded://bevy_rock/render/rock.wgsl");
|
||||
/// ```
|
||||
///
|
||||
@ -251,11 +330,11 @@ pub fn _embedded_asset_path(
|
||||
/// [`embedded_path`]: crate::embedded_path
|
||||
#[macro_export]
|
||||
macro_rules! embedded_asset {
|
||||
($app: ident, $path: expr) => {{
|
||||
($app: expr, $path: expr) => {{
|
||||
$crate::embedded_asset!($app, "src", $path)
|
||||
}};
|
||||
|
||||
($app: ident, $source_path: expr, $path: expr) => {{
|
||||
($app: expr, $source_path: expr, $path: expr) => {{
|
||||
let mut embedded = $app
|
||||
.world_mut()
|
||||
.resource_mut::<$crate::io::embedded::EmbeddedAssetRegistry>();
|
||||
|
||||
@ -74,6 +74,15 @@ impl AssetReader for FileAssetReader {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// filter out hidden files. they are not listed by default but are directly targetable
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.map(|file_name| file_name.starts_with('.'))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let relative_path = path.strip_prefix(&root_path).unwrap();
|
||||
Some(relative_path.to_owned())
|
||||
})
|
||||
|
||||
@ -35,7 +35,7 @@ impl FileWatcher {
|
||||
sender: Sender<AssetSourceEvent>,
|
||||
debounce_wait_time: Duration,
|
||||
) -> Result<Self, notify::Error> {
|
||||
let root = normalize_path(&path).canonicalize().unwrap();
|
||||
let root = normalize_path(&path).canonicalize()?;
|
||||
let watcher = new_asset_event_debouncer(
|
||||
path.clone(),
|
||||
debounce_wait_time,
|
||||
@ -262,7 +262,7 @@ impl FilesystemEventHandler for FileEventHandler {
|
||||
self.last_event = None;
|
||||
}
|
||||
fn get_path(&self, absolute_path: &Path) -> Option<(PathBuf, bool)> {
|
||||
let absolute_path = absolute_path.canonicalize().unwrap();
|
||||
let absolute_path = absolute_path.canonicalize().ok()?;
|
||||
Some(get_asset_path(&self.root, &absolute_path))
|
||||
}
|
||||
|
||||
|
||||
@ -145,6 +145,16 @@ impl AssetReader for FileAssetReader {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// filter out hidden files. they are not listed by default but are directly targetable
|
||||
if path
|
||||
.file_name()
|
||||
.and_then(|file_name| file_name.to_str())
|
||||
.map(|file_name| file_name.starts_with('.'))
|
||||
.unwrap_or_default()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let relative_path = path.strip_prefix(&root_path).unwrap();
|
||||
Some(relative_path.to_owned())
|
||||
})
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
use parking_lot::RwLock;
|
||||
use std::path::Path;
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc, vec::Vec};
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use core::{pin::Pin, task::Poll};
|
||||
use futures_io::AsyncRead;
|
||||
use futures_lite::{ready, Stream};
|
||||
@ -60,8 +60,7 @@ impl Dir {
|
||||
dir = self.get_or_insert_dir(parent);
|
||||
}
|
||||
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
|
||||
let data = dir.0.write().assets.remove(&key);
|
||||
data
|
||||
dir.0.write().assets.remove(&key)
|
||||
}
|
||||
|
||||
pub fn insert_meta(&self, path: &Path, value: impl Into<Value>) {
|
||||
|
||||
@ -9,7 +9,7 @@ use alloc::{
|
||||
};
|
||||
use atomicow::CowArc;
|
||||
use bevy_ecs::resource::Resource;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use core::{fmt::Display, hash::Hash, time::Duration};
|
||||
use thiserror::Error;
|
||||
use tracing::{error, warn};
|
||||
|
||||
@ -52,7 +52,7 @@ fn js_value_to_err(context: &str) -> impl FnOnce(JsValue) -> std::io::Error + '_
|
||||
}
|
||||
|
||||
impl HttpWasmAssetReader {
|
||||
async fn fetch_bytes<'a>(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
|
||||
async fn fetch_bytes(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
|
||||
// The JS global scope includes a self-reference via a specializing name, which can be used to determine the type of global context available.
|
||||
let global: Global = js_sys::global().unchecked_into();
|
||||
let promise = if !global.window().is_undefined() {
|
||||
|
||||
@ -223,18 +223,11 @@ use bevy_ecs::{
|
||||
schedule::{IntoScheduleConfigs, SystemSet},
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
|
||||
use core::any::TypeId;
|
||||
use tracing::error;
|
||||
|
||||
#[cfg(all(feature = "file_watcher", not(feature = "multi_threaded")))]
|
||||
compile_error!(
|
||||
"The \"file_watcher\" feature for hot reloading requires the \
|
||||
\"multi_threaded\" feature to be functional.\n\
|
||||
Consider either disabling the \"file_watcher\" feature or enabling \"multi_threaded\""
|
||||
);
|
||||
|
||||
/// Provides "asset" loading and processing functionality. An [`Asset`] is a "runtime value" that is loaded from an [`AssetSource`],
|
||||
/// which can be something like a filesystem, a network, etc.
|
||||
///
|
||||
@ -258,6 +251,33 @@ pub struct AssetPlugin {
|
||||
pub mode: AssetMode,
|
||||
/// How/If asset meta files should be checked.
|
||||
pub meta_check: AssetMetaCheck,
|
||||
/// How to handle load requests of files that are outside the approved directories.
|
||||
///
|
||||
/// Approved folders are [`AssetPlugin::file_path`] and the folder of each
|
||||
/// [`AssetSource`](io::AssetSource). Subfolders within these folders are also valid.
|
||||
pub unapproved_path_mode: UnapprovedPathMode,
|
||||
}
|
||||
|
||||
/// Determines how to react to attempts to load assets not inside the approved folders.
|
||||
///
|
||||
/// Approved folders are [`AssetPlugin::file_path`] and the folder of each
|
||||
/// [`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
|
||||
/// access for malicious code.
|
||||
///
|
||||
/// See [`AssetPath::is_unapproved`](crate::AssetPath::is_unapproved)
|
||||
#[derive(Clone, Default)]
|
||||
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
|
||||
/// [`AssetServer::load_override`].
|
||||
Deny,
|
||||
/// Fails to load any asset that is is unapproved.
|
||||
#[default]
|
||||
Forbid,
|
||||
}
|
||||
|
||||
/// Controls whether or not assets are pre-processed before being loaded.
|
||||
@ -311,6 +331,7 @@ impl Default for AssetPlugin {
|
||||
processed_file_path: Self::DEFAULT_PROCESSED_FILE_PATH.to_string(),
|
||||
watch_for_changes_override: None,
|
||||
meta_check: AssetMetaCheck::default(),
|
||||
unapproved_path_mode: UnapprovedPathMode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -351,6 +372,7 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Unprocessed,
|
||||
self.meta_check.clone(),
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
));
|
||||
}
|
||||
AssetMode::Processed => {
|
||||
@ -367,6 +389,7 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
))
|
||||
.insert_resource(processor)
|
||||
.add_systems(bevy_app::Startup, AssetProcessor::start);
|
||||
@ -380,6 +403,7 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -390,7 +414,10 @@ impl Plugin for AssetPlugin {
|
||||
.init_asset::<LoadedUntypedAsset>()
|
||||
.init_asset::<()>()
|
||||
.add_event::<UntypedAssetLoadFailedEvent>()
|
||||
.configure_sets(PreUpdate, TrackAssets.after(handle_internal_asset_events))
|
||||
.configure_sets(
|
||||
PreUpdate,
|
||||
AssetTrackingSystems.after(handle_internal_asset_events),
|
||||
)
|
||||
// `handle_internal_asset_events` requires the use of `&mut World`,
|
||||
// and as a result has ambiguous system ordering with all other systems in `PreUpdate`.
|
||||
// This is virtually never a real problem: asset loading is async and so anything that interacts directly with it
|
||||
@ -587,9 +614,12 @@ impl AssetApp for App {
|
||||
PostUpdate,
|
||||
Assets::<A>::asset_events
|
||||
.run_if(Assets::<A>::asset_events_condition)
|
||||
.in_set(AssetEvents),
|
||||
.in_set(AssetEventSystems),
|
||||
)
|
||||
.add_systems(
|
||||
PreUpdate,
|
||||
Assets::<A>::track_assets.in_set(AssetTrackingSystems),
|
||||
)
|
||||
.add_systems(PreUpdate, Assets::<A>::track_assets.in_set(TrackAssets))
|
||||
}
|
||||
|
||||
fn register_asset_reflect<A>(&mut self) -> &mut Self
|
||||
@ -619,13 +649,21 @@ impl AssetApp for App {
|
||||
|
||||
/// A system set that holds all "track asset" operations.
|
||||
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
|
||||
pub struct TrackAssets;
|
||||
pub struct AssetTrackingSystems;
|
||||
|
||||
/// Deprecated alias for [`AssetTrackingSystems`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `AssetTrackingSystems`.")]
|
||||
pub type TrackAssets = AssetTrackingSystems;
|
||||
|
||||
/// A system set where events accumulated in [`Assets`] are applied to the [`AssetEvent`] [`Events`] resource.
|
||||
///
|
||||
/// [`Events`]: bevy_ecs::event::Events
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||
pub struct AssetEvents;
|
||||
pub struct AssetEventSystems;
|
||||
|
||||
/// Deprecated alias for [`AssetEventSystems`].
|
||||
#[deprecated(since = "0.17.0", note = "Renamed to `AssetEventSystems`.")]
|
||||
pub type AssetEvents = AssetEventSystems;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
@ -639,7 +677,7 @@ mod tests {
|
||||
},
|
||||
loader::{AssetLoader, LoadContext},
|
||||
Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
|
||||
AssetPlugin, AssetServer, Assets, DuplicateLabelAssetError, LoadState,
|
||||
AssetPlugin, AssetServer, Assets, LoadState, UnapprovedPathMode,
|
||||
};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
@ -655,8 +693,7 @@ mod tests {
|
||||
prelude::*,
|
||||
schedule::{LogLevel, ScheduleBuildSettings},
|
||||
};
|
||||
use bevy_log::LogPlugin;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_reflect::TypePath;
|
||||
use core::time::Duration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -695,8 +732,6 @@ mod tests {
|
||||
CannotLoadDependency { dependency: AssetPath<'static> },
|
||||
#[error("A RON error occurred during loading")]
|
||||
RonSpannedError(#[from] ron::error::SpannedError),
|
||||
#[error(transparent)]
|
||||
DuplicateLabelAssetError(#[from] DuplicateLabelAssetError),
|
||||
#[error("An IO error occurred during loading")]
|
||||
Io(#[from] std::io::Error),
|
||||
}
|
||||
@ -727,7 +762,7 @@ mod tests {
|
||||
.map_err(|_| Self::Error::CannotLoadDependency {
|
||||
dependency: dep.into(),
|
||||
})?;
|
||||
let cool = loaded.get_asset().get();
|
||||
let cool = loaded.get();
|
||||
embedded.push_str(&cool.text);
|
||||
}
|
||||
Ok(CoolText {
|
||||
@ -742,7 +777,7 @@ mod tests {
|
||||
.sub_texts
|
||||
.drain(..)
|
||||
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
|
||||
@ -826,11 +861,7 @@ mod tests {
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build().with_reader(move || Box::new(gated_memory_reader.clone())),
|
||||
)
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
));
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
|
||||
(app, gate_opener)
|
||||
}
|
||||
|
||||
@ -1728,11 +1759,7 @@ mod tests {
|
||||
"unstable",
|
||||
AssetSource::build().with_reader(move || Box::new(unstable_reader.clone())),
|
||||
)
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
))
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
|
||||
.init_asset::<CoolText>()
|
||||
.register_asset_loader(CoolTextLoader)
|
||||
.init_resource::<ErrorTracker>()
|
||||
@ -1780,49 +1807,6 @@ mod tests {
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fails_to_load_for_duplicate_subasset_labels() {
|
||||
let mut app = App::new();
|
||||
|
||||
let dir = Dir::default();
|
||||
dir.insert_asset_text(
|
||||
Path::new("a.ron"),
|
||||
r#"(
|
||||
text: "b",
|
||||
dependencies: [],
|
||||
embedded_dependencies: [],
|
||||
sub_texts: ["A", "A"],
|
||||
)"#,
|
||||
);
|
||||
|
||||
app.register_asset_source(
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build()
|
||||
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
|
||||
)
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
));
|
||||
|
||||
app.init_asset::<CoolText>()
|
||||
.init_asset::<SubText>()
|
||||
.register_asset_loader(CoolTextLoader);
|
||||
|
||||
let asset_server = app.world().resource::<AssetServer>().clone();
|
||||
let handle = asset_server.load::<CoolText>("a.ron");
|
||||
|
||||
run_app_until(&mut app, |_world| match asset_server.load_state(&handle) {
|
||||
LoadState::Loading => None,
|
||||
LoadState::Failed(err) => {
|
||||
assert!(matches!(*err, AssetLoadError::AssetLoaderError(_)));
|
||||
Some(())
|
||||
}
|
||||
state => panic!("Unexpected asset state: {state:?}"),
|
||||
});
|
||||
}
|
||||
|
||||
// This test is not checking a requirement, but documenting a current limitation. We simply are
|
||||
// not capable of loading subassets when doing nested immediate loads.
|
||||
#[test]
|
||||
@ -1846,11 +1830,7 @@ mod tests {
|
||||
AssetSource::build()
|
||||
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
|
||||
)
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
));
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
|
||||
|
||||
app.init_asset::<CoolText>()
|
||||
.init_asset::<SubText>()
|
||||
@ -1933,4 +1913,91 @@ mod tests {
|
||||
|
||||
#[derive(Asset, TypePath)]
|
||||
pub struct TupleTestAsset(#[dependency] Handle<TestAsset>);
|
||||
|
||||
fn unapproved_path_setup(mode: UnapprovedPathMode) -> App {
|
||||
let dir = Dir::default();
|
||||
let a_path = "../a.cool.ron";
|
||||
let a_ron = r#"
|
||||
(
|
||||
text: "a",
|
||||
dependencies: [],
|
||||
embedded_dependencies: [],
|
||||
sub_texts: [],
|
||||
)"#;
|
||||
|
||||
dir.insert_asset_text(Path::new(a_path), a_ron);
|
||||
|
||||
let mut app = App::new();
|
||||
let memory_reader = MemoryAssetReader { root: dir };
|
||||
app.register_asset_source(
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build().with_reader(move || Box::new(memory_reader.clone())),
|
||||
)
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
AssetPlugin {
|
||||
unapproved_path_mode: mode,
|
||||
..Default::default()
|
||||
},
|
||||
));
|
||||
app.init_asset::<CoolText>();
|
||||
|
||||
app
|
||||
}
|
||||
|
||||
fn load_a_asset(assets: Res<AssetServer>) {
|
||||
let a = assets.load::<CoolText>("../a.cool.ron");
|
||||
if a == Handle::default() {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
fn load_a_asset_override(assets: Res<AssetServer>) {
|
||||
let a = assets.load_override::<CoolText>("../a.cool.ron");
|
||||
if a == Handle::default() {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn unapproved_path_forbid_should_panic() {
|
||||
let mut app = unapproved_path_setup(UnapprovedPathMode::Forbid);
|
||||
|
||||
fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
|
||||
app.add_systems(Update, (uses_assets, load_a_asset_override));
|
||||
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn unapproved_path_deny_should_panic() {
|
||||
let mut app = unapproved_path_setup(UnapprovedPathMode::Deny);
|
||||
|
||||
fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
|
||||
app.add_systems(Update, (uses_assets, load_a_asset));
|
||||
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unapproved_path_deny_should_finish() {
|
||||
let mut app = unapproved_path_setup(UnapprovedPathMode::Deny);
|
||||
|
||||
fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
|
||||
app.add_systems(Update, (uses_assets, load_a_asset_override));
|
||||
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unapproved_path_allow_should_finish() {
|
||||
let mut app = unapproved_path_setup(UnapprovedPathMode::Allow);
|
||||
|
||||
fn uses_assets(_asset: ResMut<Assets<CoolText>>) {}
|
||||
app.add_systems(Update, (uses_assets, load_a_asset));
|
||||
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,7 @@ use alloc::{
|
||||
};
|
||||
use atomicow::CowArc;
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
|
||||
use core::any::{Any, TypeId};
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
@ -60,7 +60,7 @@ pub trait ErasedAssetLoader: Send + Sync + 'static {
|
||||
load_context: LoadContext<'a>,
|
||||
) -> BoxedFuture<
|
||||
'a,
|
||||
Result<CompleteErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
>;
|
||||
|
||||
/// Returns a list of extensions supported by this asset loader, without the preceding dot.
|
||||
@ -91,7 +91,7 @@ where
|
||||
mut load_context: LoadContext<'a>,
|
||||
) -> BoxedFuture<
|
||||
'a,
|
||||
Result<CompleteErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
Result<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
> {
|
||||
Box::pin(async move {
|
||||
let settings = meta
|
||||
@ -152,6 +152,7 @@ pub struct LoadedAsset<A: Asset> {
|
||||
pub(crate) value: A,
|
||||
pub(crate) dependencies: HashSet<UntypedAssetId>,
|
||||
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<A: Asset> LoadedAsset<A> {
|
||||
@ -165,6 +166,7 @@ impl<A: Asset> LoadedAsset<A> {
|
||||
value,
|
||||
dependencies,
|
||||
loader_dependencies: HashMap::default(),
|
||||
labeled_assets: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,6 +179,19 @@ impl<A: Asset> LoadedAsset<A> {
|
||||
pub fn get(&self) -> &A {
|
||||
&self.value
|
||||
}
|
||||
|
||||
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
|
||||
pub fn get_labeled(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<&ErasedLoadedAsset> {
|
||||
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
|
||||
}
|
||||
|
||||
/// Iterate over all labels for "labeled assets" in the loaded asset
|
||||
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asset> From<A> for LoadedAsset<A> {
|
||||
@ -190,6 +205,7 @@ pub struct ErasedLoadedAsset {
|
||||
pub(crate) value: Box<dyn AssetContainer>,
|
||||
pub(crate) dependencies: HashSet<UntypedAssetId>,
|
||||
pub(crate) loader_dependencies: HashMap<AssetPath<'static>, AssetHash>,
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
|
||||
@ -198,6 +214,7 @@ impl<A: Asset> From<LoadedAsset<A>> for ErasedLoadedAsset {
|
||||
value: Box::new(asset.value),
|
||||
dependencies: asset.dependencies,
|
||||
loader_dependencies: asset.loader_dependencies,
|
||||
labeled_assets: asset.labeled_assets,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -224,6 +241,19 @@ impl ErasedLoadedAsset {
|
||||
self.value.asset_type_name()
|
||||
}
|
||||
|
||||
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
|
||||
pub fn get_labeled(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<&ErasedLoadedAsset> {
|
||||
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
|
||||
}
|
||||
|
||||
/// Iterate over all labels for "labeled assets" in the loaded asset
|
||||
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
|
||||
/// Cast this loaded asset as the given type. If the type does not match,
|
||||
/// the original type-erased asset is returned.
|
||||
pub fn downcast<A: Asset>(mut self) -> Result<LoadedAsset<A>, ErasedLoadedAsset> {
|
||||
@ -232,6 +262,7 @@ impl ErasedLoadedAsset {
|
||||
value: *value,
|
||||
dependencies: self.dependencies,
|
||||
loader_dependencies: self.loader_dependencies,
|
||||
labeled_assets: self.labeled_assets,
|
||||
}),
|
||||
Err(value) => {
|
||||
self.value = value;
|
||||
@ -259,100 +290,6 @@ impl<A: Asset> AssetContainer for A {
|
||||
}
|
||||
}
|
||||
|
||||
/// A loaded asset and all its loaded subassets.
|
||||
pub struct CompleteLoadedAsset<A: Asset> {
|
||||
/// The loaded asset.
|
||||
pub(crate) asset: LoadedAsset<A>,
|
||||
/// The subassets by their label.
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<A: Asset> CompleteLoadedAsset<A> {
|
||||
/// Take ownership of the stored [`Asset`] value.
|
||||
pub fn take(self) -> A {
|
||||
self.asset.value
|
||||
}
|
||||
|
||||
/// Returns the stored asset.
|
||||
pub fn get_asset(&self) -> &LoadedAsset<A> {
|
||||
&self.asset
|
||||
}
|
||||
|
||||
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
|
||||
pub fn get_labeled(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<&ErasedLoadedAsset> {
|
||||
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
|
||||
}
|
||||
|
||||
/// Iterate over all labels for "labeled assets" in the loaded asset
|
||||
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A "type erased / boxed" counterpart to [`CompleteLoadedAsset`]. This is used in places where the
|
||||
/// loaded type is not statically known.
|
||||
pub struct CompleteErasedLoadedAsset {
|
||||
/// The loaded asset.
|
||||
pub(crate) asset: ErasedLoadedAsset,
|
||||
/// The subassets by their label.
|
||||
pub(crate) labeled_assets: HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl CompleteErasedLoadedAsset {
|
||||
/// Cast (and take ownership) of the [`Asset`] value of the given type. This will return
|
||||
/// [`Some`] if the stored type matches `A` and [`None`] if it does not.
|
||||
pub fn take<A: Asset>(self) -> Option<A> {
|
||||
self.asset.take()
|
||||
}
|
||||
|
||||
/// Returns the stored asset.
|
||||
pub fn get_asset(&self) -> &ErasedLoadedAsset {
|
||||
&self.asset
|
||||
}
|
||||
|
||||
/// Returns the [`ErasedLoadedAsset`] for the given label, if it exists.
|
||||
pub fn get_labeled(
|
||||
&self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
) -> Option<&ErasedLoadedAsset> {
|
||||
self.labeled_assets.get(&label.into()).map(|a| &a.asset)
|
||||
}
|
||||
|
||||
/// Iterate over all labels for "labeled assets" in the loaded asset
|
||||
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
|
||||
/// Cast this loaded asset as the given type. If the type does not match,
|
||||
/// the original type-erased asset is returned.
|
||||
pub fn downcast<A: Asset>(
|
||||
mut self,
|
||||
) -> Result<CompleteLoadedAsset<A>, CompleteErasedLoadedAsset> {
|
||||
match self.asset.downcast::<A>() {
|
||||
Ok(asset) => Ok(CompleteLoadedAsset {
|
||||
asset,
|
||||
labeled_assets: self.labeled_assets,
|
||||
}),
|
||||
Err(asset) => {
|
||||
self.asset = asset;
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: Asset> From<CompleteLoadedAsset<A>> for CompleteErasedLoadedAsset {
|
||||
fn from(value: CompleteLoadedAsset<A>) -> Self {
|
||||
Self {
|
||||
asset: value.asset.into(),
|
||||
labeled_assets: value.labeled_assets,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An error that occurs when attempting to call [`NestedLoader::load`] which
|
||||
/// is configured to work [immediately].
|
||||
///
|
||||
@ -461,11 +398,11 @@ impl<'a> LoadContext<'a> {
|
||||
&mut self,
|
||||
label: String,
|
||||
load: impl FnOnce(&mut LoadContext) -> A,
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
) -> Handle<A> {
|
||||
let mut context = self.begin_labeled_asset();
|
||||
let asset = load(&mut context);
|
||||
let complete_asset = context.finish(asset);
|
||||
self.add_loaded_labeled_asset(label, complete_asset)
|
||||
let loaded_asset = context.finish(asset);
|
||||
self.add_loaded_labeled_asset(label, loaded_asset)
|
||||
}
|
||||
|
||||
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
|
||||
@ -478,11 +415,7 @@ impl<'a> LoadContext<'a> {
|
||||
/// new [`LoadContext`] to track the dependencies for the labeled asset.
|
||||
///
|
||||
/// See [`AssetPath`] for more on labeled assets.
|
||||
pub fn add_labeled_asset<A: Asset>(
|
||||
&mut self,
|
||||
label: String,
|
||||
asset: A,
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
pub fn add_labeled_asset<A: Asset>(&mut self, label: String, asset: A) -> Handle<A> {
|
||||
self.labeled_asset_scope(label, |_| asset)
|
||||
}
|
||||
|
||||
@ -494,37 +427,22 @@ impl<'a> LoadContext<'a> {
|
||||
pub fn add_loaded_labeled_asset<A: Asset>(
|
||||
&mut self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
loaded_asset: CompleteLoadedAsset<A>,
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
loaded_asset: LoadedAsset<A>,
|
||||
) -> Handle<A> {
|
||||
let label = label.into();
|
||||
let CompleteLoadedAsset {
|
||||
asset,
|
||||
labeled_assets,
|
||||
} = loaded_asset;
|
||||
let loaded_asset: ErasedLoadedAsset = asset.into();
|
||||
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
|
||||
let labeled_path = self.asset_path.clone().with_label(label.clone());
|
||||
let handle = self
|
||||
.asset_server
|
||||
.get_or_create_path_handle(labeled_path, None);
|
||||
let has_duplicate = self
|
||||
.labeled_assets
|
||||
.insert(
|
||||
label.clone(),
|
||||
self.labeled_assets.insert(
|
||||
label,
|
||||
LabeledAsset {
|
||||
asset: loaded_asset,
|
||||
handle: handle.clone().untyped(),
|
||||
},
|
||||
)
|
||||
.is_some();
|
||||
if has_duplicate {
|
||||
return Err(DuplicateLabelAssetError(label.to_string()));
|
||||
}
|
||||
for (label, asset) in labeled_assets {
|
||||
if self.labeled_assets.insert(label.clone(), asset).is_some() {
|
||||
return Err(DuplicateLabelAssetError(label.to_string()));
|
||||
}
|
||||
}
|
||||
Ok(handle)
|
||||
);
|
||||
handle
|
||||
}
|
||||
|
||||
/// Returns `true` if an asset with the label `label` exists in this context.
|
||||
@ -536,13 +454,11 @@ impl<'a> LoadContext<'a> {
|
||||
}
|
||||
|
||||
/// "Finishes" this context by populating the final [`Asset`] value.
|
||||
pub fn finish<A: Asset>(self, value: A) -> CompleteLoadedAsset<A> {
|
||||
CompleteLoadedAsset {
|
||||
asset: LoadedAsset {
|
||||
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
|
||||
LoadedAsset {
|
||||
value,
|
||||
dependencies: self.dependencies,
|
||||
loader_dependencies: self.loader_dependencies,
|
||||
},
|
||||
labeled_assets: self.labeled_assets,
|
||||
}
|
||||
}
|
||||
@ -613,8 +529,8 @@ impl<'a> LoadContext<'a> {
|
||||
meta: &dyn AssetMetaDyn,
|
||||
loader: &dyn ErasedAssetLoader,
|
||||
reader: &mut dyn Reader,
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
let complete_asset = self
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
let loaded_asset = self
|
||||
.asset_server
|
||||
.load_with_meta_loader_and_reader(
|
||||
&path,
|
||||
@ -632,7 +548,7 @@ impl<'a> LoadContext<'a> {
|
||||
let info = meta.processed_info().as_ref();
|
||||
let hash = info.map(|i| i.full_hash).unwrap_or_default();
|
||||
self.loader_dependencies.insert(path, hash);
|
||||
Ok(complete_asset)
|
||||
Ok(loaded_asset)
|
||||
}
|
||||
|
||||
/// Create a builder for loading nested assets in this context.
|
||||
@ -674,8 +590,3 @@ pub enum ReadAssetBytesError {
|
||||
#[error("The LoadContext for this read_asset_bytes call requires hash metadata, but it was not provided. This is likely an internal implementation error.")]
|
||||
MissingAssetHash,
|
||||
}
|
||||
|
||||
/// An error when labeled assets have the same label, containing the duplicate label.
|
||||
#[derive(Error, Debug)]
|
||||
#[error("Encountered a duplicate label while loading an asset: \"{0}\"")]
|
||||
pub struct DuplicateLabelAssetError(pub String);
|
||||
|
||||
@ -4,8 +4,8 @@
|
||||
use crate::{
|
||||
io::Reader,
|
||||
meta::{meta_transform_settings, AssetMetaDyn, MetaTransform, Settings},
|
||||
Asset, AssetLoadError, AssetPath, CompleteErasedLoadedAsset, CompleteLoadedAsset,
|
||||
ErasedAssetLoader, Handle, LoadContext, LoadDirectError, LoadedUntypedAsset, UntypedHandle,
|
||||
Asset, AssetLoadError, AssetPath, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
|
||||
LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
|
||||
};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, sync::Arc};
|
||||
use core::any::TypeId;
|
||||
@ -57,11 +57,11 @@ impl ReaderRef<'_> {
|
||||
/// If you know the type ID of the asset at runtime, but not at compile time,
|
||||
/// use [`with_dynamic_type`] followed by [`load`] to start loading an asset
|
||||
/// of that type. This lets you get an [`UntypedHandle`] (via [`Deferred`]),
|
||||
/// or a [`CompleteErasedLoadedAsset`] (via [`Immediate`]).
|
||||
/// or a [`ErasedLoadedAsset`] (via [`Immediate`]).
|
||||
///
|
||||
/// - in [`UnknownTyped`]: loading either a type-erased version of the asset
|
||||
/// ([`CompleteErasedLoadedAsset`]), or a handle *to a handle* of the actual
|
||||
/// asset ([`LoadedUntypedAsset`]).
|
||||
/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset
|
||||
/// ([`LoadedUntypedAsset`]).
|
||||
///
|
||||
/// If you have no idea what type of asset you will be loading (not even at
|
||||
/// runtime with a [`TypeId`]), use this.
|
||||
@ -305,9 +305,12 @@ impl NestedLoader<'_, '_, StaticTyped, Deferred> {
|
||||
pub fn load<'c, A: Asset>(self, path: impl Into<AssetPath<'c>>) -> Handle<A> {
|
||||
let path = path.into().to_owned();
|
||||
let handle = if self.load_context.should_load_dependencies {
|
||||
self.load_context
|
||||
.asset_server
|
||||
.load_with_meta_transform(path, self.meta_transform, ())
|
||||
self.load_context.asset_server.load_with_meta_transform(
|
||||
path,
|
||||
self.meta_transform,
|
||||
(),
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
self.load_context
|
||||
.asset_server
|
||||
@ -386,7 +389,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
|
||||
self,
|
||||
path: &AssetPath<'static>,
|
||||
asset_type_id: Option<TypeId>,
|
||||
) -> Result<(Arc<dyn ErasedAssetLoader>, CompleteErasedLoadedAsset), LoadDirectError> {
|
||||
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
|
||||
if path.label().is_some() {
|
||||
return Err(LoadDirectError::RequestedSubasset(path.clone()));
|
||||
}
|
||||
@ -451,7 +454,7 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p, A: Asset>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<CompleteLoadedAsset<A>, LoadDirectError> {
|
||||
) -> Result<LoadedAsset<A>, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
self.load_internal(&path, Some(TypeId::of::<A>()))
|
||||
.await
|
||||
@ -481,7 +484,7 @@ impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
let asset_type_id = Some(self.typing.asset_type_id);
|
||||
self.load_internal(&path, asset_type_id)
|
||||
@ -497,7 +500,7 @@ impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
self.load_internal(&path, None)
|
||||
.await
|
||||
|
||||
@ -223,6 +223,16 @@ impl<'a> AssetPath<'a> {
|
||||
Ok((source, path, label))
|
||||
}
|
||||
|
||||
/// Creates a new [`AssetPath`] from a [`PathBuf`].
|
||||
#[inline]
|
||||
pub fn from_path_buf(path_buf: PathBuf) -> AssetPath<'a> {
|
||||
AssetPath {
|
||||
path: CowArc::Owned(path_buf.into()),
|
||||
source: AssetSourceId::Default,
|
||||
label: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`AssetPath`] from a [`Path`].
|
||||
#[inline]
|
||||
pub fn from_path(path: &'a Path) -> AssetPath<'a> {
|
||||
@ -478,6 +488,51 @@ impl<'a> AssetPath<'a> {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`AssetPath`] points to a file that is
|
||||
/// outside of its [`AssetSource`](crate::io::AssetSource) folder.
|
||||
///
|
||||
/// ## Example
|
||||
/// ```
|
||||
/// # use bevy_asset::AssetPath;
|
||||
/// // Inside the default AssetSource.
|
||||
/// let path = AssetPath::parse("thingy.png");
|
||||
/// assert!( ! path.is_unapproved());
|
||||
/// let path = AssetPath::parse("gui/thingy.png");
|
||||
/// assert!( ! path.is_unapproved());
|
||||
///
|
||||
/// // Inside a different AssetSource.
|
||||
/// let path = AssetPath::parse("embedded://thingy.png");
|
||||
/// assert!( ! path.is_unapproved());
|
||||
///
|
||||
/// // Exits the `AssetSource`s directory.
|
||||
/// let path = AssetPath::parse("../thingy.png");
|
||||
/// assert!(path.is_unapproved());
|
||||
/// let path = AssetPath::parse("folder/../../thingy.png");
|
||||
/// assert!(path.is_unapproved());
|
||||
///
|
||||
/// // This references the linux root directory.
|
||||
/// let path = AssetPath::parse("/home/thingy.png");
|
||||
/// assert!(path.is_unapproved());
|
||||
/// ```
|
||||
pub fn is_unapproved(&self) -> bool {
|
||||
use std::path::Component;
|
||||
let mut simplified = PathBuf::new();
|
||||
for component in self.path.components() {
|
||||
match component {
|
||||
Component::Prefix(_) | Component::RootDir => return true,
|
||||
Component::CurDir => {}
|
||||
Component::ParentDir => {
|
||||
if !simplified.pop() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Component::Normal(os_str) => simplified.push(os_str),
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetPath<'static> {
|
||||
|
||||
@ -5,7 +5,7 @@ use alloc::{
|
||||
vec::Vec,
|
||||
};
|
||||
use async_fs::File;
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use futures_lite::{AsyncReadExt, AsyncWriteExt};
|
||||
use std::path::PathBuf;
|
||||
use thiserror::Error;
|
||||
|
||||
@ -54,11 +54,11 @@ use crate::{
|
||||
AssetMetaDyn, AssetMetaMinimal, ProcessedInfo, ProcessedInfoMinimal,
|
||||
},
|
||||
AssetLoadError, AssetMetaCheck, AssetPath, AssetServer, AssetServerMode, DeserializeMetaError,
|
||||
MissingAssetLoaderForExtensionError, WriteDefaultMetaError,
|
||||
MissingAssetLoaderForExtensionError, UnapprovedPathMode, WriteDefaultMetaError,
|
||||
};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_tasks::IoTaskPool;
|
||||
use futures_io::ErrorKind;
|
||||
use futures_lite::{AsyncReadExt, AsyncWriteExt, StreamExt};
|
||||
@ -122,6 +122,7 @@ impl AssetProcessor {
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
false,
|
||||
UnapprovedPathMode::default(),
|
||||
);
|
||||
Self { server, data }
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ use crate::{
|
||||
processor::AssetProcessor,
|
||||
saver::{AssetSaver, SavedAsset},
|
||||
transformer::{AssetTransformer, IdentityAssetTransformer, TransformedAsset},
|
||||
AssetLoadError, AssetLoader, AssetPath, CompleteErasedLoadedAsset, DeserializeMetaError,
|
||||
AssetLoadError, AssetLoader, AssetPath, DeserializeMetaError, ErasedLoadedAsset,
|
||||
MissingAssetLoaderForExtensionError, MissingAssetLoaderForTypeNameError,
|
||||
};
|
||||
use alloc::{
|
||||
@ -305,15 +305,15 @@ impl<'a> ProcessContext<'a> {
|
||||
pub async fn load_source_asset<L: AssetLoader>(
|
||||
&mut self,
|
||||
meta: AssetMeta<L, ()>,
|
||||
) -> Result<CompleteErasedLoadedAsset, AssetLoadError> {
|
||||
) -> Result<ErasedLoadedAsset, AssetLoadError> {
|
||||
let server = &self.processor.server;
|
||||
let loader_name = core::any::type_name::<L>();
|
||||
let loader = server.get_asset_loader_with_type_name(loader_name).await?;
|
||||
let mut reader = SliceReader::new(self.asset_bytes);
|
||||
let complete_asset = server
|
||||
let loaded_asset = server
|
||||
.load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true)
|
||||
.await?;
|
||||
for (path, full_hash) in &complete_asset.asset.loader_dependencies {
|
||||
for (path, full_hash) in &loaded_asset.loader_dependencies {
|
||||
self.new_processed_info
|
||||
.process_dependencies
|
||||
.push(ProcessDependencyInfo {
|
||||
@ -321,7 +321,7 @@ impl<'a> ProcessContext<'a> {
|
||||
path: path.to_owned(),
|
||||
});
|
||||
}
|
||||
Ok(complete_asset)
|
||||
Ok(loaded_asset)
|
||||
}
|
||||
|
||||
/// The path of the asset being processed.
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader,
|
||||
CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
|
||||
ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use atomicow::CowArc;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_tasks::{BoxedFuture, ConditionalSendFuture};
|
||||
use core::{borrow::Borrow, hash::Hash, ops::Deref};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -44,7 +44,7 @@ pub trait ErasedAssetSaver: Send + Sync + 'static {
|
||||
fn save<'a>(
|
||||
&'a self,
|
||||
writer: &'a mut Writer,
|
||||
complete_asset: &'a CompleteErasedLoadedAsset,
|
||||
asset: &'a ErasedLoadedAsset,
|
||||
settings: &'a dyn Settings,
|
||||
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>>;
|
||||
|
||||
@ -56,14 +56,14 @@ impl<S: AssetSaver> ErasedAssetSaver for S {
|
||||
fn save<'a>(
|
||||
&'a self,
|
||||
writer: &'a mut Writer,
|
||||
complete_asset: &'a CompleteErasedLoadedAsset,
|
||||
asset: &'a ErasedLoadedAsset,
|
||||
settings: &'a dyn Settings,
|
||||
) -> BoxedFuture<'a, Result<(), Box<dyn core::error::Error + Send + Sync + 'static>>> {
|
||||
Box::pin(async move {
|
||||
let settings = settings
|
||||
.downcast_ref::<S::Settings>()
|
||||
.expect("AssetLoader settings should match the loader type");
|
||||
let saved_asset = SavedAsset::<S::Asset>::from_loaded(complete_asset).unwrap();
|
||||
let saved_asset = SavedAsset::<S::Asset>::from_loaded(asset).unwrap();
|
||||
if let Err(err) = self.save(writer, saved_asset, settings).await {
|
||||
return Err(err.into());
|
||||
}
|
||||
@ -91,11 +91,11 @@ impl<'a, A: Asset> Deref for SavedAsset<'a, A> {
|
||||
|
||||
impl<'a, A: Asset> SavedAsset<'a, A> {
|
||||
/// Creates a new [`SavedAsset`] from `asset` if its internal value matches `A`.
|
||||
pub fn from_loaded(complete_asset: &'a CompleteErasedLoadedAsset) -> Option<Self> {
|
||||
let value = complete_asset.asset.value.downcast_ref::<A>()?;
|
||||
pub fn from_loaded(asset: &'a ErasedLoadedAsset) -> Option<Self> {
|
||||
let value = asset.value.downcast_ref::<A>()?;
|
||||
Some(SavedAsset {
|
||||
value,
|
||||
labeled_assets: &complete_asset.labeled_assets,
|
||||
labeled_assets: &asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
|
||||
@ -114,13 +114,17 @@ impl<'a, A: Asset> SavedAsset<'a, A> {
|
||||
}
|
||||
|
||||
/// Returns the labeled asset, if it exists and matches this type.
|
||||
pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<&B>
|
||||
pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<SavedAsset<B>>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
labeled.asset.value.downcast_ref::<B>()
|
||||
let value = labeled.asset.value.downcast_ref::<B>()?;
|
||||
Some(SavedAsset {
|
||||
value,
|
||||
labeled_assets: &labeled.asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the type-erased labeled asset, if it exists and matches this type.
|
||||
|
||||
@ -11,7 +11,7 @@ use alloc::{
|
||||
vec::Vec,
|
||||
};
|
||||
use bevy_ecs::world::World;
|
||||
use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use bevy_platform::collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use bevy_tasks::Task;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::{any::TypeId, task::Waker};
|
||||
@ -347,14 +347,9 @@ impl AssetInfos {
|
||||
|
||||
/// Returns `true` if the asset this path points to is still alive
|
||||
pub(crate) fn is_path_alive<'a>(&self, path: impl Into<AssetPath<'a>>) -> bool {
|
||||
let path = path.into();
|
||||
|
||||
let result = self
|
||||
.get_path_ids(&path)
|
||||
self.get_path_ids(&path.into())
|
||||
.filter_map(|id| self.infos.get(&id))
|
||||
.any(|info| info.weak_handle.strong_count() > 0);
|
||||
|
||||
result
|
||||
.any(|info| info.weak_handle.strong_count() > 0)
|
||||
}
|
||||
|
||||
/// Returns `true` if the asset at this path should be reloaded
|
||||
|
||||
@ -4,7 +4,7 @@ use crate::{
|
||||
};
|
||||
use alloc::{boxed::Box, sync::Arc, vec::Vec};
|
||||
use async_broadcast::RecvError;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_tasks::IoTaskPool;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::any::TypeId;
|
||||
|
||||
@ -15,7 +15,7 @@ use crate::{
|
||||
},
|
||||
path::AssetPath,
|
||||
Asset, AssetEvent, AssetHandleProvider, AssetId, AssetLoadFailedEvent, AssetMetaCheck, Assets,
|
||||
CompleteErasedLoadedAsset, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset,
|
||||
DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode,
|
||||
UntypedAssetId, UntypedAssetLoadFailedEvent, UntypedHandle,
|
||||
};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, vec, vec::Vec};
|
||||
@ -26,7 +26,7 @@ use alloc::{
|
||||
};
|
||||
use atomicow::CowArc;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_tasks::IoTaskPool;
|
||||
use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
@ -68,6 +68,7 @@ pub(crate) struct AssetServerData {
|
||||
sources: AssetSources,
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
}
|
||||
|
||||
/// The "asset mode" the server is currently in.
|
||||
@ -82,13 +83,19 @@ pub enum AssetServerMode {
|
||||
impl AssetServer {
|
||||
/// Create a new instance of [`AssetServer`]. If `watch_for_changes` is true, the [`AssetReader`](crate::io::AssetReader) storage will watch for changes to
|
||||
/// asset sources and hot-reload them.
|
||||
pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self {
|
||||
pub fn new(
|
||||
sources: AssetSources,
|
||||
mode: AssetServerMode,
|
||||
watching_for_changes: bool,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
) -> Self {
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
Default::default(),
|
||||
mode,
|
||||
AssetMetaCheck::Always,
|
||||
watching_for_changes,
|
||||
unapproved_path_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -99,6 +106,7 @@ impl AssetServer {
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
watching_for_changes: bool,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
) -> Self {
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
@ -106,6 +114,7 @@ impl AssetServer {
|
||||
mode,
|
||||
meta_check,
|
||||
watching_for_changes,
|
||||
unapproved_path_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -115,6 +124,7 @@ impl AssetServer {
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
watching_for_changes: bool,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
) -> Self {
|
||||
let (asset_event_sender, asset_event_receiver) = crossbeam_channel::unbounded();
|
||||
let mut infos = AssetInfos::default();
|
||||
@ -128,6 +138,7 @@ impl AssetServer {
|
||||
asset_event_receiver,
|
||||
loaders,
|
||||
infos: RwLock::new(infos),
|
||||
unapproved_path_mode,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -311,7 +322,16 @@ impl AssetServer {
|
||||
/// The asset load will fail and an error will be printed to the logs if the asset stored at `path` is not of type `A`.
|
||||
#[must_use = "not using the returned strong handle may result in the unexpected release of the asset"]
|
||||
pub fn load<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, None, ())
|
||||
self.load_with_meta_transform(path, None, (), false)
|
||||
}
|
||||
|
||||
/// Same as [`load`](AssetServer::load), but you can load assets from unaproved paths
|
||||
/// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
|
||||
/// is [`Deny`](UnapprovedPathMode::Deny).
|
||||
///
|
||||
/// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
|
||||
pub fn load_override<'a, A: Asset>(&self, path: impl Into<AssetPath<'a>>) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, None, (), true)
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
|
||||
@ -335,7 +355,20 @@ impl AssetServer {
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
guard: G,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, None, guard)
|
||||
self.load_with_meta_transform(path, None, guard, false)
|
||||
}
|
||||
|
||||
/// Same as [`load`](AssetServer::load_acquire), but you can load assets from unaproved paths
|
||||
/// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
|
||||
/// is [`Deny`](UnapprovedPathMode::Deny).
|
||||
///
|
||||
/// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
|
||||
pub fn load_acquire_override<'a, A: Asset, G: Send + Sync + 'static>(
|
||||
&self,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
guard: G,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, None, guard, true)
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path`. The given `settings` function will override the asset's
|
||||
@ -347,7 +380,30 @@ impl AssetServer {
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), ())
|
||||
self.load_with_meta_transform(
|
||||
path,
|
||||
Some(loader_settings_meta_transform(settings)),
|
||||
(),
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [`load`](AssetServer::load_with_settings), but you can load assets from unaproved paths
|
||||
/// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
|
||||
/// is [`Deny`](UnapprovedPathMode::Deny).
|
||||
///
|
||||
/// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
|
||||
pub fn load_with_settings_override<'a, A: Asset, S: Settings>(
|
||||
&self,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(
|
||||
path,
|
||||
Some(loader_settings_meta_transform(settings)),
|
||||
(),
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
|
||||
@ -366,7 +422,36 @@ impl AssetServer {
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
guard: G,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), guard)
|
||||
self.load_with_meta_transform(
|
||||
path,
|
||||
Some(loader_settings_meta_transform(settings)),
|
||||
guard,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
/// Same as [`load`](AssetServer::load_acquire_with_settings), but you can load assets from unaproved paths
|
||||
/// if [`AssetPlugin::unapproved_path_mode`](super::AssetPlugin::unapproved_path_mode)
|
||||
/// is [`Deny`](UnapprovedPathMode::Deny).
|
||||
///
|
||||
/// See [`UnapprovedPathMode`] and [`AssetPath::is_unapproved`]
|
||||
pub fn load_acquire_with_settings_override<
|
||||
'a,
|
||||
A: Asset,
|
||||
S: Settings,
|
||||
G: Send + Sync + 'static,
|
||||
>(
|
||||
&self,
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
settings: impl Fn(&mut S) + Send + Sync + 'static,
|
||||
guard: G,
|
||||
) -> Handle<A> {
|
||||
self.load_with_meta_transform(
|
||||
path,
|
||||
Some(loader_settings_meta_transform(settings)),
|
||||
guard,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
|
||||
@ -374,8 +459,20 @@ impl AssetServer {
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
meta_transform: Option<MetaTransform>,
|
||||
guard: G,
|
||||
override_unapproved: bool,
|
||||
) -> Handle<A> {
|
||||
let path = path.into().into_owned();
|
||||
|
||||
if path.is_unapproved() {
|
||||
match (&self.data.unapproved_path_mode, override_unapproved) {
|
||||
(UnapprovedPathMode::Allow, _) | (UnapprovedPathMode::Deny, true) => {}
|
||||
(UnapprovedPathMode::Deny, false) | (UnapprovedPathMode::Forbid, _) => {
|
||||
error!("Asset path {path} is unapproved. See UnapprovedPathMode for details.");
|
||||
return Handle::default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut infos = self.data.infos.write();
|
||||
let (handle, should_load) = infos.get_or_create_path_handle::<A>(
|
||||
path.clone(),
|
||||
@ -699,18 +796,12 @@ impl AssetServer {
|
||||
|
||||
/// Sends a load event for the given `loaded_asset` and does the same recursively for all
|
||||
/// labeled assets.
|
||||
fn send_loaded_asset(&self, id: UntypedAssetId, mut complete_asset: CompleteErasedLoadedAsset) {
|
||||
for (_, labeled_asset) in complete_asset.labeled_assets.drain() {
|
||||
self.send_asset_event(InternalAssetEvent::Loaded {
|
||||
id: labeled_asset.handle.id(),
|
||||
loaded_asset: labeled_asset.asset,
|
||||
});
|
||||
fn send_loaded_asset(&self, id: UntypedAssetId, mut loaded_asset: ErasedLoadedAsset) {
|
||||
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
|
||||
self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
|
||||
}
|
||||
|
||||
self.send_asset_event(InternalAssetEvent::Loaded {
|
||||
id,
|
||||
loaded_asset: complete_asset.asset,
|
||||
});
|
||||
self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
|
||||
}
|
||||
|
||||
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
|
||||
@ -1334,7 +1425,7 @@ impl AssetServer {
|
||||
reader: &mut dyn Reader,
|
||||
load_dependencies: bool,
|
||||
populate_hashes: bool,
|
||||
) -> Result<CompleteErasedLoadedAsset, AssetLoadError> {
|
||||
) -> Result<ErasedLoadedAsset, AssetLoadError> {
|
||||
// TODO: experiment with this
|
||||
let asset_path = asset_path.clone_owned();
|
||||
let load_context =
|
||||
|
||||
@ -1,10 +1,7 @@
|
||||
use crate::{
|
||||
meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset,
|
||||
UntypedHandle,
|
||||
};
|
||||
use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle};
|
||||
use alloc::boxed::Box;
|
||||
use atomicow::CowArc;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_tasks::ConditionalSendFuture;
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
@ -59,11 +56,11 @@ impl<A: Asset> DerefMut for TransformedAsset<A> {
|
||||
|
||||
impl<A: Asset> TransformedAsset<A> {
|
||||
/// Creates a new [`TransformedAsset`] from `asset` if its internal value matches `A`.
|
||||
pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option<Self> {
|
||||
if let Ok(value) = complete_asset.asset.value.downcast::<A>() {
|
||||
pub fn from_loaded(asset: ErasedLoadedAsset) -> Option<Self> {
|
||||
if let Ok(value) = asset.value.downcast::<A>() {
|
||||
return Some(TransformedAsset {
|
||||
value: *value,
|
||||
labeled_assets: complete_asset.labeled_assets,
|
||||
labeled_assets: asset.labeled_assets,
|
||||
});
|
||||
}
|
||||
None
|
||||
@ -90,13 +87,117 @@ impl<A: Asset> TransformedAsset<A> {
|
||||
&mut self.value
|
||||
}
|
||||
/// Returns the labeled asset, if it exists and matches this type.
|
||||
pub fn get_labeled<B: Asset, Q>(&mut self, label: &'_ Q) -> Option<&mut B>
|
||||
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get_mut(label)?;
|
||||
labeled.asset.value.downcast_mut::<B>()
|
||||
let value = labeled.asset.value.downcast_mut::<B>()?;
|
||||
Some(TransformedSubAsset {
|
||||
value,
|
||||
labeled_assets: &mut labeled.asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
/// Returns the type-erased labeled asset, if it exists and matches this type.
|
||||
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
Some(&labeled.asset)
|
||||
}
|
||||
/// Returns the [`UntypedHandle`] of the labeled asset with the provided 'label', if it exists.
|
||||
pub fn get_untyped_handle<Q>(&self, label: &Q) -> Option<UntypedHandle>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
Some(labeled.handle.clone())
|
||||
}
|
||||
/// Returns the [`Handle`] of the labeled asset with the provided 'label', if it exists and is an asset of type `B`
|
||||
pub fn get_handle<Q, B: Asset>(&self, label: &Q) -> Option<Handle<B>>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
if let Ok(handle) = labeled.handle.clone().try_typed::<B>() {
|
||||
return Some(handle);
|
||||
}
|
||||
None
|
||||
}
|
||||
/// Adds `asset` as a labeled sub asset using `label` and `handle`
|
||||
pub fn insert_labeled(
|
||||
&mut self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
handle: impl Into<UntypedHandle>,
|
||||
asset: impl Into<ErasedLoadedAsset>,
|
||||
) {
|
||||
let labeled = LabeledAsset {
|
||||
asset: asset.into(),
|
||||
handle: handle.into(),
|
||||
};
|
||||
self.labeled_assets.insert(label.into(), labeled);
|
||||
}
|
||||
/// Iterate over all labels for "labeled assets" in the loaded asset
|
||||
pub fn iter_labels(&self) -> impl Iterator<Item = &str> {
|
||||
self.labeled_assets.keys().map(|s| &**s)
|
||||
}
|
||||
}
|
||||
|
||||
/// A labeled sub-asset of [`TransformedAsset`]
|
||||
pub struct TransformedSubAsset<'a, A: Asset> {
|
||||
value: &'a mut A,
|
||||
labeled_assets: &'a mut HashMap<CowArc<'static, str>, LabeledAsset>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Deref for TransformedSubAsset<'a, A> {
|
||||
type Target = A;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> DerefMut for TransformedSubAsset<'a, A> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> TransformedSubAsset<'a, A> {
|
||||
/// Creates a new [`TransformedSubAsset`] from `asset` if its internal value matches `A`.
|
||||
pub fn from_loaded(asset: &'a mut ErasedLoadedAsset) -> Option<Self> {
|
||||
let value = asset.value.downcast_mut::<A>()?;
|
||||
Some(TransformedSubAsset {
|
||||
value,
|
||||
labeled_assets: &mut asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
/// Retrieves the value of this asset.
|
||||
#[inline]
|
||||
pub fn get(&self) -> &A {
|
||||
self.value
|
||||
}
|
||||
/// Mutably retrieves the value of this asset.
|
||||
#[inline]
|
||||
pub fn get_mut(&mut self) -> &mut A {
|
||||
self.value
|
||||
}
|
||||
/// Returns the labeled asset, if it exists and matches this type.
|
||||
pub fn get_labeled<B: Asset, Q>(&mut self, label: &Q) -> Option<TransformedSubAsset<B>>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get_mut(label)?;
|
||||
let value = labeled.asset.value.downcast_mut::<B>()?;
|
||||
Some(TransformedSubAsset {
|
||||
value,
|
||||
labeled_assets: &mut labeled.asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
/// Returns the type-erased labeled asset, if it exists and matches this type.
|
||||
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -156,12 +156,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::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
|
||||
sink.append(audio_source.decoder());
|
||||
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 => {
|
||||
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 +233,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::Once | PlaybackMode::Despawn | PlaybackMode::Remove => {
|
||||
sink.append(audio_source.decoder());
|
||||
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 => {
|
||||
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);
|
||||
|
||||
|
||||
@ -58,13 +58,13 @@ pub use sinks::*;
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_asset::{Asset, AssetApp};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_transform::TransformSystem;
|
||||
use bevy_transform::TransformSystems;
|
||||
|
||||
use audio_output::*;
|
||||
|
||||
/// Set for the audio playback systems, so they can share a run condition
|
||||
#[derive(SystemSet, Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
struct AudioPlaySet;
|
||||
struct AudioPlaybackSystems;
|
||||
|
||||
/// Adds support for audio playback to a Bevy Application
|
||||
///
|
||||
@ -90,13 +90,13 @@ impl Plugin for AudioPlugin {
|
||||
.insert_resource(DefaultSpatialScale(self.default_spatial_scale))
|
||||
.configure_sets(
|
||||
PostUpdate,
|
||||
AudioPlaySet
|
||||
AudioPlaybackSystems
|
||||
.run_if(audio_output_available)
|
||||
.after(TransformSystem::TransformPropagate), // For spatial audio transforms
|
||||
.after(TransformSystems::Propagate), // For spatial audio transforms
|
||||
)
|
||||
.add_systems(
|
||||
PostUpdate,
|
||||
(update_emitter_positions, update_listener_positions).in_set(AudioPlaySet),
|
||||
(update_emitter_positions, update_listener_positions).in_set(AudioPlaybackSystems),
|
||||
)
|
||||
.init_resource::<AudioOutput>();
|
||||
|
||||
@ -118,7 +118,8 @@ impl AddAudioSource for App {
|
||||
{
|
||||
self.init_asset::<T>().add_systems(
|
||||
PostUpdate,
|
||||
(play_queued_audio_system::<T>, cleanup_finished_audio::<T>).in_set(AudioPlaySet),
|
||||
(play_queued_audio_system::<T>, cleanup_finished_audio::<T>)
|
||||
.in_set(AudioPlaybackSystems),
|
||||
);
|
||||
self
|
||||
}
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
use crate::Volume;
|
||||
use bevy_ecs::component::Component;
|
||||
use bevy_math::Vec3;
|
||||
use bevy_transform::prelude::Transform;
|
||||
use core::time::Duration;
|
||||
pub use rodio::source::SeekError;
|
||||
use rodio::{Sink, SpatialSink};
|
||||
|
||||
use crate::Volume;
|
||||
|
||||
/// Common interactions with an audio sink.
|
||||
pub trait AudioSinkPlayback {
|
||||
/// Gets the volume of the sound as a [`Volume`].
|
||||
@ -41,6 +42,34 @@ 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.
|
||||
///
|
||||
/// As long as the duration of the source is known, seek is guaranteed to saturate
|
||||
/// at the end of the source. For example given a source that reports a total duration
|
||||
/// of 42 seconds calling `try_seek()` with 60 seconds as argument will seek to
|
||||
/// 42 seconds.
|
||||
///
|
||||
/// # Errors
|
||||
/// This function will return [`SeekError::NotSupported`] if one of the underlying
|
||||
/// sources does not support seeking.
|
||||
///
|
||||
/// It will return an error if an implementation ran
|
||||
/// into one during the seek.
|
||||
///
|
||||
/// When seeking beyond the end of a source, this
|
||||
/// function might return an error if the duration of the source is not known.
|
||||
fn try_seek(&self, pos: Duration) -> Result<(), SeekError>;
|
||||
|
||||
/// Pauses playback of this sink.
|
||||
///
|
||||
/// No effect if already paused.
|
||||
@ -160,6 +189,14 @@ 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)
|
||||
}
|
||||
|
||||
fn pause(&self) {
|
||||
self.sink.pause();
|
||||
}
|
||||
@ -256,6 +293,14 @@ 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)
|
||||
}
|
||||
|
||||
fn pause(&self) {
|
||||
self.sink.pause();
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -60,7 +60,7 @@ pub trait Alpha: Sized {
|
||||
/// Return a new version of this color with the given alpha value.
|
||||
fn with_alpha(&self, alpha: f32) -> Self;
|
||||
|
||||
/// Return a the alpha component of this color.
|
||||
/// Return the alpha component of this color.
|
||||
fn alpha(&self) -> f32;
|
||||
|
||||
/// Sets the alpha component of this color.
|
||||
@ -77,6 +77,20 @@ pub trait Alpha: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
impl Alpha for f32 {
|
||||
fn with_alpha(&self, alpha: f32) -> Self {
|
||||
alpha
|
||||
}
|
||||
|
||||
fn alpha(&self) -> f32 {
|
||||
*self
|
||||
}
|
||||
|
||||
fn set_alpha(&mut self, alpha: f32) {
|
||||
*self = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for manipulating the hue of a color.
|
||||
pub trait Hue: Sized {
|
||||
/// Return a new version of this color with the hue channel set to the given value.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user