Compare commits
1 Commits
custom
...
dependabot
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4e3ccd7776 |
31
.github/workflows/ci.yml
vendored
31
.github/workflows/ci.yml
vendored
@ -177,31 +177,6 @@ jobs:
|
||||
- name: Check Compile
|
||||
run: cargo check -p bevy --no-default-features --features default_no_std --target thumbv6m-none-eabi
|
||||
|
||||
check-compiles-no-std-examples:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
needs: ci
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
~/.cargo/registry/index/
|
||||
~/.cargo/registry/cache/
|
||||
~/.cargo/git/db/
|
||||
target/
|
||||
crates/bevy_ecs_compile_fail_tests/target/
|
||||
crates/bevy_reflect_compile_fail_tests/target/
|
||||
key: ${{ runner.os }}-cargo-check-compiles-no-std-examples-${{ hashFiles('**/Cargo.toml') }}
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
targets: x86_64-unknown-none
|
||||
- name: Install Linux dependencies
|
||||
uses: ./.github/actions/install-linux-deps
|
||||
- name: Check Compile
|
||||
run: cd examples/no_std/library && cargo check --no-default-features --features libm,critical-section --target x86_64-unknown-none
|
||||
|
||||
build-wasm:
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 30
|
||||
@ -259,7 +234,7 @@ jobs:
|
||||
# Full git history is needed to get a proper list of changed files within `super-linter`
|
||||
fetch-depth: 0
|
||||
- name: Run Markdown Lint
|
||||
uses: super-linter/super-linter/slim@v7.3.0
|
||||
uses: docker://ghcr.io/github/super-linter:slim-v4
|
||||
env:
|
||||
MULTI_STATUS: false
|
||||
VALIDATE_ALL_CODEBASE: false
|
||||
@ -292,7 +267,7 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Check for typos
|
||||
uses: crate-ci/typos@v1.30.2
|
||||
uses: crate-ci/typos@v1.30.1
|
||||
- name: Typos info
|
||||
if: failure()
|
||||
run: |
|
||||
@ -453,7 +428,7 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
errors=""
|
||||
for file in $(find examples tests -name '*.rs' -not -path 'examples/mobile/*'); do
|
||||
for file in $(find examples tests -name '*.rs'); do
|
||||
if grep -q "use bevy_" "$file"; then
|
||||
errors+="ERROR: Detected internal Bevy import in $file\n"
|
||||
fi
|
||||
|
96
Cargo.toml
96
Cargo.toml
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
categories = ["game-engines", "graphics", "gui", "rendering"]
|
||||
description = "A refreshingly simple data-driven game engine and app framework"
|
||||
@ -26,8 +26,6 @@ members = [
|
||||
"crates/bevy_reflect/compile_fail",
|
||||
# Examples of compiling Bevy for mobile platforms.
|
||||
"examples/mobile",
|
||||
# Examples of using Bevy on no_std platforms.
|
||||
"examples/no_std/*",
|
||||
# Benchmarks
|
||||
"benches",
|
||||
# Internal tools that are not published.
|
||||
@ -53,8 +51,6 @@ unwrap_or_default = "warn"
|
||||
needless_lifetimes = "allow"
|
||||
too_many_arguments = "allow"
|
||||
nonstandard_macro_braces = "warn"
|
||||
print_stdout = "warn"
|
||||
print_stderr = "warn"
|
||||
|
||||
ptr_as_ptr = "warn"
|
||||
ptr_cast_constness = "warn"
|
||||
@ -280,9 +276,6 @@ bevy_log = ["bevy_internal/bevy_log"]
|
||||
# Enable input focus subsystem
|
||||
bevy_input_focus = ["bevy_internal/bevy_input_focus"]
|
||||
|
||||
# Use the configurable global error handler as the default error handler.
|
||||
configurable_error_handler = ["bevy_internal/configurable_error_handler"]
|
||||
|
||||
# Enable passthrough loading for SPIR-V shaders (Only supported on Vulkan, shader capabilities and extensions must agree with the platform implementation)
|
||||
spirv_shader_passthrough = ["bevy_internal/spirv_shader_passthrough"]
|
||||
|
||||
@ -527,12 +520,12 @@ libm = ["bevy_internal/libm"]
|
||||
web = ["bevy_internal/web"]
|
||||
|
||||
[dependencies]
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.1", default-features = false }
|
||||
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
|
||||
tracing = { version = "0.1", default-features = false, optional = true }
|
||||
|
||||
# Wasm does not support dynamic linking.
|
||||
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.1", default-features = false, optional = true }
|
||||
bevy_dylib = { path = "crates/bevy_dylib", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.0"
|
||||
@ -540,16 +533,16 @@ rand_chacha = "0.3.1"
|
||||
ron = "0.8.0"
|
||||
flate2 = "1.0"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
serde_json = "1"
|
||||
bytemuck = "1.7"
|
||||
bevy_render = { path = "crates/bevy_render", version = "0.16.1", default-features = false }
|
||||
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.
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.1", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.16.1", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.16.1", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.1", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.16.1", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.1", default-features = false }
|
||||
bevy_ecs = { path = "crates/bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_state = { path = "crates/bevy_state", version = "0.16.0-dev", default-features = false }
|
||||
bevy_asset = { path = "crates/bevy_asset", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "crates/bevy_reflect", version = "0.16.0-dev", default-features = false }
|
||||
bevy_image = { path = "crates/bevy_image", version = "0.16.0-dev", default-features = false }
|
||||
bevy_gizmos = { path = "crates/bevy_gizmos", version = "0.16.0-dev", default-features = false }
|
||||
# Needed to poll Task examples
|
||||
futures-lite = "2.0.1"
|
||||
async-std = "1.13"
|
||||
@ -561,7 +554,7 @@ hyper = { version = "1", features = ["server", "http1"] }
|
||||
http-body-util = "0.1"
|
||||
anyhow = "1"
|
||||
macro_rules_attribute = "0.2"
|
||||
accesskit = "0.18"
|
||||
accesskit = "0.17"
|
||||
nonmax = "0.5"
|
||||
|
||||
[target.'cfg(not(target_family = "wasm"))'.dev-dependencies]
|
||||
@ -612,7 +605,7 @@ doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.2d_viewport_to_world]
|
||||
name = "2D Viewport To World"
|
||||
description = "Demonstrates how to use the `Camera::viewport_to_world_2d` method with a dynamic viewport and camera."
|
||||
description = "Demonstrates how to use the `Camera::viewport_to_world_2d` method"
|
||||
category = "2D Rendering"
|
||||
wasm = true
|
||||
|
||||
@ -1581,7 +1574,6 @@ wasm = true
|
||||
name = "headless"
|
||||
path = "examples/app/headless.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_log"]
|
||||
|
||||
[package.metadata.example.headless]
|
||||
name = "Headless"
|
||||
@ -1843,7 +1835,7 @@ path = "examples/asset/multi_asset_sync.rs"
|
||||
doc-scrape-examples = true
|
||||
|
||||
[package.metadata.example.multi_asset_sync]
|
||||
name = "Multi-asset synchronization"
|
||||
name = "Mult-asset synchronization"
|
||||
description = "Demonstrates how to wait for multiple assets to be loaded."
|
||||
category = "Assets"
|
||||
wasm = true
|
||||
@ -2204,7 +2196,6 @@ wasm = false
|
||||
name = "fallible_params"
|
||||
path = "examples/ecs/fallible_params.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["configurable_error_handler"]
|
||||
|
||||
[package.metadata.example.fallible_params]
|
||||
name = "Fallible System Parameters"
|
||||
@ -2213,14 +2204,14 @@ category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "error_handling"
|
||||
path = "examples/ecs/error_handling.rs"
|
||||
name = "fallible_systems"
|
||||
path = "examples/ecs/fallible_systems.rs"
|
||||
doc-scrape-examples = true
|
||||
required-features = ["bevy_mesh_picking_backend", "configurable_error_handler"]
|
||||
required-features = ["bevy_mesh_picking_backend"]
|
||||
|
||||
[package.metadata.example.error_handling]
|
||||
name = "Error handling"
|
||||
description = "How to return and handle errors across the ECS"
|
||||
[package.metadata.example.fallible_systems]
|
||||
name = "Fallible Systems"
|
||||
description = "Systems that return results to handle errors"
|
||||
category = "ECS (Entity Component System)"
|
||||
wasm = false
|
||||
|
||||
@ -4270,50 +4261,3 @@ name = "Occlusion Culling"
|
||||
description = "Demonstration of Occlusion Culling"
|
||||
category = "3D Rendering"
|
||||
wasm = false
|
||||
|
||||
[[example]]
|
||||
name = "camera_controller"
|
||||
path = "examples/helpers/camera_controller.rs"
|
||||
doc-scrape-examples = true
|
||||
crate-type = ["lib"]
|
||||
|
||||
[package.metadata.example.camera_controller]
|
||||
name = "Camera Controller"
|
||||
description = "Example Free-Cam Styled Camera Controller"
|
||||
category = "Helpers"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "widgets"
|
||||
path = "examples/helpers/widgets.rs"
|
||||
doc-scrape-examples = true
|
||||
crate-type = ["lib"]
|
||||
|
||||
[package.metadata.example.widgets]
|
||||
name = "Widgets"
|
||||
description = "Example UI Widgets"
|
||||
category = "Helpers"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "no_std_library"
|
||||
path = "examples/no_std/library/src/lib.rs"
|
||||
doc-scrape-examples = true
|
||||
crate-type = ["lib"]
|
||||
|
||||
[package.metadata.example.no_std_library]
|
||||
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
|
||||
|
@ -15,7 +15,7 @@ struct MaterialBindings {
|
||||
}
|
||||
|
||||
#ifdef BINDLESS
|
||||
@group(2) @binding(0) var<storage> materials: array<MaterialBindings>;
|
||||
@group(2) @binding(0) var<storage> materials: binding_array<MaterialBindings>;
|
||||
@group(2) @binding(10) var<storage> material_color: binding_array<Color>;
|
||||
#else // BINDLESS
|
||||
@group(2) @binding(0) var<uniform> material_color: Color;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import super::util::make_polka_dots;
|
||||
import super::shaders::util::make_polka_dots;
|
||||
|
||||
struct VertexOutput {
|
||||
@builtin(position) position: vec4<f32>,
|
||||
@ -6,8 +6,7 @@ struct VertexOutput {
|
||||
}
|
||||
|
||||
struct CustomMaterial {
|
||||
// Needed for 16-bit alignment on WebGL2
|
||||
time: vec4<f32>,
|
||||
time: f32,
|
||||
}
|
||||
|
||||
@group(2) @binding(0) var<uniform> material: CustomMaterial;
|
||||
@ -16,5 +15,5 @@ struct CustomMaterial {
|
||||
fn fragment(
|
||||
mesh: VertexOutput,
|
||||
) -> @location(0) vec4<f32> {
|
||||
return make_polka_dots(mesh.uv, material.time.x);
|
||||
}
|
||||
return make_polka_dots(mesh.uv, material.time);
|
||||
}
|
@ -1,107 +0,0 @@
|
||||
// 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;
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
//! between the vertex and fragment shader. Also shows the custom vertex layout.
|
||||
|
||||
// First we import everything we need from bevy_pbr
|
||||
// A 2D shader would be very similar but import from bevy_sprite instead
|
||||
// A 2d shader would be vevry similar but import from bevy_sprite instead
|
||||
#import bevy_pbr::{
|
||||
mesh_functions,
|
||||
view_transformations::position_world_to_clip
|
||||
@ -45,4 +45,4 @@ fn vertex(vertex: Vertex) -> VertexOutput {
|
||||
fn fragment(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
// output the color directly
|
||||
return vec4(in.color, 1.0);
|
||||
}
|
||||
}
|
@ -9,19 +9,19 @@
|
||||
#import bevy_core_pipeline::tonemapping::tone_mapping
|
||||
#endif
|
||||
|
||||
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
|
||||
// Sweep across hues on y axis with value from 0.0 to +15EV across x axis
|
||||
// quantized into 24 steps for both axis.
|
||||
fn color_sweep(uv_input: vec2<f32>) -> vec3<f32> {
|
||||
var uv = uv_input;
|
||||
let steps = 24.0;
|
||||
uv.y = uv.y * (1.0 + 1.0 / steps);
|
||||
let ratio = 2.0;
|
||||
|
||||
|
||||
let h = PI * 2.0 * floor(1.0 + steps * uv.y) / steps;
|
||||
let L = floor(uv.x * steps * ratio) / (steps * ratio) - 0.5;
|
||||
|
||||
|
||||
var color = vec3(0.0);
|
||||
if uv.y < 1.0 {
|
||||
if uv.y < 1.0 {
|
||||
color = cos(h + vec3(0.0, 1.0, 2.0) * PI * 2.0 / 3.0);
|
||||
let maxRGB = max(color.r, max(color.g, color.b));
|
||||
let minRGB = min(color.r, min(color.g, color.b));
|
||||
|
@ -1,44 +1,29 @@
|
||||
fn make_polka_dots(pos: vec2<f32>, time: f32) -> vec4<f32> {
|
||||
// Create repeating circles
|
||||
let scaled_pos = pos * 6.0;
|
||||
let cell = vec2<f32>(fract(scaled_pos.x), fract(scaled_pos.y));
|
||||
var dist_from_center = distance(cell, vec2<f32>(0.5));
|
||||
|
||||
let dist_from_center = distance(cell, vec2<f32>(0.5));
|
||||
|
||||
// Make dots alternate between pink and purple
|
||||
let is_even = (floor(scaled_pos.x) + floor(scaled_pos.y)) % 2.0;
|
||||
|
||||
var dot_color = vec3<f32>(0.0);
|
||||
var is_dot = 0.0;
|
||||
|
||||
@if(!PARTY_MODE) {
|
||||
let color1 = vec3<f32>(1.0, 0.4, 0.8); // pink
|
||||
let color2 = vec3<f32>(0.6, 0.2, 1.0); // purple
|
||||
dot_color = mix(color1, color2, is_even);
|
||||
is_dot = step(dist_from_center, 0.3);
|
||||
} @else {
|
||||
let grid_x = floor(scaled_pos.x);
|
||||
let grid_y = floor(scaled_pos.y);
|
||||
let wave_speed = 3.0;
|
||||
let wave_phase = time * wave_speed;
|
||||
|
||||
let diagonal_pos = (grid_x + grid_y) * 0.5;
|
||||
let wave_value = sin(diagonal_pos + wave_phase);
|
||||
|
||||
let wave_normalized = (wave_value + 1.0) * 0.5;
|
||||
|
||||
let color1 = vec3<f32>(1.0, 0.3, 0.7);
|
||||
let color2 = vec3<f32>(0.5, 0.1, 1.0);
|
||||
let intense_color1 = vec3<f32>(1.0, 0.1, 0.9);
|
||||
let intense_color2 = vec3<f32>(0.8, 0.0, 1.0);
|
||||
|
||||
let animated_color1 = mix(color1, intense_color1, wave_normalized);
|
||||
let animated_color2 = mix(color2, intense_color2, wave_normalized);
|
||||
|
||||
}
|
||||
// Animate the colors in party mode
|
||||
@if(PARTY_MODE) {
|
||||
let color1 = vec3<f32>(1.0, 0.2, 0.2); // red
|
||||
let color2 = vec3<f32>(0.2, 0.2, 1.0); // blue
|
||||
let oscillation = (sin(time * 10.0) + 1.0) * 0.5;
|
||||
let animated_color1 = mix(color1, color2, oscillation);
|
||||
let animated_color2 = mix(color2, color1, oscillation);
|
||||
dot_color = mix(animated_color1, animated_color2, is_even);
|
||||
|
||||
let size_mod = 0.15 * wave_value;
|
||||
dist_from_center = dist_from_center * (1.0 - size_mod);
|
||||
// Animate whether something is a dot by position but also time
|
||||
is_dot = step(dist_from_center, 0.3 + wave_normalized * 0.2);
|
||||
}
|
||||
|
||||
return vec4<f32>(dot_color * is_dot, 1.0);
|
||||
}
|
||||
// Draw the dot
|
||||
let is_dot = step(dist_from_center, 0.3);
|
||||
return vec4<f32>(dot_color * is_dot, is_dot);
|
||||
}
|
@ -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 = { path = "../crates/bevy_platform", default-features = false, features = [
|
||||
bevy_platform_support = { path = "../crates/bevy_platform_support", default-features = false, features = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ pub fn build_schedule(criterion: &mut Criterion) {
|
||||
// Benchmark graphs of different sizes.
|
||||
for graph_size in [100, 500, 1000] {
|
||||
// Basic benchmark without constraints.
|
||||
group.bench_function(format!("{graph_size}_schedule_no_constraints"), |bencher| {
|
||||
group.bench_function(format!("{graph_size}_schedule_noconstraints"), |bencher| {
|
||||
bencher.iter(|| {
|
||||
let mut app = App::new();
|
||||
for _ in 0..graph_size {
|
||||
|
@ -62,31 +62,6 @@ 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]);
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
use bevy_ecs::entity::{Entity, EntityHashSet};
|
||||
use bevy_ecs::entity::{hash_set::EntityHashSet, Entity};
|
||||
use criterion::{BenchmarkId, Criterion, Throughput};
|
||||
use rand::{Rng, SeedableRng};
|
||||
use rand_chacha::ChaCha8Rng;
|
||||
|
@ -17,7 +17,6 @@ criterion_group!(
|
||||
benches,
|
||||
empty_commands,
|
||||
spawn_commands,
|
||||
nonempty_spawn_commands,
|
||||
insert_commands,
|
||||
fake_commands,
|
||||
zero_sized_commands,
|
||||
|
@ -10,7 +10,7 @@ use criterion::{
|
||||
criterion_group!(
|
||||
benches,
|
||||
concrete_list_apply,
|
||||
concrete_list_to_dynamic_list,
|
||||
concrete_list_clone_dynamic,
|
||||
dynamic_list_apply,
|
||||
dynamic_list_push
|
||||
);
|
||||
@ -81,20 +81,20 @@ fn concrete_list_apply(criterion: &mut Criterion) {
|
||||
list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch);
|
||||
|
||||
list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
|
||||
patch(size).to_dynamic_list()
|
||||
patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
list_apply(&mut group, "same_len_concrete_patch", full_base, patch);
|
||||
|
||||
list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| {
|
||||
patch(size).to_dynamic_list()
|
||||
patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
group.finish();
|
||||
}
|
||||
|
||||
fn concrete_list_to_dynamic_list(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("concrete_list_to_dynamic_list"));
|
||||
fn concrete_list_clone_dynamic(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("concrete_list_clone_dynamic"));
|
||||
|
||||
for size in SIZES {
|
||||
group.throughput(Throughput::Elements(size as u64));
|
||||
@ -105,7 +105,7 @@ fn concrete_list_to_dynamic_list(criterion: &mut Criterion) {
|
||||
|bencher, &size| {
|
||||
let v = iter::repeat_n(0, size).collect::<Vec<_>>();
|
||||
|
||||
bencher.iter(|| black_box(&v).to_dynamic_list());
|
||||
bencher.iter(|| black_box(&v).clone_dynamic());
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -127,7 +127,7 @@ fn dynamic_list_push(criterion: &mut Criterion) {
|
||||
let dst = DynamicList::default();
|
||||
|
||||
bencher.iter_batched(
|
||||
|| (src.clone(), dst.to_dynamic_list()),
|
||||
|| (src.clone(), dst.clone_dynamic()),
|
||||
|(src, mut dst)| {
|
||||
for item in src {
|
||||
dst.push(item);
|
||||
@ -145,20 +145,20 @@ fn dynamic_list_push(criterion: &mut Criterion) {
|
||||
fn dynamic_list_apply(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("dynamic_list_apply"));
|
||||
|
||||
let empty_base = |_: usize| || Vec::<u64>::new().to_dynamic_list();
|
||||
let empty_base = |_: usize| || Vec::<u64>::new().clone_dynamic();
|
||||
let full_base = |size: usize| move || iter::repeat_n(0, size).collect::<Vec<u64>>();
|
||||
let patch = |size: usize| iter::repeat_n(1, size).collect::<Vec<u64>>();
|
||||
|
||||
list_apply(&mut group, "empty_base_concrete_patch", empty_base, patch);
|
||||
|
||||
list_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
|
||||
patch(size).to_dynamic_list()
|
||||
patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
list_apply(&mut group, "same_len_concrete_patch", full_base, patch);
|
||||
|
||||
list_apply(&mut group, "same_len_dynamic_patch", full_base, |size| {
|
||||
patch(size).to_dynamic_list()
|
||||
patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
group.finish();
|
||||
|
@ -1,7 +1,7 @@
|
||||
use core::{fmt::Write, hint::black_box, iter, time::Duration};
|
||||
|
||||
use benches::bench;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::{DynamicMap, Map};
|
||||
use criterion::{
|
||||
criterion_group, measurement::Measurement, AxisScale, BatchSize, BenchmarkGroup, BenchmarkId,
|
||||
@ -108,7 +108,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
|
||||
);
|
||||
|
||||
map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
|
||||
key_range_patch(size).to_dynamic_map()
|
||||
key_range_patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
map_apply(
|
||||
@ -122,7 +122,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
|
||||
&mut group,
|
||||
"same_keys_dynamic_patch",
|
||||
key_range_base,
|
||||
|size| key_range_patch(size).to_dynamic_map(),
|
||||
|size| key_range_patch(size).clone_dynamic(),
|
||||
);
|
||||
|
||||
map_apply(
|
||||
@ -136,7 +136,7 @@ fn concrete_map_apply(criterion: &mut Criterion) {
|
||||
&mut group,
|
||||
"disjoint_keys_dynamic_patch",
|
||||
key_range_base,
|
||||
|size| disjoint_patch(size).to_dynamic_map(),
|
||||
|size| disjoint_patch(size).clone_dynamic(),
|
||||
);
|
||||
}
|
||||
|
||||
@ -159,7 +159,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
|
||||
(0..size as u64)
|
||||
.zip(iter::repeat(0))
|
||||
.collect::<HashMap<u64, u64>>()
|
||||
.to_dynamic_map()
|
||||
.clone_dynamic()
|
||||
}
|
||||
};
|
||||
|
||||
@ -183,7 +183,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
|
||||
);
|
||||
|
||||
map_apply(&mut group, "empty_base_dynamic_patch", empty_base, |size| {
|
||||
key_range_patch(size).to_dynamic_map()
|
||||
key_range_patch(size).clone_dynamic()
|
||||
});
|
||||
|
||||
map_apply(
|
||||
@ -197,7 +197,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
|
||||
&mut group,
|
||||
"same_keys_dynamic_patch",
|
||||
key_range_base,
|
||||
|size| key_range_patch(size).to_dynamic_map(),
|
||||
|size| key_range_patch(size).clone_dynamic(),
|
||||
);
|
||||
|
||||
map_apply(
|
||||
@ -211,7 +211,7 @@ fn dynamic_map_apply(criterion: &mut Criterion) {
|
||||
&mut group,
|
||||
"disjoint_keys_dynamic_patch",
|
||||
key_range_base,
|
||||
|size| disjoint_patch(size).to_dynamic_map(),
|
||||
|size| disjoint_patch(size).clone_dynamic(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,8 @@ criterion_group!(
|
||||
concrete_struct_apply,
|
||||
concrete_struct_field,
|
||||
concrete_struct_type_info,
|
||||
concrete_struct_to_dynamic_struct,
|
||||
dynamic_struct_to_dynamic_struct,
|
||||
concrete_struct_clone,
|
||||
dynamic_struct_clone,
|
||||
dynamic_struct_apply,
|
||||
dynamic_struct_get_field,
|
||||
dynamic_struct_insert,
|
||||
@ -113,7 +113,7 @@ fn concrete_struct_apply(criterion: &mut Criterion) {
|
||||
bencher.iter_batched(
|
||||
|| {
|
||||
let (obj, _) = input();
|
||||
let patch = obj.to_dynamic_struct();
|
||||
let patch = obj.clone_dynamic();
|
||||
(obj, patch)
|
||||
},
|
||||
|(mut obj, patch)| obj.apply(black_box(&patch)),
|
||||
@ -170,8 +170,8 @@ fn concrete_struct_type_info(criterion: &mut Criterion) {
|
||||
}
|
||||
}
|
||||
|
||||
fn concrete_struct_to_dynamic_struct(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("concrete_struct_to_dynamic_struct"));
|
||||
fn concrete_struct_clone(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("concrete_struct_clone"));
|
||||
|
||||
let structs: [(Box<dyn Struct>, Box<dyn Struct>); 5] = [
|
||||
(
|
||||
@ -203,28 +203,28 @@ fn concrete_struct_to_dynamic_struct(criterion: &mut Criterion) {
|
||||
BenchmarkId::new("NonGeneric", field_count),
|
||||
&standard,
|
||||
|bencher, s| {
|
||||
bencher.iter(|| s.to_dynamic_struct());
|
||||
bencher.iter(|| s.clone_dynamic());
|
||||
},
|
||||
);
|
||||
group.bench_with_input(
|
||||
BenchmarkId::new("Generic", field_count),
|
||||
&generic,
|
||||
|bencher, s| {
|
||||
bencher.iter(|| s.to_dynamic_struct());
|
||||
bencher.iter(|| s.clone_dynamic());
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn dynamic_struct_to_dynamic_struct(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("dynamic_struct_to_dynamic_struct"));
|
||||
fn dynamic_struct_clone(criterion: &mut Criterion) {
|
||||
let mut group = create_group(criterion, bench!("dynamic_struct_clone"));
|
||||
|
||||
let structs: [Box<dyn Struct>; 5] = [
|
||||
Box::new(Struct1::default().to_dynamic_struct()),
|
||||
Box::new(Struct16::default().to_dynamic_struct()),
|
||||
Box::new(Struct32::default().to_dynamic_struct()),
|
||||
Box::new(Struct64::default().to_dynamic_struct()),
|
||||
Box::new(Struct128::default().to_dynamic_struct()),
|
||||
Box::new(Struct1::default().clone_dynamic()),
|
||||
Box::new(Struct16::default().clone_dynamic()),
|
||||
Box::new(Struct32::default().clone_dynamic()),
|
||||
Box::new(Struct64::default().clone_dynamic()),
|
||||
Box::new(Struct128::default().clone_dynamic()),
|
||||
];
|
||||
|
||||
for s in structs {
|
||||
@ -234,7 +234,7 @@ fn dynamic_struct_to_dynamic_struct(criterion: &mut Criterion) {
|
||||
BenchmarkId::from_parameter(field_count),
|
||||
&s,
|
||||
|bencher, s| {
|
||||
bencher.iter(|| s.to_dynamic_struct());
|
||||
bencher.iter(|| s.clone_dynamic());
|
||||
},
|
||||
);
|
||||
}
|
||||
@ -265,7 +265,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
|
||||
&patch,
|
||||
|bencher, patch| {
|
||||
bencher.iter_batched(
|
||||
|| (base.to_dynamic_struct(), patch()),
|
||||
|| (base.clone_dynamic(), patch()),
|
||||
|(mut base, patch)| base.apply(black_box(&*patch)),
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
@ -289,7 +289,7 @@ fn dynamic_struct_apply(criterion: &mut Criterion) {
|
||||
}
|
||||
|
||||
bencher.iter_batched(
|
||||
|| base.to_dynamic_struct(),
|
||||
|| base.clone_dynamic(),
|
||||
|mut base| base.apply(black_box(&patch)),
|
||||
BatchSize::SmallInput,
|
||||
);
|
||||
@ -315,7 +315,7 @@ fn dynamic_struct_insert(criterion: &mut Criterion) {
|
||||
|
||||
let field = format!("field_{}", field_count);
|
||||
bencher.iter_batched(
|
||||
|| s.to_dynamic_struct(),
|
||||
|| s.clone_dynamic(),
|
||||
|mut s| {
|
||||
s.insert(black_box(&field), ());
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_a11y"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides accessibility support for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -18,17 +18,28 @@ 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", "accesskit/serde"]
|
||||
serialize = [
|
||||
"dep:serde",
|
||||
"bevy_ecs/serialize",
|
||||
"bevy_input_focus/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"]
|
||||
std = [
|
||||
"bevy_app/std",
|
||||
"bevy_ecs/std",
|
||||
"bevy_reflect/std",
|
||||
"bevy_input_focus/std",
|
||||
]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
## on all platforms, including `no_std`.
|
||||
@ -36,17 +47,22 @@ 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.1", default-features = false }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", default-features = false, optional = true }
|
||||
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.18", default-features = false }
|
||||
accesskit = { version = "0.17", default-features = false }
|
||||
serde = { version = "1", default-features = false, features = [
|
||||
"alloc",
|
||||
], optional = true }
|
||||
|
@ -54,11 +54,7 @@ pub struct ActionRequest(pub accesskit::ActionRequest);
|
||||
/// Useful if a third-party plugin needs to conditionally integrate with
|
||||
/// `AccessKit`
|
||||
#[derive(Resource, Default, Clone, Debug, Deref, DerefMut)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Default, Clone, Resource)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Default, Resource))]
|
||||
pub struct AccessibilityRequested(Arc<AtomicBool>);
|
||||
|
||||
impl AccessibilityRequested {
|
||||
@ -82,11 +78,7 @@ impl AccessibilityRequested {
|
||||
/// will generate conflicting updates.
|
||||
#[derive(Resource, Clone, Debug, Deref, DerefMut)]
|
||||
#[cfg_attr(feature = "serialize", derive(Serialize, Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Resource, Clone, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(Resource))]
|
||||
#[cfg_attr(
|
||||
all(feature = "bevy_reflect", feature = "serialize"),
|
||||
reflect(Serialize, Deserialize)
|
||||
@ -135,7 +127,7 @@ impl From<Node> for AccessibilityNode {
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect))]
|
||||
#[cfg_attr(
|
||||
all(feature = "bevy_reflect", feature = "serialize"),
|
||||
reflect(Serialize, Deserialize, Clone)
|
||||
reflect(Serialize, Deserialize)
|
||||
)]
|
||||
pub enum AccessibilitySystem {
|
||||
/// Update the accessibility tree
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_animation"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides animation functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,22 +10,21 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.1" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.2" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.1" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.1" }
|
||||
bevy_mesh = { path = "../bevy_mesh", version = "0.16.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_log = { path = "../bevy_log", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", features = [
|
||||
"petgraph",
|
||||
] }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.1" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.1" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.1", default-features = false, features = [
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.0-dev" }
|
||||
bevy_time = { path = "../bevy_time", version = "0.16.0-dev" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
@ -100,48 +100,43 @@ use bevy_math::curve::{
|
||||
iterable::IterableCurve,
|
||||
Curve, Interval,
|
||||
};
|
||||
use bevy_mesh::morph::MorphWeights;
|
||||
use bevy_platform::hash::Hashed;
|
||||
use bevy_platform_support::hash::Hashed;
|
||||
use bevy_reflect::{FromReflect, Reflect, Reflectable, TypeInfo, Typed};
|
||||
use bevy_render::mesh::morph::MorphWeights;
|
||||
use downcast_rs::{impl_downcast, Downcast};
|
||||
|
||||
/// A trait for exposing a value in an entity so that it can be animated.
|
||||
/// A value on a component that Bevy can animate.
|
||||
///
|
||||
/// `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`.
|
||||
/// 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:
|
||||
///
|
||||
/// # use bevy_animation::{prelude::AnimatableProperty, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// # use bevy_ecs::component::Component;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use std::any::TypeId;
|
||||
/// #[derive(Component)]
|
||||
/// struct ExampleComponent {
|
||||
/// power_level: Option<f32>
|
||||
/// }
|
||||
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||
/// #[derive(Reflect)]
|
||||
/// struct FieldOfViewProperty;
|
||||
///
|
||||
/// #[derive(Clone)]
|
||||
/// struct PowerLevelProperty;
|
||||
///
|
||||
/// impl AnimatableProperty for PowerLevelProperty {
|
||||
/// impl AnimatableProperty for FieldOfViewProperty {
|
||||
/// 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::<ExampleComponent>()
|
||||
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(
|
||||
/// TypeId::of::<ExampleComponent>()
|
||||
/// ))?
|
||||
/// .get_mut::<Projection>()
|
||||
/// .ok_or(AnimationEvaluationError::ComponentNotPresent(TypeId::of::<
|
||||
/// Projection,
|
||||
/// >(
|
||||
/// )))?
|
||||
/// .into_inner();
|
||||
/// component.power_level.as_mut().ok_or(AnimationEvaluationError::PropertyNotPresent(
|
||||
/// TypeId::of::<Option<f32>>()
|
||||
/// ))
|
||||
/// match component {
|
||||
/// Projection::Perspective(perspective) => Ok(&mut perspective.fov),
|
||||
/// _ => Err(AnimationEvaluationError::PropertyNotPresent(TypeId::of::<
|
||||
/// PerspectiveProjection,
|
||||
/// >(
|
||||
/// ))),
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// fn evaluator_id(&self) -> EvaluatorId {
|
||||
@ -149,44 +144,58 @@ use downcast_rs::{impl_downcast, Downcast};
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// You can then create an [`AnimationClip`] to animate this property like so:
|
||||
///
|
||||
/// You can then create an [`AnimatableCurve`] to animate this property like so:
|
||||
///
|
||||
/// # use bevy_animation::{VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// # use bevy_animation::{AnimationClip, AnimationTargetId, VariableCurve, AnimationEntityMut, AnimationEvaluationError, animation_curves::EvaluatorId};
|
||||
/// # use bevy_animation::prelude::{AnimatableProperty, AnimatableKeyframeCurve, AnimatableCurve};
|
||||
/// # use bevy_ecs::{name::Name, component::Component};
|
||||
/// # use bevy_ecs::name::Name;
|
||||
/// # use bevy_reflect::Reflect;
|
||||
/// # use bevy_render::camera::{Projection, PerspectiveProjection};
|
||||
/// # use std::any::TypeId;
|
||||
/// # #[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 {
|
||||
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
||||
/// # }
|
||||
/// # let animation_target_id = AnimationTargetId::from(&Name::new("Test"));
|
||||
/// # #[derive(Reflect, Clone)]
|
||||
/// # 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,
|
||||
/// # >(
|
||||
/// # ))),
|
||||
/// # }
|
||||
/// # }
|
||||
/// # fn evaluator_id(&self) -> EvaluatorId {
|
||||
/// # EvaluatorId::Type(TypeId::of::<Self>())
|
||||
/// # }
|
||||
/// # }
|
||||
/// AnimatableCurve::new(
|
||||
/// PowerLevelProperty,
|
||||
/// AnimatableKeyframeCurve::new([
|
||||
/// (0.0, 0.0),
|
||||
/// (1.0, 9001.0),
|
||||
/// ]).expect("Failed to create power level curve")
|
||||
/// let mut animation_clip = AnimationClip::default();
|
||||
/// animation_clip.add_curve_to_target(
|
||||
/// animation_target_id,
|
||||
/// AnimatableCurve::new(
|
||||
/// FieldOfViewProperty,
|
||||
/// 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")
|
||||
/// )
|
||||
/// );
|
||||
///
|
||||
/// 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;
|
||||
|
@ -111,7 +111,6 @@ impl<T> CubicKeyframeCurve<T> {
|
||||
/// A keyframe-defined curve that uses cubic spline interpolation, special-cased for quaternions
|
||||
/// since it uses `Vec4` internally.
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub struct CubicRotationCurve {
|
||||
// Note: The sample width here should be 3.
|
||||
core: ChunkedUnevenCore<Vec4>,
|
||||
@ -373,9 +372,8 @@ 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_mesh::morph::MorphWeights
|
||||
/// [`MorphWeights`]: bevy_render::prelude::MorphWeights
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub enum WeightsCurve {
|
||||
/// A curve which takes a constant value over its domain. Notably, this is how animations with
|
||||
/// only a single keyframe are interpreted.
|
||||
|
@ -17,7 +17,7 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
system::{Res, ResMut},
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, ReflectSerialize};
|
||||
use derive_more::derive::From;
|
||||
use petgraph::{
|
||||
@ -108,7 +108,7 @@ use crate::{AnimationClip, AnimationTargetId};
|
||||
///
|
||||
/// [RFC 51]: https://github.com/bevyengine/rfcs/blob/main/rfcs/51-animation-composition.md
|
||||
#[derive(Asset, Reflect, Clone, Debug, Serialize)]
|
||||
#[reflect(Serialize, Debug, Clone)]
|
||||
#[reflect(Serialize, Debug)]
|
||||
#[serde(into = "SerializedAnimationGraph")]
|
||||
pub struct AnimationGraph {
|
||||
/// The `petgraph` data structure that defines the animation graph.
|
||||
@ -131,7 +131,7 @@ pub struct AnimationGraph {
|
||||
|
||||
/// A [`Handle`] to the [`AnimationGraph`] to be used by the [`AnimationPlayer`](crate::AnimationPlayer) on the same entity.
|
||||
#[derive(Component, Clone, Debug, Default, Deref, DerefMut, Reflect, PartialEq, Eq, From)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AnimationGraphHandle(pub Handle<AnimationGraph>);
|
||||
|
||||
impl From<AnimationGraphHandle> for AssetId<AnimationGraph> {
|
||||
@ -164,7 +164,6 @@ pub type AnimationNodeIndex = NodeIndex<u32>;
|
||||
/// of the graph, contain animation clips to play. Blend and add nodes describe
|
||||
/// how to combine their children to produce a final animation.
|
||||
#[derive(Clone, Reflect, Debug)]
|
||||
#[reflect(Clone)]
|
||||
pub struct AnimationGraphNode {
|
||||
/// Animation node data specific to the type of node (clip, blend, or add).
|
||||
///
|
||||
@ -206,7 +205,6 @@ pub struct AnimationGraphNode {
|
||||
/// In the case of clip nodes, this contains the actual animation clip
|
||||
/// associated with the node.
|
||||
#[derive(Clone, Default, Reflect, Debug)]
|
||||
#[reflect(Clone)]
|
||||
pub enum AnimationNodeType {
|
||||
/// A *clip node*, which plays an animation clip.
|
||||
///
|
||||
|
@ -35,7 +35,7 @@ use bevy_app::{Animation, App, Plugin, PostUpdate};
|
||||
use bevy_asset::{Asset, AssetApp, AssetEvents, Assets};
|
||||
use bevy_ecs::{prelude::*, world::EntityMutExcept};
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_platform::{collections::HashMap, hash::NoOpHash};
|
||||
use bevy_platform_support::{collections::HashMap, hash::NoOpHash};
|
||||
use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath};
|
||||
use bevy_time::Time;
|
||||
use bevy_transform::TransformSystem;
|
||||
@ -96,26 +96,23 @@ impl VariableCurve {
|
||||
/// Because animation clips refer to targets by UUID, they can target any
|
||||
/// [`AnimationTarget`] with that ID.
|
||||
#[derive(Asset, Reflect, Clone, Debug, Default)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub struct AnimationClip {
|
||||
// This field is ignored by reflection because AnimationCurves can contain things that are not reflect-able
|
||||
#[reflect(ignore, clone)]
|
||||
#[reflect(ignore)]
|
||||
curves: AnimationCurves,
|
||||
events: AnimationEvents,
|
||||
duration: f32,
|
||||
}
|
||||
|
||||
#[derive(Reflect, Debug, Clone)]
|
||||
#[reflect(Clone)]
|
||||
struct TimedAnimationEvent {
|
||||
time: f32,
|
||||
event: AnimationEvent,
|
||||
}
|
||||
|
||||
#[derive(Reflect, Debug, Clone)]
|
||||
#[reflect(Clone)]
|
||||
struct AnimationEvent {
|
||||
#[reflect(ignore, clone)]
|
||||
#[reflect(ignore)]
|
||||
trigger: AnimationEventFn,
|
||||
}
|
||||
|
||||
@ -127,7 +124,6 @@ impl AnimationEvent {
|
||||
|
||||
#[derive(Reflect, Clone)]
|
||||
#[reflect(opaque)]
|
||||
#[reflect(Clone, Default, Debug)]
|
||||
struct AnimationEventFn(Arc<dyn Fn(&mut Commands, Entity, f32, f32) + Send + Sync>);
|
||||
|
||||
impl Default for AnimationEventFn {
|
||||
@ -143,7 +139,6 @@ impl Debug for AnimationEventFn {
|
||||
}
|
||||
|
||||
#[derive(Reflect, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone)]
|
||||
#[reflect(Clone)]
|
||||
enum AnimationEventTarget {
|
||||
Root,
|
||||
Node(AnimationTargetId),
|
||||
@ -177,7 +172,6 @@ pub type AnimationCurves = HashMap<AnimationTargetId, Vec<VariableCurve>, NoOpHa
|
||||
///
|
||||
/// [UUID]: https://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Reflect, Debug, Serialize, Deserialize)]
|
||||
#[reflect(Clone)]
|
||||
pub struct AnimationTargetId(pub Uuid);
|
||||
|
||||
impl Hash for AnimationTargetId {
|
||||
@ -209,7 +203,7 @@ impl Hash for AnimationTargetId {
|
||||
/// time. However, you can change [`AnimationTarget`]'s `player` property at
|
||||
/// runtime to change which player is responsible for animating the entity.
|
||||
#[derive(Clone, Copy, Component, Reflect)]
|
||||
#[reflect(Component, Clone)]
|
||||
#[reflect(Component)]
|
||||
pub struct AnimationTarget {
|
||||
/// The ID of this animation target.
|
||||
///
|
||||
@ -431,7 +425,6 @@ impl AnimationClip {
|
||||
|
||||
/// Repetition behavior of an animation.
|
||||
#[derive(Reflect, Debug, PartialEq, Eq, Copy, Clone, Default)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub enum RepeatAnimation {
|
||||
/// The animation will finish after running once.
|
||||
#[default]
|
||||
@ -469,7 +462,6 @@ pub enum AnimationEvaluationError {
|
||||
///
|
||||
/// A stopped animation is considered no longer active.
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub struct ActiveAnimation {
|
||||
/// The factor by which the weight from the [`AnimationGraph`] is multiplied.
|
||||
weight: f32,
|
||||
@ -682,7 +674,7 @@ impl ActiveAnimation {
|
||||
/// Automatically added to any root animations of a scene when it is
|
||||
/// spawned.
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AnimationPlayer {
|
||||
active_animations: HashMap<AnimationNodeIndex, ActiveAnimation>,
|
||||
blend_weights: HashMap<AnimationNodeIndex, f32>,
|
||||
@ -758,10 +750,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::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
||||
bevy_platform_support::collections::hash_map::Entry::Occupied(occupied_entry) => {
|
||||
&mut **occupied_entry.into_mut()
|
||||
}
|
||||
bevy_platform::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||
bevy_platform_support::collections::hash_map::Entry::Vacant(vacant_entry) => {
|
||||
&mut **vacant_entry.insert(func())
|
||||
}
|
||||
},
|
||||
@ -1533,8 +1525,6 @@ impl<'a> Iterator for TriggeredEventsIter<'a> {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use bevy_reflect::{DynamicMap, Map};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[derive(Event, Reflect, Clone)]
|
||||
@ -1666,13 +1656,4 @@ 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()),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ use crate::{graph::AnimationNodeIndex, ActiveAnimation, AnimationPlayer};
|
||||
/// component to get confused about which animation is the "main" animation, and
|
||||
/// transitions will usually be incorrect as a result.
|
||||
#[derive(Component, Default, Reflect)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AnimationTransitions {
|
||||
main_animation: Option<AnimationNodeIndex>,
|
||||
transitions: Vec<AnimationTransition>,
|
||||
@ -52,7 +52,6 @@ impl Clone for AnimationTransitions {
|
||||
|
||||
/// An animation that is being faded out as part of a transition
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub struct AnimationTransition {
|
||||
/// The current weight. Starts at 1.0 and goes to 0.0 during the fade-out.
|
||||
current_weight: f32,
|
||||
@ -118,9 +117,8 @@ 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
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_app"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides core App functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -49,7 +49,7 @@ std = [
|
||||
"downcast-rs/std",
|
||||
"bevy_utils/std",
|
||||
"bevy_tasks/std",
|
||||
"bevy_platform/std",
|
||||
"bevy_platform_support/std",
|
||||
]
|
||||
|
||||
## `critical-section` provides the building blocks for synchronization primitives
|
||||
@ -57,14 +57,14 @@ std = [
|
||||
critical-section = [
|
||||
"bevy_tasks/critical-section",
|
||||
"bevy_ecs/critical-section",
|
||||
"bevy_platform/critical-section",
|
||||
"bevy_platform_support/critical-section",
|
||||
"bevy_reflect?/critical-section",
|
||||
]
|
||||
|
||||
## Enables use of browser APIs.
|
||||
## Note this is currently only applicable on `wasm32` architectures.
|
||||
web = [
|
||||
"bevy_platform/web",
|
||||
"bevy_platform_support/web",
|
||||
"bevy_tasks/web",
|
||||
"bevy_reflect?/web",
|
||||
"dep:wasm-bindgen",
|
||||
@ -74,14 +74,14 @@ web = [
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.1", default-features = false, features = [
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev", default-features = false }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.0-dev", default-features = false, features = [
|
||||
"alloc",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.1", default-features = false }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.1", 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 }
|
||||
|
||||
# other
|
||||
downcast-rs = { version = "2", default-features = false }
|
||||
|
@ -10,13 +10,14 @@ use alloc::{
|
||||
pub use bevy_derive::AppLabel;
|
||||
use bevy_ecs::{
|
||||
component::RequiredComponentsError,
|
||||
error::{BevyError, SystemErrorContext},
|
||||
event::{event_update_system, EventCursor},
|
||||
intern::Interned,
|
||||
prelude::*,
|
||||
schedule::{InternedSystemSet, ScheduleBuildSettings, ScheduleLabel},
|
||||
system::{IntoObserverSystem, ScheduleSystem, SystemId, SystemInput},
|
||||
schedule::{ScheduleBuildSettings, ScheduleLabel},
|
||||
system::{IntoObserverSystem, SystemId, SystemInput},
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use core::{fmt::Debug, num::NonZero, panic::AssertUnwindSafe};
|
||||
use log::debug;
|
||||
|
||||
@ -301,7 +302,7 @@ impl App {
|
||||
pub fn add_systems<M>(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
|
||||
systems: impl IntoSystemConfigs<M>,
|
||||
) -> &mut Self {
|
||||
self.main_mut().add_systems(schedule, systems);
|
||||
self
|
||||
@ -329,10 +330,10 @@ impl App {
|
||||
|
||||
/// Configures a collection of system sets in the provided schedule, adding any sets that do not exist.
|
||||
#[track_caller]
|
||||
pub fn configure_sets<M>(
|
||||
pub fn configure_sets(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
|
||||
sets: impl IntoSystemSetConfigs,
|
||||
) -> &mut Self {
|
||||
self.main_mut().configure_sets(schedule, sets);
|
||||
self
|
||||
@ -1273,6 +1274,18 @@ impl App {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the global system error handler to use for systems that return a [`Result`].
|
||||
///
|
||||
/// See the [`bevy_ecs::result` module-level documentation](../../bevy_ecs/result/index.html)
|
||||
/// 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.
|
||||
@ -1433,7 +1446,7 @@ mod tests {
|
||||
query::With,
|
||||
removal_detection::RemovedComponents,
|
||||
resource::Resource,
|
||||
schedule::{IntoScheduleConfigs, ScheduleLabel},
|
||||
schedule::{IntoSystemConfigs, ScheduleLabel},
|
||||
system::{Commands, Query},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
|
@ -3,7 +3,7 @@ use alloc::{vec, vec::Vec};
|
||||
use bevy_ecs::{
|
||||
resource::Resource,
|
||||
schedule::{
|
||||
ExecutorKind, InternedScheduleLabel, IntoScheduleConfigs, Schedule, ScheduleLabel,
|
||||
ExecutorKind, InternedScheduleLabel, IntoSystemSetConfigs, Schedule, ScheduleLabel,
|
||||
SystemSet,
|
||||
},
|
||||
system::Local,
|
||||
@ -15,13 +15,6 @@ 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`]
|
||||
@ -29,7 +22,7 @@ use bevy_ecs::{
|
||||
/// Then it will run:
|
||||
/// * [`First`]
|
||||
/// * [`PreUpdate`]
|
||||
/// * [`StateTransition`] [^1]
|
||||
/// * [`StateTransition`]
|
||||
/// * [`RunFixedMainLoop`]
|
||||
/// * This will run [`FixedMain`] zero to many times, based on how much time has elapsed.
|
||||
/// * [`Update`]
|
||||
@ -44,39 +37,35 @@ 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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct First;
|
||||
|
||||
/// The schedule that contains logic that must run before [`Update`]. For example, a system that reads raw keyboard
|
||||
@ -87,7 +76,7 @@ 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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct PreUpdate;
|
||||
|
||||
/// Runs the [`FixedMain`] schedule in a loop according until all relevant elapsed time has been "consumed".
|
||||
@ -99,21 +88,21 @@ pub struct PreUpdate;
|
||||
/// [`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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FixedPreUpdate;
|
||||
|
||||
/// The schedule that contains most gameplay logic, which runs at a fixed rate rather than every render frame.
|
||||
@ -128,7 +117,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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FixedUpdate;
|
||||
|
||||
/// The schedule that runs after the [`FixedUpdate`] schedule, for reacting
|
||||
@ -136,14 +125,14 @@ 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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FixedLast;
|
||||
|
||||
/// The schedule that contains systems which only run after a fixed period of time has elapsed.
|
||||
@ -155,7 +144,7 @@ pub struct FixedLast;
|
||||
/// 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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct FixedMain;
|
||||
|
||||
/// The schedule that contains any app logic that must run once per render frame.
|
||||
@ -168,13 +157,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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct SpawnScene;
|
||||
|
||||
/// The schedule that contains logic that must run after [`Update`]. For example, synchronizing "local transforms" in a hierarchy
|
||||
@ -185,13 +174,13 @@ 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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
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, Default)]
|
||||
#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Last;
|
||||
|
||||
/// Animation system set. This exists in [`PostUpdate`].
|
||||
@ -327,7 +316,7 @@ impl Plugin for MainSchedulePlugin {
|
||||
|
||||
#[cfg(feature = "bevy_debug_stepping")]
|
||||
{
|
||||
use bevy_ecs::schedule::{IntoScheduleConfigs, Stepping};
|
||||
use bevy_ecs::schedule::{IntoSystemConfigs, Stepping};
|
||||
app.add_systems(Main, Stepping::begin_frame.before(Main::run_main));
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use alloc::{
|
||||
string::{String, ToString},
|
||||
vec::Vec,
|
||||
};
|
||||
use bevy_platform::collections::hash_map::Entry;
|
||||
use bevy_platform_support::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::time::Instant;
|
||||
use bevy_platform_support::time::Instant;
|
||||
use core::time::Duration;
|
||||
|
||||
#[cfg(all(target_arch = "wasm32", feature = "web"))]
|
||||
@ -159,8 +159,9 @@ impl Plugin for ScheduleRunnerPlugin {
|
||||
} else {
|
||||
loop {
|
||||
match tick(&mut app, wait) {
|
||||
Ok(Some(delay)) => {
|
||||
bevy_platform::thread::sleep(delay);
|
||||
Ok(Some(_delay)) => {
|
||||
#[cfg(feature = "std")]
|
||||
std::thread::sleep(_delay);
|
||||
}
|
||||
Ok(None) => continue,
|
||||
Err(exit) => return exit,
|
||||
|
@ -1,12 +1,13 @@
|
||||
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},
|
||||
schedule::{InternedScheduleLabel, ScheduleBuildSettings, ScheduleLabel},
|
||||
system::{SystemId, SystemInput},
|
||||
};
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use core::fmt::Debug;
|
||||
|
||||
#[cfg(feature = "trace")]
|
||||
@ -211,7 +212,7 @@ impl SubApp {
|
||||
pub fn add_systems<M>(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
systems: impl IntoScheduleConfigs<ScheduleSystem, M>,
|
||||
systems: impl IntoSystemConfigs<M>,
|
||||
) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
schedules.add_systems(schedule, systems);
|
||||
@ -233,10 +234,10 @@ impl SubApp {
|
||||
|
||||
/// See [`App::configure_sets`].
|
||||
#[track_caller]
|
||||
pub fn configure_sets<M>(
|
||||
pub fn configure_sets(
|
||||
&mut self,
|
||||
schedule: impl ScheduleLabel,
|
||||
sets: impl IntoScheduleConfigs<InternedSystemSet, M>,
|
||||
sets: impl IntoSystemSetConfigs,
|
||||
) -> &mut Self {
|
||||
let mut schedules = self.world.resource_mut::<Schedules>();
|
||||
schedules.configure_sets(schedule, sets);
|
||||
@ -335,6 +336,22 @@ impl SubApp {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the global error handler to use for systems that return a [`Result`].
|
||||
///
|
||||
/// See the [`bevy_ecs::result` module-level documentation](../../bevy_ecs/result/index.html)
|
||||
/// 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,7 +1,7 @@
|
||||
use crate::{App, Plugin};
|
||||
|
||||
use alloc::string::ToString;
|
||||
use bevy_platform::sync::Arc;
|
||||
use bevy_platform_support::sync::Arc;
|
||||
use bevy_tasks::{AsyncComputeTaskPool, ComputeTaskPool, IoTaskPool, TaskPoolBuilder};
|
||||
use core::{fmt::Debug, marker::PhantomData};
|
||||
use log::trace;
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides asset functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -19,15 +19,16 @@ watch = []
|
||||
trace = []
|
||||
|
||||
[dependencies]
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1" }
|
||||
bevy_asset_macros = { path = "macros", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
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 = [
|
||||
"uuid",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.1" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.1", default-features = false, features = [
|
||||
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 = [
|
||||
"std",
|
||||
] }
|
||||
|
||||
@ -53,7 +54,7 @@ uuid = { version = "1.13.1", features = ["v4"] }
|
||||
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
|
||||
[target.'cfg(target_arch = "wasm32")'.dependencies]
|
||||
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.
|
||||
@ -66,13 +67,13 @@ web-sys = { version = "0.3", features = [
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
uuid = { version = "1.13.1", default-features = false, features = ["js"] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.1", default-features = false, features = [
|
||||
bevy_tasks = { path = "../bevy_tasks", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_asset_macros"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Derive implementations for bevy_asset"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -12,7 +12,7 @@ keywords = ["bevy"]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.1" }
|
||||
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.16.0-dev" }
|
||||
|
||||
syn = "2.0"
|
||||
proc-macro2 = "1.0"
|
||||
|
@ -13,7 +13,7 @@ use bevy_ecs::{
|
||||
storage::{Table, TableRow},
|
||||
world::unsafe_world_cell::UnsafeWorldCell,
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use core::marker::PhantomData;
|
||||
use disqualified::ShortName;
|
||||
use tracing::error;
|
||||
@ -281,7 +281,6 @@ 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 alloc::{vec, vec::Vec};
|
||||
@ -290,7 +289,7 @@ mod tests {
|
||||
|
||||
use crate::{AssetApp, Assets};
|
||||
use bevy_app::{App, AppExit, PostUpdate, Startup, TaskPoolPlugin, Update};
|
||||
use bevy_ecs::schedule::IntoScheduleConfigs;
|
||||
use bevy_ecs::schedule::IntoSystemConfigs;
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
event::EventWriter,
|
||||
|
@ -6,7 +6,7 @@ use bevy_ecs::{
|
||||
resource::Resource,
|
||||
system::{Res, ResMut, SystemChangeTick},
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::{Reflect, TypePath};
|
||||
use core::{any::TypeId, iter::Enumerate, marker::PhantomData, sync::atomic::AtomicU32};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
@ -462,22 +462,16 @@ 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) => {
|
||||
self.duplicate_handles.remove(&id);
|
||||
}
|
||||
None | Some(0) => {}
|
||||
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 });
|
||||
}
|
||||
@ -559,6 +553,7 @@ impl<A: Asset> Assets<A> {
|
||||
}
|
||||
}
|
||||
|
||||
assets.queued_events.push(AssetEvent::Unused { id });
|
||||
assets.remove_dropped(id);
|
||||
}
|
||||
}
|
||||
@ -600,7 +595,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::collections::hash_map::IterMut<'a, Uuid, A>,
|
||||
hash_map: bevy_platform_support::collections::hash_map::IterMut<'a, Uuid, A>,
|
||||
}
|
||||
|
||||
impl<'a, A: Asset> Iterator for AssetsMutIterator<'a, A> {
|
||||
|
@ -129,7 +129,7 @@ impl core::fmt::Debug for StrongHandle {
|
||||
///
|
||||
/// [`Handle::Strong`], via [`StrongHandle`] also provides access to useful [`Asset`] metadata, such as the [`AssetPath`] (if it exists).
|
||||
#[derive(Reflect)]
|
||||
#[reflect(Default, Debug, Hash, PartialEq, Clone)]
|
||||
#[reflect(Default, Debug, Hash, PartialEq)]
|
||||
pub enum Handle<A: Asset> {
|
||||
/// A "strong" reference to a live (or loading) [`Asset`]. If a [`Handle`] is [`Handle::Strong`], the [`Asset`] will be kept
|
||||
/// alive until the [`Handle`] is dropped. Strong handles also provide access to additional asset metadata.
|
||||
@ -150,10 +150,7 @@ 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(
|
||||
since = "0.16.0",
|
||||
note = "use the `weak_handle!` macro with a UUID string instead"
|
||||
)]
|
||||
#[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),
|
||||
@ -551,7 +548,7 @@ pub enum UntypedAssetConversionError {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use alloc::boxed::Box;
|
||||
use bevy_platform::hash::FixedHasher;
|
||||
use bevy_platform_support::hash::FixedHasher;
|
||||
use bevy_reflect::PartialReflect;
|
||||
use core::hash::BuildHasher;
|
||||
|
||||
@ -661,7 +658,7 @@ mod tests {
|
||||
assert_eq!(UntypedHandle::from(typed.clone()), untyped);
|
||||
}
|
||||
|
||||
/// `PartialReflect::reflect_clone`/`PartialReflect::to_dynamic` should increase the strong count of a strong handle
|
||||
/// `Reflect::clone_value` should increase the strong count of a strong handle
|
||||
#[test]
|
||||
fn strong_handle_reflect_clone() {
|
||||
use crate::{AssetApp, AssetPlugin, Assets, VisitAssetDependencies};
|
||||
@ -692,7 +689,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let reflected: &dyn Reflect = &handle;
|
||||
let _cloned_handle: Box<dyn Reflect> = reflected.reflect_clone().unwrap();
|
||||
let cloned_handle: Box<dyn PartialReflect> = reflected.clone_value();
|
||||
|
||||
assert_eq!(
|
||||
Arc::strong_count(strong),
|
||||
@ -700,18 +697,10 @@ mod tests {
|
||||
"Cloning the handle with reflect should increase the strong count to 2"
|
||||
);
|
||||
|
||||
let dynamic_handle: Box<dyn PartialReflect> = reflected.to_dynamic();
|
||||
|
||||
assert_eq!(
|
||||
Arc::strong_count(strong),
|
||||
3,
|
||||
"Converting the handle to a dynamic should increase the strong count to 3"
|
||||
);
|
||||
|
||||
let from_reflect_handle: Handle<MyAsset> =
|
||||
FromReflect::from_reflect(&*dynamic_handle).unwrap();
|
||||
FromReflect::from_reflect(&*cloned_handle).unwrap();
|
||||
|
||||
assert_eq!(Arc::strong_count(strong), 4, "Converting the reflected value back to a handle should increase the strong count to 4");
|
||||
assert_eq!(Arc::strong_count(strong), 3, "Converting the reflected value back to a handle should increase the strong count to 3");
|
||||
assert!(
|
||||
from_reflect_handle.is_strong(),
|
||||
"The cloned handle should still be strong"
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{Asset, AssetIndex};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_reflect::Reflect;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
@ -19,7 +19,6 @@ use thiserror::Error;
|
||||
///
|
||||
/// For an "untyped" / "generic-less" id, see [`UntypedAssetId`].
|
||||
#[derive(Reflect, Serialize, Deserialize, From)]
|
||||
#[reflect(Clone, Default, Debug, PartialEq, Hash)]
|
||||
pub enum AssetId<A: Asset> {
|
||||
/// 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: [`AssetId::Uuid`]) will only be used if assets are
|
||||
@ -30,7 +29,7 @@ pub enum AssetId<A: Asset> {
|
||||
/// The unstable, opaque index of the asset.
|
||||
index: AssetIndex,
|
||||
/// A marker to store the type information of the asset.
|
||||
#[reflect(ignore, clone)]
|
||||
#[reflect(ignore)]
|
||||
marker: PhantomData<fn() -> A>,
|
||||
},
|
||||
/// A stable-across-runs / const asset identifier. This will only be used if an asset is explicitly registered in [`Assets`]
|
||||
@ -441,7 +440,7 @@ mod tests {
|
||||
fn hash<T: Hash>(data: &T) -> u64 {
|
||||
use core::hash::BuildHasher;
|
||||
|
||||
bevy_platform::hash::FixedHasher.hash_one(data)
|
||||
bevy_platform_support::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::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use core::time::Duration;
|
||||
use notify_debouncer_full::{notify::RecommendedWatcher, Debouncer, RecommendedCache};
|
||||
use parking_lot::RwLock;
|
||||
|
@ -29,7 +29,7 @@ pub struct EmbeddedAssetRegistry {
|
||||
dir: Dir,
|
||||
#[cfg(feature = "embedded_watcher")]
|
||||
root_paths: alloc::sync::Arc<
|
||||
parking_lot::RwLock<bevy_platform::collections::HashMap<Box<Path>, PathBuf>>,
|
||||
parking_lot::RwLock<bevy_platform_support::collections::HashMap<Box<Path>, PathBuf>>,
|
||||
>,
|
||||
}
|
||||
|
||||
|
@ -35,7 +35,7 @@ impl FileWatcher {
|
||||
sender: Sender<AssetSourceEvent>,
|
||||
debounce_wait_time: Duration,
|
||||
) -> Result<Self, notify::Error> {
|
||||
let root = normalize_path(&path).canonicalize()?;
|
||||
let root = normalize_path(&path);
|
||||
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().ok()?;
|
||||
let absolute_path = absolute_path.canonicalize().unwrap();
|
||||
Some(get_asset_path(&self.root, &absolute_path))
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::io::{AssetReader, AssetReaderError, PathStream, Reader};
|
||||
use alloc::{boxed::Box, sync::Arc};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::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::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use core::{pin::Pin, task::Poll};
|
||||
use futures_io::AsyncRead;
|
||||
use futures_lite::{ready, Stream};
|
||||
@ -60,7 +60,8 @@ impl Dir {
|
||||
dir = self.get_or_insert_dir(parent);
|
||||
}
|
||||
let key: Box<str> = path.file_name().unwrap().to_string_lossy().into();
|
||||
dir.0.write().assets.remove(&key)
|
||||
let data = dir.0.write().assets.remove(&key);
|
||||
data
|
||||
}
|
||||
|
||||
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::collections::HashMap;
|
||||
use bevy_platform_support::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(&self, path: PathBuf) -> Result<impl Reader, AssetReaderError> {
|
||||
async fn fetch_bytes<'a>(&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() {
|
||||
@ -81,10 +81,7 @@ impl HttpWasmAssetReader {
|
||||
let reader = VecReader::new(bytes);
|
||||
Ok(reader)
|
||||
}
|
||||
// Some web servers, including itch.io's CDN, return 403 when a requested file isn't present.
|
||||
// TODO: remove handling of 403 as not found when it's easier to configure
|
||||
// see https://github.com/bevyengine/bevy/pull/19268#pullrequestreview-2882410105
|
||||
403 | 404 => Err(AssetReaderError::NotFound(path)),
|
||||
404 => Err(AssetReaderError::NotFound(path)),
|
||||
status => Err(AssetReaderError::HttpError(status)),
|
||||
}
|
||||
}
|
||||
|
@ -220,10 +220,10 @@ use bevy_app::{App, Plugin, PostUpdate, PreUpdate};
|
||||
use bevy_ecs::prelude::Component;
|
||||
use bevy_ecs::{
|
||||
reflect::AppTypeRegistry,
|
||||
schedule::{IntoScheduleConfigs, SystemSet},
|
||||
schedule::{IntoSystemConfigs, IntoSystemSetConfigs, SystemSet},
|
||||
world::FromWorld,
|
||||
};
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_reflect::{FromReflect, GetTypeRegistration, Reflect, TypePath};
|
||||
use core::any::TypeId;
|
||||
use tracing::error;
|
||||
@ -258,33 +258,6 @@ 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.
|
||||
@ -338,7 +311,6 @@ 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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -379,7 +351,6 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Unprocessed,
|
||||
self.meta_check.clone(),
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
));
|
||||
}
|
||||
AssetMode::Processed => {
|
||||
@ -396,7 +367,6 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
))
|
||||
.insert_resource(processor)
|
||||
.add_systems(bevy_app::Startup, AssetProcessor::start);
|
||||
@ -410,7 +380,6 @@ impl Plugin for AssetPlugin {
|
||||
AssetServerMode::Processed,
|
||||
AssetMetaCheck::Always,
|
||||
watch,
|
||||
self.unapproved_path_mode.clone(),
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -670,7 +639,7 @@ mod tests {
|
||||
},
|
||||
loader::{AssetLoader, LoadContext},
|
||||
Asset, AssetApp, AssetEvent, AssetId, AssetLoadError, AssetLoadFailedEvent, AssetPath,
|
||||
AssetPlugin, AssetServer, Assets, LoadState, UnapprovedPathMode,
|
||||
AssetPlugin, AssetServer, Assets, DuplicateLabelAssetError, LoadState,
|
||||
};
|
||||
use alloc::{
|
||||
boxed::Box,
|
||||
@ -686,7 +655,8 @@ mod tests {
|
||||
prelude::*,
|
||||
schedule::{LogLevel, ScheduleBuildSettings},
|
||||
};
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_log::LogPlugin;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_reflect::TypePath;
|
||||
use core::time::Duration;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -725,6 +695,8 @@ 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),
|
||||
}
|
||||
@ -755,7 +727,7 @@ mod tests {
|
||||
.map_err(|_| Self::Error::CannotLoadDependency {
|
||||
dependency: dep.into(),
|
||||
})?;
|
||||
let cool = loaded.get();
|
||||
let cool = loaded.get_asset().get();
|
||||
embedded.push_str(&cool.text);
|
||||
}
|
||||
Ok(CoolText {
|
||||
@ -770,7 +742,7 @@ mod tests {
|
||||
.sub_texts
|
||||
.drain(..)
|
||||
.map(|text| load_context.add_labeled_asset(text.clone(), SubText { text }))
|
||||
.collect(),
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
})
|
||||
}
|
||||
|
||||
@ -854,7 +826,11 @@ mod tests {
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build().with_reader(move || Box::new(gated_memory_reader.clone())),
|
||||
)
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
));
|
||||
(app, gate_opener)
|
||||
}
|
||||
|
||||
@ -1752,7 +1728,11 @@ mod tests {
|
||||
"unstable",
|
||||
AssetSource::build().with_reader(move || Box::new(unstable_reader.clone())),
|
||||
)
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()))
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
))
|
||||
.init_asset::<CoolText>()
|
||||
.register_asset_loader(CoolTextLoader)
|
||||
.init_resource::<ErrorTracker>()
|
||||
@ -1800,73 +1780,43 @@ mod tests {
|
||||
app.world_mut().run_schedule(Update);
|
||||
}
|
||||
|
||||
// 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]
|
||||
fn error_on_nested_immediate_load_of_subasset() {
|
||||
fn fails_to_load_for_duplicate_subasset_labels() {
|
||||
let mut app = App::new();
|
||||
|
||||
let dir = Dir::default();
|
||||
dir.insert_asset_text(
|
||||
Path::new("a.cool.ron"),
|
||||
Path::new("a.ron"),
|
||||
r#"(
|
||||
text: "b",
|
||||
dependencies: [],
|
||||
embedded_dependencies: [],
|
||||
sub_texts: ["A"],
|
||||
sub_texts: ["A", "A"],
|
||||
)"#,
|
||||
);
|
||||
dir.insert_asset_text(Path::new("empty.txt"), "");
|
||||
|
||||
app.register_asset_source(
|
||||
AssetSourceId::Default,
|
||||
AssetSource::build()
|
||||
.with_reader(move || Box::new(MemoryAssetReader { root: dir.clone() })),
|
||||
)
|
||||
.add_plugins((TaskPoolPlugin::default(), AssetPlugin::default()));
|
||||
.add_plugins((
|
||||
TaskPoolPlugin::default(),
|
||||
LogPlugin::default(),
|
||||
AssetPlugin::default(),
|
||||
));
|
||||
|
||||
app.init_asset::<CoolText>()
|
||||
.init_asset::<SubText>()
|
||||
.register_asset_loader(CoolTextLoader);
|
||||
|
||||
struct NestedLoadOfSubassetLoader;
|
||||
|
||||
impl AssetLoader for NestedLoadOfSubassetLoader {
|
||||
type Asset = TestAsset;
|
||||
type Error = crate::loader::LoadDirectError;
|
||||
type Settings = ();
|
||||
|
||||
async fn load(
|
||||
&self,
|
||||
_: &mut dyn Reader,
|
||||
_: &Self::Settings,
|
||||
load_context: &mut LoadContext<'_>,
|
||||
) -> Result<Self::Asset, Self::Error> {
|
||||
// We expect this load to fail.
|
||||
load_context
|
||||
.loader()
|
||||
.immediate()
|
||||
.load::<SubText>("a.cool.ron#A")
|
||||
.await?;
|
||||
Ok(TestAsset)
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] {
|
||||
&["txt"]
|
||||
}
|
||||
}
|
||||
|
||||
app.init_asset::<TestAsset>()
|
||||
.register_asset_loader(NestedLoadOfSubassetLoader);
|
||||
|
||||
let asset_server = app.world().resource::<AssetServer>().clone();
|
||||
let handle = asset_server.load::<TestAsset>("empty.txt");
|
||||
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) => {
|
||||
let error_message = format!("{err}");
|
||||
assert!(error_message.contains("Requested to load an asset path (a.cool.ron#A) with a subasset, but this is unsupported"), "what? \"{error_message}\"");
|
||||
assert!(matches!(*err, AssetLoadError::AssetLoaderError(_)));
|
||||
Some(())
|
||||
}
|
||||
state => panic!("Unexpected asset state: {state:?}"),
|
||||
@ -1906,91 +1856,4 @@ 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::collections::{HashMap, HashSet};
|
||||
use bevy_platform_support::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<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
Result<CompleteErasedLoadedAsset, 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<ErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
Result<CompleteErasedLoadedAsset, Box<dyn core::error::Error + Send + Sync + 'static>>,
|
||||
> {
|
||||
Box::pin(async move {
|
||||
let settings = meta
|
||||
@ -152,7 +152,6 @@ 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> {
|
||||
@ -166,7 +165,6 @@ impl<A: Asset> LoadedAsset<A> {
|
||||
value,
|
||||
dependencies,
|
||||
loader_dependencies: HashMap::default(),
|
||||
labeled_assets: HashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -179,19 +177,6 @@ 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> {
|
||||
@ -205,7 +190,6 @@ 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 {
|
||||
@ -214,7 +198,6 @@ 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -241,19 +224,6 @@ 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> {
|
||||
@ -262,7 +232,6 @@ impl ErasedLoadedAsset {
|
||||
value: *value,
|
||||
dependencies: self.dependencies,
|
||||
loader_dependencies: self.loader_dependencies,
|
||||
labeled_assets: self.labeled_assets,
|
||||
}),
|
||||
Err(value) => {
|
||||
self.value = value;
|
||||
@ -290,20 +259,110 @@ 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].
|
||||
///
|
||||
/// [`NestedLoader::load`]: crate::NestedLoader::load
|
||||
/// [immediately]: crate::Immediate
|
||||
#[derive(Error, Debug)]
|
||||
pub enum LoadDirectError {
|
||||
#[error("Requested to load an asset path ({0:?}) with a subasset, but this is unsupported. See issue #18291")]
|
||||
RequestedSubasset(AssetPath<'static>),
|
||||
#[error("Failed to load dependency {dependency:?} {error}")]
|
||||
LoadError {
|
||||
dependency: AssetPath<'static>,
|
||||
error: AssetLoadError,
|
||||
},
|
||||
#[error("Failed to load dependency {dependency:?} {error}")]
|
||||
pub struct LoadDirectError {
|
||||
pub dependency: AssetPath<'static>,
|
||||
pub error: AssetLoadError,
|
||||
}
|
||||
|
||||
/// An error that occurs while deserializing [`AssetMeta`].
|
||||
@ -398,11 +457,11 @@ impl<'a> LoadContext<'a> {
|
||||
&mut self,
|
||||
label: String,
|
||||
load: impl FnOnce(&mut LoadContext) -> A,
|
||||
) -> Handle<A> {
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
let mut context = self.begin_labeled_asset();
|
||||
let asset = load(&mut context);
|
||||
let loaded_asset = context.finish(asset);
|
||||
self.add_loaded_labeled_asset(label, loaded_asset)
|
||||
let complete_asset = context.finish(asset);
|
||||
self.add_loaded_labeled_asset(label, complete_asset)
|
||||
}
|
||||
|
||||
/// This will add the given `asset` as a "labeled [`Asset`]" with the `label` label.
|
||||
@ -415,7 +474,11 @@ 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) -> Handle<A> {
|
||||
pub fn add_labeled_asset<A: Asset>(
|
||||
&mut self,
|
||||
label: String,
|
||||
asset: A,
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
self.labeled_asset_scope(label, |_| asset)
|
||||
}
|
||||
|
||||
@ -427,22 +490,37 @@ impl<'a> LoadContext<'a> {
|
||||
pub fn add_loaded_labeled_asset<A: Asset>(
|
||||
&mut self,
|
||||
label: impl Into<CowArc<'static, str>>,
|
||||
loaded_asset: LoadedAsset<A>,
|
||||
) -> Handle<A> {
|
||||
loaded_asset: CompleteLoadedAsset<A>,
|
||||
) -> Result<Handle<A>, DuplicateLabelAssetError> {
|
||||
let label = label.into();
|
||||
let loaded_asset: ErasedLoadedAsset = loaded_asset.into();
|
||||
let CompleteLoadedAsset {
|
||||
asset,
|
||||
labeled_assets,
|
||||
} = loaded_asset;
|
||||
let loaded_asset: ErasedLoadedAsset = 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);
|
||||
self.labeled_assets.insert(
|
||||
label,
|
||||
LabeledAsset {
|
||||
asset: loaded_asset,
|
||||
handle: handle.clone().untyped(),
|
||||
},
|
||||
);
|
||||
handle
|
||||
let has_duplicate = self
|
||||
.labeled_assets
|
||||
.insert(
|
||||
label.clone(),
|
||||
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)
|
||||
}
|
||||
|
||||
/// Returns `true` if an asset with the label `label` exists in this context.
|
||||
@ -454,11 +532,13 @@ impl<'a> LoadContext<'a> {
|
||||
}
|
||||
|
||||
/// "Finishes" this context by populating the final [`Asset`] value.
|
||||
pub fn finish<A: Asset>(self, value: A) -> LoadedAsset<A> {
|
||||
LoadedAsset {
|
||||
value,
|
||||
dependencies: self.dependencies,
|
||||
loader_dependencies: self.loader_dependencies,
|
||||
pub fn finish<A: Asset>(self, value: A) -> CompleteLoadedAsset<A> {
|
||||
CompleteLoadedAsset {
|
||||
asset: LoadedAsset {
|
||||
value,
|
||||
dependencies: self.dependencies,
|
||||
loader_dependencies: self.loader_dependencies,
|
||||
},
|
||||
labeled_assets: self.labeled_assets,
|
||||
}
|
||||
}
|
||||
@ -529,8 +609,8 @@ impl<'a> LoadContext<'a> {
|
||||
meta: &dyn AssetMetaDyn,
|
||||
loader: &dyn ErasedAssetLoader,
|
||||
reader: &mut dyn Reader,
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
let loaded_asset = self
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
let complete_asset = self
|
||||
.asset_server
|
||||
.load_with_meta_loader_and_reader(
|
||||
&path,
|
||||
@ -541,14 +621,14 @@ impl<'a> LoadContext<'a> {
|
||||
self.populate_hashes,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| LoadDirectError::LoadError {
|
||||
.map_err(|error| LoadDirectError {
|
||||
dependency: path.clone(),
|
||||
error,
|
||||
})?;
|
||||
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(loaded_asset)
|
||||
Ok(complete_asset)
|
||||
}
|
||||
|
||||
/// Create a builder for loading nested assets in this context.
|
||||
@ -590,3 +670,8 @@ 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, ErasedAssetLoader, ErasedLoadedAsset, Handle, LoadContext,
|
||||
LoadDirectError, LoadedAsset, LoadedUntypedAsset, UntypedHandle,
|
||||
Asset, AssetLoadError, AssetPath, CompleteErasedLoadedAsset, CompleteLoadedAsset,
|
||||
ErasedAssetLoader, Handle, LoadContext, LoadDirectError, 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 [`ErasedLoadedAsset`] (via [`Immediate`]).
|
||||
/// or a [`CompleteErasedLoadedAsset`] (via [`Immediate`]).
|
||||
///
|
||||
/// - in [`UnknownTyped`]: loading either a type-erased version of the asset
|
||||
/// ([`ErasedLoadedAsset`]), or a handle *to a handle* of the actual asset
|
||||
/// ([`LoadedUntypedAsset`]).
|
||||
/// ([`CompleteErasedLoadedAsset`]), 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,12 +305,9 @@ 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,
|
||||
(),
|
||||
true,
|
||||
)
|
||||
self.load_context
|
||||
.asset_server
|
||||
.load_with_meta_transform(path, self.meta_transform, ())
|
||||
} else {
|
||||
self.load_context
|
||||
.asset_server
|
||||
@ -389,17 +386,14 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
|
||||
self,
|
||||
path: &AssetPath<'static>,
|
||||
asset_type_id: Option<TypeId>,
|
||||
) -> Result<(Arc<dyn ErasedAssetLoader>, ErasedLoadedAsset), LoadDirectError> {
|
||||
if path.label().is_some() {
|
||||
return Err(LoadDirectError::RequestedSubasset(path.clone()));
|
||||
}
|
||||
) -> Result<(Arc<dyn ErasedAssetLoader>, CompleteErasedLoadedAsset), LoadDirectError> {
|
||||
let (mut meta, loader, mut reader) = if let Some(reader) = self.mode.reader {
|
||||
let loader = if let Some(asset_type_id) = asset_type_id {
|
||||
self.load_context
|
||||
.asset_server
|
||||
.get_asset_loader_with_asset_type_id(asset_type_id)
|
||||
.await
|
||||
.map_err(|error| LoadDirectError::LoadError {
|
||||
.map_err(|error| LoadDirectError {
|
||||
dependency: path.clone(),
|
||||
error: error.into(),
|
||||
})?
|
||||
@ -408,7 +402,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
|
||||
.asset_server
|
||||
.get_path_asset_loader(path)
|
||||
.await
|
||||
.map_err(|error| LoadDirectError::LoadError {
|
||||
.map_err(|error| LoadDirectError {
|
||||
dependency: path.clone(),
|
||||
error: error.into(),
|
||||
})?
|
||||
@ -421,7 +415,7 @@ impl<'builder, 'reader, T> NestedLoader<'_, '_, T, Immediate<'builder, 'reader>>
|
||||
.asset_server
|
||||
.get_meta_loader_and_reader(path, asset_type_id)
|
||||
.await
|
||||
.map_err(|error| LoadDirectError::LoadError {
|
||||
.map_err(|error| LoadDirectError {
|
||||
dependency: path.clone(),
|
||||
error,
|
||||
})?;
|
||||
@ -454,22 +448,20 @@ impl NestedLoader<'_, '_, StaticTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p, A: Asset>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<LoadedAsset<A>, LoadDirectError> {
|
||||
) -> Result<CompleteLoadedAsset<A>, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
self.load_internal(&path, Some(TypeId::of::<A>()))
|
||||
.await
|
||||
.and_then(move |(loader, untyped_asset)| {
|
||||
untyped_asset
|
||||
.downcast::<A>()
|
||||
.map_err(|_| LoadDirectError::LoadError {
|
||||
dependency: path.clone(),
|
||||
error: AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path,
|
||||
requested: TypeId::of::<A>(),
|
||||
actual_asset_name: loader.asset_type_name(),
|
||||
loader_name: loader.type_name(),
|
||||
},
|
||||
})
|
||||
untyped_asset.downcast::<A>().map_err(|_| LoadDirectError {
|
||||
dependency: path.clone(),
|
||||
error: AssetLoadError::RequestedHandleTypeMismatch {
|
||||
path,
|
||||
requested: TypeId::of::<A>(),
|
||||
actual_asset_name: loader.asset_type_name(),
|
||||
loader_name: loader.type_name(),
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -484,7 +476,7 @@ impl NestedLoader<'_, '_, DynamicTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
let asset_type_id = Some(self.typing.asset_type_id);
|
||||
self.load_internal(&path, asset_type_id)
|
||||
@ -500,7 +492,7 @@ impl NestedLoader<'_, '_, UnknownTyped, Immediate<'_, '_>> {
|
||||
pub async fn load<'p>(
|
||||
self,
|
||||
path: impl Into<AssetPath<'p>>,
|
||||
) -> Result<ErasedLoadedAsset, LoadDirectError> {
|
||||
) -> Result<CompleteErasedLoadedAsset, LoadDirectError> {
|
||||
let path = path.into().into_owned();
|
||||
self.load_internal(&path, None)
|
||||
.await
|
||||
|
@ -53,7 +53,7 @@ use thiserror::Error;
|
||||
/// This also means that you should use [`AssetPath::parse`] in cases where `&str` is the explicit type.
|
||||
#[derive(Eq, PartialEq, Hash, Clone, Default, Reflect)]
|
||||
#[reflect(opaque)]
|
||||
#[reflect(Debug, PartialEq, Hash, Clone, Serialize, Deserialize)]
|
||||
#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize)]
|
||||
pub struct AssetPath<'a> {
|
||||
source: AssetSourceId<'a>,
|
||||
path: CowArc<'a, Path>,
|
||||
@ -478,51 +478,6 @@ impl<'a> AssetPath<'a> {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns `true` if this [`AssetPath`] points to a file that is
|
||||
/// outside of it's [`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::collections::HashSet;
|
||||
use bevy_platform_support::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, UnapprovedPathMode, WriteDefaultMetaError,
|
||||
MissingAssetLoaderForExtensionError, WriteDefaultMetaError,
|
||||
};
|
||||
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, sync::Arc, vec, vec::Vec};
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_tasks::IoTaskPool;
|
||||
use futures_io::ErrorKind;
|
||||
use futures_lite::{AsyncReadExt, AsyncWriteExt, StreamExt};
|
||||
@ -122,7 +122,6 @@ 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, DeserializeMetaError, ErasedLoadedAsset,
|
||||
AssetLoadError, AssetLoader, AssetPath, CompleteErasedLoadedAsset, DeserializeMetaError,
|
||||
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<ErasedLoadedAsset, AssetLoadError> {
|
||||
) -> Result<CompleteErasedLoadedAsset, 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 loaded_asset = server
|
||||
let complete_asset = server
|
||||
.load_with_meta_loader_and_reader(self.path, &meta, &*loader, &mut reader, false, true)
|
||||
.await?;
|
||||
for (path, full_hash) in &loaded_asset.loader_dependencies {
|
||||
for (path, full_hash) in &complete_asset.asset.loader_dependencies {
|
||||
self.new_processed_info
|
||||
.process_dependencies
|
||||
.push(ProcessDependencyInfo {
|
||||
@ -321,7 +321,7 @@ impl<'a> ProcessContext<'a> {
|
||||
path: path.to_owned(),
|
||||
});
|
||||
}
|
||||
Ok(loaded_asset)
|
||||
Ok(complete_asset)
|
||||
}
|
||||
|
||||
/// The path of the asset being processed.
|
||||
|
@ -27,7 +27,7 @@ bitflags::bitflags! {
|
||||
#[repr(transparent)]
|
||||
#[derive(Serialize, Deserialize, Hash, Clone, Copy, PartialEq, Eq, Debug, Reflect)]
|
||||
#[reflect(opaque)]
|
||||
#[reflect(Serialize, Deserialize, Hash, Clone, PartialEq, Debug)]
|
||||
#[reflect(Serialize, Deserialize, Hash, PartialEq, Debug)]
|
||||
pub struct RenderAssetUsages: u8 {
|
||||
/// The bit flag for the main world.
|
||||
const MAIN_WORLD = 1 << 0;
|
||||
|
@ -1,10 +1,10 @@
|
||||
use crate::{
|
||||
io::Writer, meta::Settings, transformer::TransformedAsset, Asset, AssetLoader,
|
||||
ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
|
||||
CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use atomicow::CowArc;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::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,
|
||||
asset: &'a ErasedLoadedAsset,
|
||||
complete_asset: &'a CompleteErasedLoadedAsset,
|
||||
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,
|
||||
asset: &'a ErasedLoadedAsset,
|
||||
complete_asset: &'a CompleteErasedLoadedAsset,
|
||||
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(asset).unwrap();
|
||||
let saved_asset = SavedAsset::<S::Asset>::from_loaded(complete_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(asset: &'a ErasedLoadedAsset) -> Option<Self> {
|
||||
let value = asset.value.downcast_ref::<A>()?;
|
||||
pub fn from_loaded(complete_asset: &'a CompleteErasedLoadedAsset) -> Option<Self> {
|
||||
let value = complete_asset.asset.value.downcast_ref::<A>()?;
|
||||
Some(SavedAsset {
|
||||
value,
|
||||
labeled_assets: &asset.labeled_assets,
|
||||
labeled_assets: &complete_asset.labeled_assets,
|
||||
})
|
||||
}
|
||||
|
||||
@ -114,17 +114,13 @@ 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<SavedAsset<B>>
|
||||
pub fn get_labeled<B: Asset, Q>(&self, label: &Q) -> Option<&B>
|
||||
where
|
||||
CowArc<'static, str>: Borrow<Q>,
|
||||
Q: ?Sized + Hash + Eq,
|
||||
{
|
||||
let labeled = self.labeled_assets.get(label)?;
|
||||
let value = labeled.asset.value.downcast_ref::<B>()?;
|
||||
Some(SavedAsset {
|
||||
value,
|
||||
labeled_assets: &labeled.asset.labeled_assets,
|
||||
})
|
||||
labeled.asset.value.downcast_ref::<B>()
|
||||
}
|
||||
|
||||
/// 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::collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use bevy_platform_support::collections::{hash_map::Entry, HashMap, HashSet};
|
||||
use bevy_tasks::Task;
|
||||
use bevy_utils::TypeIdMap;
|
||||
use core::{any::TypeId, task::Waker};
|
||||
@ -347,9 +347,14 @@ 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 {
|
||||
self.get_path_ids(&path.into())
|
||||
let path = path.into();
|
||||
|
||||
let result = self
|
||||
.get_path_ids(&path)
|
||||
.filter_map(|id| self.infos.get(&id))
|
||||
.any(|info| info.weak_handle.strong_count() > 0)
|
||||
.any(|info| info.weak_handle.strong_count() > 0);
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// 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::collections::HashMap;
|
||||
use bevy_platform_support::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,
|
||||
DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset, UnapprovedPathMode,
|
||||
CompleteErasedLoadedAsset, DeserializeMetaError, ErasedLoadedAsset, Handle, LoadedUntypedAsset,
|
||||
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::collections::HashSet;
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_tasks::IoTaskPool;
|
||||
use core::{any::TypeId, future::Future, panic::AssertUnwindSafe, task::Poll};
|
||||
use crossbeam_channel::{Receiver, Sender};
|
||||
@ -68,7 +68,6 @@ pub(crate) struct AssetServerData {
|
||||
sources: AssetSources,
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
}
|
||||
|
||||
/// The "asset mode" the server is currently in.
|
||||
@ -83,19 +82,13 @@ 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,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
) -> Self {
|
||||
pub fn new(sources: AssetSources, mode: AssetServerMode, watching_for_changes: bool) -> Self {
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
Default::default(),
|
||||
mode,
|
||||
AssetMetaCheck::Always,
|
||||
watching_for_changes,
|
||||
unapproved_path_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -106,7 +99,6 @@ impl AssetServer {
|
||||
mode: AssetServerMode,
|
||||
meta_check: AssetMetaCheck,
|
||||
watching_for_changes: bool,
|
||||
unapproved_path_mode: UnapprovedPathMode,
|
||||
) -> Self {
|
||||
Self::new_with_loaders(
|
||||
sources,
|
||||
@ -114,7 +106,6 @@ impl AssetServer {
|
||||
mode,
|
||||
meta_check,
|
||||
watching_for_changes,
|
||||
unapproved_path_mode,
|
||||
)
|
||||
}
|
||||
|
||||
@ -124,7 +115,6 @@ 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();
|
||||
@ -138,7 +128,6 @@ impl AssetServer {
|
||||
asset_event_receiver,
|
||||
loaders,
|
||||
infos: RwLock::new(infos),
|
||||
unapproved_path_mode,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@ -322,16 +311,7 @@ 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, (), 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)
|
||||
self.load_with_meta_transform(path, None, ())
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
|
||||
@ -355,20 +335,7 @@ impl AssetServer {
|
||||
path: impl Into<AssetPath<'a>>,
|
||||
guard: G,
|
||||
) -> Handle<A> {
|
||||
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)
|
||||
self.load_with_meta_transform(path, None, guard)
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path`. The given `settings` function will override the asset's
|
||||
@ -380,30 +347,7 @@ 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)),
|
||||
(),
|
||||
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,
|
||||
)
|
||||
self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), ())
|
||||
}
|
||||
|
||||
/// Begins loading an [`Asset`] of type `A` stored at `path` while holding a guard item.
|
||||
@ -422,36 +366,7 @@ 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,
|
||||
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,
|
||||
)
|
||||
self.load_with_meta_transform(path, Some(loader_settings_meta_transform(settings)), guard)
|
||||
}
|
||||
|
||||
pub(crate) fn load_with_meta_transform<'a, A: Asset, G: Send + Sync + 'static>(
|
||||
@ -459,20 +374,8 @@ 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(),
|
||||
@ -796,12 +699,18 @@ 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 loaded_asset: ErasedLoadedAsset) {
|
||||
for (_, labeled_asset) in loaded_asset.labeled_assets.drain() {
|
||||
self.send_loaded_asset(labeled_asset.handle.id(), labeled_asset.asset);
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
||||
self.send_asset_event(InternalAssetEvent::Loaded { id, loaded_asset });
|
||||
self.send_asset_event(InternalAssetEvent::Loaded {
|
||||
id,
|
||||
loaded_asset: complete_asset.asset,
|
||||
});
|
||||
}
|
||||
|
||||
/// Kicks off a reload of the asset stored at the given path. This will only reload the asset if it currently loaded.
|
||||
@ -1425,7 +1334,7 @@ impl AssetServer {
|
||||
reader: &mut dyn Reader,
|
||||
load_dependencies: bool,
|
||||
populate_hashes: bool,
|
||||
) -> Result<ErasedLoadedAsset, AssetLoadError> {
|
||||
) -> Result<CompleteErasedLoadedAsset, AssetLoadError> {
|
||||
// TODO: experiment with this
|
||||
let asset_path = asset_path.clone_owned();
|
||||
let load_context =
|
||||
|
@ -1,7 +1,10 @@
|
||||
use crate::{meta::Settings, Asset, ErasedLoadedAsset, Handle, LabeledAsset, UntypedHandle};
|
||||
use crate::{
|
||||
meta::Settings, Asset, CompleteErasedLoadedAsset, ErasedLoadedAsset, Handle, LabeledAsset,
|
||||
UntypedHandle,
|
||||
};
|
||||
use alloc::boxed::Box;
|
||||
use atomicow::CowArc;
|
||||
use bevy_platform::collections::HashMap;
|
||||
use bevy_platform_support::collections::HashMap;
|
||||
use bevy_tasks::ConditionalSendFuture;
|
||||
use core::{
|
||||
borrow::Borrow,
|
||||
@ -56,11 +59,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(asset: ErasedLoadedAsset) -> Option<Self> {
|
||||
if let Ok(value) = asset.value.downcast::<A>() {
|
||||
pub fn from_loaded(complete_asset: CompleteErasedLoadedAsset) -> Option<Self> {
|
||||
if let Ok(value) = complete_asset.asset.value.downcast::<A>() {
|
||||
return Some(TransformedAsset {
|
||||
value: *value,
|
||||
labeled_assets: asset.labeled_assets,
|
||||
labeled_assets: complete_asset.labeled_assets,
|
||||
});
|
||||
}
|
||||
None
|
||||
@ -87,117 +90,13 @@ 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<TransformedSubAsset<B>>
|
||||
pub fn get_labeled<B: Asset, Q>(&mut self, label: &'_ Q) -> Option<&mut 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>
|
||||
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,
|
||||
})
|
||||
labeled.asset.value.downcast_mut::<B>()
|
||||
}
|
||||
/// Returns the type-erased labeled asset, if it exists and matches this type.
|
||||
pub fn get_erased_labeled<Q>(&self, label: &Q) -> Option<&ErasedLoadedAsset>
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_audio"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Provides audio functionality for Bevy Engine"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,13 +10,13 @@ keywords = ["bevy"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.1" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.1" }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
|
||||
# other
|
||||
rodio = { version = "0.20", default-features = false }
|
||||
@ -30,10 +30,10 @@ cpal = { version = "0.15", optional = true }
|
||||
rodio = { version = "0.20", default-features = false, features = [
|
||||
"wasm-bindgen",
|
||||
] }
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", default-features = false, features = [
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, features = [
|
||||
"web",
|
||||
] }
|
||||
|
||||
|
@ -6,7 +6,6 @@ use bevy_reflect::prelude::*;
|
||||
|
||||
/// The way Bevy manages the sound playback.
|
||||
#[derive(Debug, Clone, Copy, Reflect)]
|
||||
#[reflect(Clone)]
|
||||
pub enum PlaybackMode {
|
||||
/// Play the sound once. Do nothing when it ends.
|
||||
///
|
||||
@ -30,7 +29,7 @@ pub enum PlaybackMode {
|
||||
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink]
|
||||
/// components. Changes to this component will *not* be applied to already-playing audio.
|
||||
#[derive(Component, Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
#[reflect(Default, Component, Debug)]
|
||||
pub struct PlaybackSettings {
|
||||
/// The desired playback behavior.
|
||||
pub mode: PlaybackMode,
|
||||
@ -143,7 +142,7 @@ impl PlaybackSettings {
|
||||
/// This must be accompanied by `Transform` and `GlobalTransform`.
|
||||
/// Only one entity with a `SpatialListener` should be present at any given time.
|
||||
#[derive(Component, Clone, Debug, Reflect)]
|
||||
#[reflect(Clone, Default, Component, Debug)]
|
||||
#[reflect(Default, Component, Debug)]
|
||||
pub struct SpatialListener {
|
||||
/// Left ear position relative to the `GlobalTransform`.
|
||||
pub left_ear_offset: Vec3,
|
||||
@ -175,7 +174,6 @@ impl SpatialListener {
|
||||
///
|
||||
/// Default is `Vec3::ONE`.
|
||||
#[derive(Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub struct SpatialScale(pub Vec3);
|
||||
|
||||
impl SpatialScale {
|
||||
@ -204,7 +202,7 @@ impl Default for SpatialScale {
|
||||
///
|
||||
/// Default is `Vec3::ONE`.
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource, Default, Clone)]
|
||||
#[reflect(Resource, Default)]
|
||||
pub struct DefaultSpatialScale(pub SpatialScale);
|
||||
|
||||
/// A component for playing a sound.
|
||||
@ -220,7 +218,7 @@ pub struct DefaultSpatialScale(pub SpatialScale);
|
||||
/// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the
|
||||
/// `PlaybackSettings` component will *not* affect already-playing audio.
|
||||
#[derive(Component, Reflect)]
|
||||
#[reflect(Component, Clone)]
|
||||
#[reflect(Component)]
|
||||
#[require(PlaybackSettings)]
|
||||
pub struct AudioPlayer<Source = AudioSource>(pub Handle<Source>)
|
||||
where
|
||||
|
@ -6,7 +6,7 @@ use bevy_reflect::prelude::*;
|
||||
///
|
||||
/// Note: Changing [`GlobalVolume`] does not affect already playing audio.
|
||||
#[derive(Resource, Debug, Default, Clone, Copy, Reflect)]
|
||||
#[reflect(Resource, Debug, Default, Clone)]
|
||||
#[reflect(Resource, Debug, Default)]
|
||||
pub struct GlobalVolume {
|
||||
/// The global volume of all audio.
|
||||
pub volume: Volume,
|
||||
@ -32,7 +32,7 @@ impl GlobalVolume {
|
||||
///
|
||||
/// To create a new [`Volume`] from decibels, use [`Volume::Decibels`].
|
||||
#[derive(Clone, Copy, Debug, Reflect)]
|
||||
#[reflect(Clone, Debug, PartialEq)]
|
||||
#[reflect(Debug, PartialEq)]
|
||||
pub enum Volume {
|
||||
/// Create a new [`Volume`] from the given volume in linear scale.
|
||||
///
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_color"
|
||||
version = "0.16.2"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
description = "Types for representing and manipulating color values"
|
||||
homepage = "https://bevyengine.org"
|
||||
@ -10,10 +10,10 @@ keywords = ["bevy", "color"]
|
||||
rust-version = "1.85.0"
|
||||
|
||||
[dependencies]
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.1", default-features = false, features = [
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.0-dev", default-features = false, features = [
|
||||
"curve",
|
||||
] }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1", default-features = false, optional = true }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.0-dev", default-features = false, optional = true }
|
||||
bytemuck = { version = "1", features = ["derive"] }
|
||||
serde = { version = "1.0", features = [
|
||||
"derive",
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
color_difference::EuclideanDistance, Alpha, Hsla, Hsva, Hue, Hwba, Laba, Lcha, LinearRgba,
|
||||
Luminance, Mix, Oklaba, Oklcha, Saturation, Srgba, StandardColor, Xyza,
|
||||
Luminance, Mix, Oklaba, Oklcha, Srgba, StandardColor, Xyza,
|
||||
};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
use bevy_reflect::prelude::*;
|
||||
@ -42,11 +42,7 @@ use derive_more::derive::From;
|
||||
/// To avoid the cost of repeated conversion, and ensure consistent results where that is desired,
|
||||
/// first convert this [`Color`] into your desired color space.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, From)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
@ -814,44 +810,6 @@ impl Hue for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl Saturation for Color {
|
||||
fn with_saturation(&self, saturation: f32) -> Self {
|
||||
let mut new = *self;
|
||||
|
||||
match &mut new {
|
||||
Color::Srgba(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::LinearRgba(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Hsla(x) => x.with_saturation(saturation).into(),
|
||||
Color::Hsva(x) => x.with_saturation(saturation).into(),
|
||||
Color::Hwba(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Laba(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Lcha(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Oklaba(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Oklcha(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
Color::Xyza(x) => Hsla::from(*x).with_saturation(saturation).into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn saturation(&self) -> f32 {
|
||||
match self {
|
||||
Color::Srgba(x) => Hsla::from(*x).saturation(),
|
||||
Color::LinearRgba(x) => Hsla::from(*x).saturation(),
|
||||
Color::Hsla(x) => x.saturation(),
|
||||
Color::Hsva(x) => x.saturation(),
|
||||
Color::Hwba(x) => Hsla::from(*x).saturation(),
|
||||
Color::Laba(x) => Hsla::from(*x).saturation(),
|
||||
Color::Lcha(x) => Hsla::from(*x).saturation(),
|
||||
Color::Oklaba(x) => Hsla::from(*x).saturation(),
|
||||
Color::Oklcha(x) => Hsla::from(*x).saturation(),
|
||||
Color::Xyza(x) => Hsla::from(*x).saturation(),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_saturation(&mut self, saturation: f32) {
|
||||
*self = self.with_saturation(saturation);
|
||||
}
|
||||
}
|
||||
|
||||
impl Mix for Color {
|
||||
fn mix(&self, other: &Self, factor: f32) -> Self {
|
||||
let mut new = *self;
|
||||
|
@ -95,21 +95,6 @@ pub trait Hue: Sized {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for manipulating the saturation of a color.
|
||||
///
|
||||
/// When working with color spaces that do not have native saturation components
|
||||
/// the operations are performed in [`crate::Hsla`].
|
||||
pub trait Saturation: Sized {
|
||||
/// Return a new version of this color with the saturation channel set to the given value.
|
||||
fn with_saturation(&self, saturation: f32) -> Self;
|
||||
|
||||
/// Return the saturation of this color [0.0, 1.0].
|
||||
fn saturation(&self) -> f32;
|
||||
|
||||
/// Sets the saturation of this color.
|
||||
fn set_saturation(&mut self, saturation: f32);
|
||||
}
|
||||
|
||||
/// Trait with methods for converting colors to non-color types
|
||||
pub trait ColorToComponents {
|
||||
/// Convert to an f32 array
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
Alpha, ColorToComponents, Gray, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Saturation,
|
||||
Srgba, StandardColor, Xyza,
|
||||
Alpha, ColorToComponents, Gray, Hsva, Hue, Hwba, Lcha, LinearRgba, Luminance, Mix, Srgba,
|
||||
StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
@ -13,11 +13,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
@ -163,26 +159,6 @@ impl Hue for Hsla {
|
||||
}
|
||||
}
|
||||
|
||||
impl Saturation for Hsla {
|
||||
#[inline]
|
||||
fn with_saturation(&self, saturation: f32) -> Self {
|
||||
Self {
|
||||
saturation,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturation(&self) -> f32 {
|
||||
self.saturation
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_saturation(&mut self, saturation: f32) {
|
||||
self.saturation = saturation;
|
||||
}
|
||||
}
|
||||
|
||||
impl Luminance for Hsla {
|
||||
#[inline]
|
||||
fn with_luminance(&self, lightness: f32) -> Self {
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::{
|
||||
Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Saturation, Srgba,
|
||||
StandardColor, Xyza,
|
||||
Alpha, ColorToComponents, Gray, Hue, Hwba, Lcha, LinearRgba, Mix, Srgba, StandardColor, Xyza,
|
||||
};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
#[cfg(feature = "bevy_reflect")]
|
||||
@ -13,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
@ -134,26 +129,6 @@ impl Hue for Hsva {
|
||||
}
|
||||
}
|
||||
|
||||
impl Saturation for Hsva {
|
||||
#[inline]
|
||||
fn with_saturation(&self, saturation: f32) -> Self {
|
||||
Self {
|
||||
saturation,
|
||||
..*self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn saturation(&self) -> f32 {
|
||||
self.saturation
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn set_saturation(&mut self, saturation: f32) {
|
||||
self.saturation = saturation;
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hsva> for Hwba {
|
||||
fn from(
|
||||
Hsva {
|
||||
|
@ -16,11 +16,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -12,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -12,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -13,11 +13,7 @@ use bytemuck::{Pod, Zeroable};
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Pod, Zeroable)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -12,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -12,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -15,11 +15,7 @@ use thiserror::Error;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -12,11 +12,7 @@ use bevy_reflect::prelude::*;
|
||||
#[doc = include_str!("../docs/diagrams/model_graph.svg")]
|
||||
/// </div>
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(
|
||||
feature = "bevy_reflect",
|
||||
derive(Reflect),
|
||||
reflect(Clone, PartialEq, Default)
|
||||
)]
|
||||
#[cfg_attr(feature = "bevy_reflect", derive(Reflect), reflect(PartialEq, Default))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
all(feature = "serialize", feature = "bevy_reflect"),
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bevy_core_pipeline"
|
||||
version = "0.16.1"
|
||||
version = "0.16.0-dev"
|
||||
edition = "2024"
|
||||
authors = [
|
||||
"Bevy Contributors <bevyengine@gmail.com>",
|
||||
@ -22,20 +22,20 @@ smaa_luts = ["bevy_render/ktx2", "bevy_image/ktx2", "bevy_image/zstd"]
|
||||
|
||||
[dependencies]
|
||||
# bevy
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.1" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.1" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.2" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.1" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.1" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.1" }
|
||||
bevy_image = { path = "../bevy_image", version = "0.16.1" }
|
||||
bevy_reflect = { path = "../bevy_reflect", version = "0.16.1" }
|
||||
bevy_render = { path = "../bevy_render", version = "0.16.1" }
|
||||
bevy_transform = { path = "../bevy_transform", version = "0.16.1" }
|
||||
bevy_math = { path = "../bevy_math", version = "0.16.1" }
|
||||
bevy_utils = { path = "../bevy_utils", version = "0.16.1" }
|
||||
bevy_window = { path = "../bevy_window", version = "0.16.1" }
|
||||
bevy_platform = { path = "../bevy_platform", version = "0.16.1", default-features = false, features = [
|
||||
bevy_app = { path = "../bevy_app", version = "0.16.0-dev" }
|
||||
bevy_asset = { path = "../bevy_asset", version = "0.16.0-dev" }
|
||||
bevy_color = { path = "../bevy_color", version = "0.16.0-dev" }
|
||||
bevy_derive = { path = "../bevy_derive", version = "0.16.0-dev" }
|
||||
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.16.0-dev" }
|
||||
bevy_ecs = { path = "../bevy_ecs", version = "0.16.0-dev" }
|
||||
bevy_image = { path = "../bevy_image", 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_transform = { path = "../bevy_transform", 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_window = { path = "../bevy_window", version = "0.16.0-dev" }
|
||||
bevy_platform_support = { path = "../bevy_platform_support", version = "0.16.0-dev", default-features = false, features = [
|
||||
"std",
|
||||
"serialize",
|
||||
] }
|
||||
|
@ -1,5 +1,5 @@
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_platform::collections::{hash_map::Entry, HashMap};
|
||||
use bevy_platform_support::collections::{hash_map::Entry, HashMap};
|
||||
use bevy_render::{
|
||||
render_resource::{StorageBuffer, UniformBuffer},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
|
@ -18,7 +18,7 @@ const LUT_SIZE: usize = 256;
|
||||
/// This curve is used to map the average log luminance of a scene to an
|
||||
/// exposure compensation value, to allow for fine control over the final exposure.
|
||||
#[derive(Asset, Reflect, Debug, Clone)]
|
||||
#[reflect(Default, Clone)]
|
||||
#[reflect(Default)]
|
||||
pub struct AutoExposureCompensationCurve {
|
||||
/// The minimum log luminance value in the curve. (the x-axis)
|
||||
min_log_lum: f32,
|
||||
|
@ -24,7 +24,7 @@ use bevy_utils::default;
|
||||
///
|
||||
/// **Auto Exposure requires compute shaders and is not compatible with WebGL2.**
|
||||
#[derive(Component, Clone, Reflect, ExtractComponent)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct AutoExposure {
|
||||
/// The range of exposure values for the histogram.
|
||||
///
|
||||
|
@ -2,6 +2,7 @@ mod downsampling_pipeline;
|
||||
mod settings;
|
||||
mod upsampling_pipeline;
|
||||
|
||||
use bevy_color::{Gray, LinearRgba};
|
||||
pub use settings::{Bloom, BloomCompositeMode, BloomPrefilter};
|
||||
|
||||
use crate::{
|
||||
@ -10,7 +11,6 @@ use crate::{
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_color::{Gray, LinearRgba};
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_math::{ops, UVec2};
|
||||
use bevy_render::{
|
||||
@ -30,8 +30,6 @@ use downsampling_pipeline::{
|
||||
prepare_downsampling_pipeline, BloomDownsamplingPipeline, BloomDownsamplingPipelineIds,
|
||||
BloomUniforms,
|
||||
};
|
||||
#[cfg(feature = "trace")]
|
||||
use tracing::info_span;
|
||||
use upsampling_pipeline::{
|
||||
prepare_upsampling_pipeline, BloomUpsamplingPipeline, UpsamplingPipelineIds,
|
||||
};
|
||||
@ -110,10 +108,10 @@ impl ViewNode for BloomNode {
|
||||
// Atypically for a post-processing effect, we do not need to
|
||||
// use a secondary texture normally provided by view_target.post_process_write(),
|
||||
// instead we write into our own bloom texture and then directly back onto main.
|
||||
fn run<'w>(
|
||||
fn run(
|
||||
&self,
|
||||
_graph: &mut RenderGraphContext,
|
||||
render_context: &mut RenderContext<'w>,
|
||||
render_context: &mut RenderContext,
|
||||
(
|
||||
camera,
|
||||
view_target,
|
||||
@ -123,8 +121,8 @@ impl ViewNode for BloomNode {
|
||||
bloom_settings,
|
||||
upsampling_pipeline_ids,
|
||||
downsampling_pipeline_ids,
|
||||
): QueryItem<'w, Self::ViewQuery>,
|
||||
world: &'w World,
|
||||
): QueryItem<Self::ViewQuery>,
|
||||
world: &World,
|
||||
) -> Result<(), NodeRunError> {
|
||||
if bloom_settings.intensity == 0.0 {
|
||||
return Ok(());
|
||||
@ -151,152 +149,133 @@ impl ViewNode for BloomNode {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let view_texture = view_target.main_texture_view();
|
||||
let view_texture_unsampled = view_target.get_unsampled_color_attachment();
|
||||
render_context.command_encoder().push_debug_group("bloom");
|
||||
|
||||
let diagnostics = render_context.diagnostic_recorder();
|
||||
let command_encoder = render_context.command_encoder();
|
||||
let time_span = diagnostics.time_span(command_encoder, "bloom");
|
||||
|
||||
render_context.add_command_buffer_generation_task(move |render_device| {
|
||||
#[cfg(feature = "trace")]
|
||||
let _bloom_span = info_span!("bloom").entered();
|
||||
// First downsample pass
|
||||
{
|
||||
let downsampling_first_bind_group = render_context.render_device().create_bind_group(
|
||||
"bloom_downsampling_first_bind_group",
|
||||
&downsampling_pipeline_res.bind_group_layout,
|
||||
&BindGroupEntries::sequential((
|
||||
// Read from main texture directly
|
||||
view_target.main_texture_view(),
|
||||
&bind_groups.sampler,
|
||||
uniforms.clone(),
|
||||
)),
|
||||
);
|
||||
|
||||
let mut command_encoder =
|
||||
render_device.create_command_encoder(&CommandEncoderDescriptor {
|
||||
label: Some("bloom_command_encoder"),
|
||||
let view = &bloom_texture.view(0);
|
||||
let mut downsampling_first_pass =
|
||||
render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("bloom_downsampling_first_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
command_encoder.push_debug_group("bloom");
|
||||
let time_span = diagnostics.time_span(&mut command_encoder, "bloom");
|
||||
downsampling_first_pass.set_render_pipeline(downsampling_first_pipeline);
|
||||
downsampling_first_pass.set_bind_group(
|
||||
0,
|
||||
&downsampling_first_bind_group,
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
downsampling_first_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
// First downsample pass
|
||||
{
|
||||
let downsampling_first_bind_group = render_device.create_bind_group(
|
||||
"bloom_downsampling_first_bind_group",
|
||||
&downsampling_pipeline_res.bind_group_layout,
|
||||
&BindGroupEntries::sequential((
|
||||
// Read from main texture directly
|
||||
view_texture,
|
||||
&bind_groups.sampler,
|
||||
uniforms.clone(),
|
||||
)),
|
||||
);
|
||||
// Other downsample passes
|
||||
for mip in 1..bloom_texture.mip_count {
|
||||
let view = &bloom_texture.view(mip);
|
||||
let mut downsampling_pass =
|
||||
render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("bloom_downsampling_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
downsampling_pass.set_render_pipeline(downsampling_pipeline);
|
||||
downsampling_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.downsampling_bind_groups[mip as usize - 1],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
downsampling_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
let view = &bloom_texture.view(0);
|
||||
let mut downsampling_first_pass =
|
||||
command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("bloom_downsampling_first_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
downsampling_first_pass.set_pipeline(downsampling_first_pipeline);
|
||||
downsampling_first_pass.set_bind_group(
|
||||
0,
|
||||
&downsampling_first_bind_group,
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
downsampling_first_pass.draw(0..3, 0..1);
|
||||
// Upsample passes except the final one
|
||||
for mip in (1..bloom_texture.mip_count).rev() {
|
||||
let view = &bloom_texture.view(mip - 1);
|
||||
let mut upsampling_pass =
|
||||
render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("bloom_upsampling_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Load,
|
||||
store: StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
upsampling_pass.set_render_pipeline(upsampling_pipeline);
|
||||
upsampling_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - mip - 1) as usize],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
let blend = compute_blend_factor(
|
||||
bloom_settings,
|
||||
mip as f32,
|
||||
(bloom_texture.mip_count - 1) as f32,
|
||||
);
|
||||
upsampling_pass.set_blend_constant(LinearRgba::gray(blend));
|
||||
upsampling_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
// Final upsample pass
|
||||
// This is very similar to the above upsampling passes with the only difference
|
||||
// being the pipeline (which itself is barely different) and the color attachment
|
||||
{
|
||||
let mut upsampling_final_pass =
|
||||
render_context.begin_tracked_render_pass(RenderPassDescriptor {
|
||||
label: Some("bloom_upsampling_final_pass"),
|
||||
color_attachments: &[Some(view_target.get_unsampled_color_attachment())],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
upsampling_final_pass.set_render_pipeline(upsampling_final_pipeline);
|
||||
upsampling_final_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
upsampling_final_pass.set_camera_viewport(viewport);
|
||||
}
|
||||
let blend =
|
||||
compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32);
|
||||
upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend));
|
||||
upsampling_final_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
// Other downsample passes
|
||||
for mip in 1..bloom_texture.mip_count {
|
||||
let view = &bloom_texture.view(mip);
|
||||
let mut downsampling_pass =
|
||||
command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("bloom_downsampling_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations::default(),
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
downsampling_pass.set_pipeline(downsampling_pipeline);
|
||||
downsampling_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.downsampling_bind_groups[mip as usize - 1],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
downsampling_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
// Upsample passes except the final one
|
||||
for mip in (1..bloom_texture.mip_count).rev() {
|
||||
let view = &bloom_texture.view(mip - 1);
|
||||
let mut upsampling_pass =
|
||||
command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("bloom_upsampling_pass"),
|
||||
color_attachments: &[Some(RenderPassColorAttachment {
|
||||
view,
|
||||
resolve_target: None,
|
||||
ops: Operations {
|
||||
load: LoadOp::Load,
|
||||
store: StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
upsampling_pass.set_pipeline(upsampling_pipeline);
|
||||
upsampling_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.upsampling_bind_groups
|
||||
[(bloom_texture.mip_count - mip - 1) as usize],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
let blend = compute_blend_factor(
|
||||
bloom_settings,
|
||||
mip as f32,
|
||||
(bloom_texture.mip_count - 1) as f32,
|
||||
);
|
||||
upsampling_pass.set_blend_constant(LinearRgba::gray(blend).into());
|
||||
upsampling_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
// Final upsample pass
|
||||
// This is very similar to the above upsampling passes with the only difference
|
||||
// being the pipeline (which itself is barely different) and the color attachment
|
||||
{
|
||||
let mut upsampling_final_pass =
|
||||
command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("bloom_upsampling_final_pass"),
|
||||
color_attachments: &[Some(view_texture_unsampled)],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
upsampling_final_pass.set_pipeline(upsampling_final_pipeline);
|
||||
upsampling_final_pass.set_bind_group(
|
||||
0,
|
||||
&bind_groups.upsampling_bind_groups[(bloom_texture.mip_count - 1) as usize],
|
||||
&[uniform_index.index()],
|
||||
);
|
||||
if let Some(viewport) = camera.viewport.as_ref() {
|
||||
upsampling_final_pass.set_viewport(
|
||||
viewport.physical_position.x as f32,
|
||||
viewport.physical_position.y as f32,
|
||||
viewport.physical_size.x as f32,
|
||||
viewport.physical_size.y as f32,
|
||||
viewport.depth.start,
|
||||
viewport.depth.end,
|
||||
);
|
||||
}
|
||||
let blend =
|
||||
compute_blend_factor(bloom_settings, 0.0, (bloom_texture.mip_count - 1) as f32);
|
||||
upsampling_final_pass.set_blend_constant(LinearRgba::gray(blend).into());
|
||||
upsampling_final_pass.draw(0..3, 0..1);
|
||||
}
|
||||
|
||||
time_span.end(&mut command_encoder);
|
||||
command_encoder.pop_debug_group();
|
||||
command_encoder.finish()
|
||||
});
|
||||
time_span.end(render_context.command_encoder());
|
||||
render_context.command_encoder().pop_debug_group();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ use bevy_render::{extract_component::ExtractComponent, prelude::Camera};
|
||||
/// See <https://starlederer.github.io/bloom/> for a visualization of the parametric curve
|
||||
/// used in Bevy as well as a visualization of the curve's respective scattering profile.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Bloom {
|
||||
/// Controls the baseline of how much the image is scattered (default: 0.15).
|
||||
///
|
||||
@ -193,7 +193,6 @@ impl Default for Bloom {
|
||||
/// * Changing these settings makes it easy to make the final result look worse
|
||||
/// * Non-default prefilter settings should be used in conjunction with [`BloomCompositeMode::Additive`]
|
||||
#[derive(Default, Clone, Reflect)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub struct BloomPrefilter {
|
||||
/// Baseline of the quadratic threshold curve (default: 0.0).
|
||||
///
|
||||
@ -210,7 +209,6 @@ pub struct BloomPrefilter {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Reflect, PartialEq, Eq, Hash, Copy)]
|
||||
#[reflect(Clone, Hash, PartialEq)]
|
||||
pub enum BloomCompositeMode {
|
||||
EnergyConserving,
|
||||
Additive,
|
||||
|
@ -36,7 +36,7 @@ pub use node::CasNode;
|
||||
///
|
||||
/// To use this, add the [`ContrastAdaptiveSharpening`] component to a 2D or 3D camera.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct ContrastAdaptiveSharpening {
|
||||
/// Enable or disable sharpening.
|
||||
pub enabled: bool,
|
||||
@ -65,7 +65,7 @@ impl Default for ContrastAdaptiveSharpening {
|
||||
}
|
||||
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DenoiseCas(bool);
|
||||
|
||||
/// The uniform struct extracted from [`ContrastAdaptiveSharpening`] attached to a [`Camera`].
|
||||
|
@ -14,13 +14,13 @@ use bevy_transform::prelude::{GlobalTransform, Transform};
|
||||
/// A 2D camera component. Enables the 2D render graph for a [`Camera`].
|
||||
#[derive(Component, Default, Reflect, Clone, ExtractComponent)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
#[require(
|
||||
Camera,
|
||||
DebandDither,
|
||||
CameraRenderGraph::new(Core2d),
|
||||
Projection::Orthographic(OrthographicProjection::default_2d()),
|
||||
Frustum = OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default())),
|
||||
Tonemapping::None,
|
||||
CameraRenderGraph(|| CameraRenderGraph::new(Core2d)),
|
||||
Projection(|| Projection::Orthographic(OrthographicProjection::default_2d())),
|
||||
Frustum(|| OrthographicProjection::default_2d().compute_frustum(&GlobalTransform::from(Transform::default()))),
|
||||
Tonemapping(|| Tonemapping::None),
|
||||
)]
|
||||
pub struct Camera2d;
|
||||
|
@ -44,15 +44,6 @@ impl ViewNode for MainTransparentPass2dNode {
|
||||
|
||||
let diagnostics = render_context.diagnostic_recorder();
|
||||
|
||||
let color_attachments = [Some(target.get_color_attachment())];
|
||||
// NOTE: For the transparent pass we load the depth buffer. There should be no
|
||||
// need to write to it, but store is set to `true` as a workaround for issue #3776,
|
||||
// https://github.com/bevyengine/bevy/issues/3776
|
||||
// so that wgpu does not clear the depth buffer.
|
||||
// As the opaque and alpha mask passes run first, opaque meshes can occlude
|
||||
// transparent ones.
|
||||
let depth_stencil_attachment = Some(depth.get_attachment(StoreOp::Store));
|
||||
|
||||
render_context.add_command_buffer_generation_task(move |render_device| {
|
||||
// Command encoder setup
|
||||
let mut command_encoder =
|
||||
@ -67,8 +58,14 @@ impl ViewNode for MainTransparentPass2dNode {
|
||||
|
||||
let render_pass = command_encoder.begin_render_pass(&RenderPassDescriptor {
|
||||
label: Some("main_transparent_pass_2d"),
|
||||
color_attachments: &color_attachments,
|
||||
depth_stencil_attachment,
|
||||
color_attachments: &[Some(target.get_color_attachment())],
|
||||
// NOTE: For the transparent pass we load the depth buffer. There should be no
|
||||
// need to write to it, but store is set to `true` as a workaround for issue #3776,
|
||||
// https://github.com/bevyengine/bevy/issues/3776
|
||||
// so that wgpu does not clear the depth buffer.
|
||||
// As the opaque and alpha mask passes run first, opaque meshes can occlude
|
||||
// transparent ones.
|
||||
depth_stencil_attachment: Some(depth.get_attachment(StoreOp::Store)),
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
|
@ -19,7 +19,6 @@ pub mod graph {
|
||||
MainOpaquePass,
|
||||
MainTransparentPass,
|
||||
EndMainPass,
|
||||
Wireframe,
|
||||
Bloom,
|
||||
PostProcessing,
|
||||
Tonemapping,
|
||||
@ -34,7 +33,7 @@ pub mod graph {
|
||||
use core::ops::Range;
|
||||
|
||||
use bevy_asset::UntypedAssetId;
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_render::{
|
||||
batching::gpu_preprocessing::GpuPreprocessingMode,
|
||||
render_phase::PhaseItemBatchSetKey,
|
||||
@ -350,7 +349,6 @@ pub struct Transparent2d {
|
||||
pub pipeline: CachedRenderPipelineId,
|
||||
pub draw_function: DrawFunctionId,
|
||||
pub batch_range: Range<u32>,
|
||||
pub extracted_index: usize,
|
||||
pub extra_index: PhaseItemExtraIndex,
|
||||
/// Whether the mesh in question is indexed (uses an index buffer in
|
||||
/// addition to its vertex buffer).
|
||||
|
@ -18,11 +18,11 @@ use serde::{Deserialize, Serialize};
|
||||
/// This means "forward" is -Z.
|
||||
#[derive(Component, Reflect, Clone, ExtractComponent)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
#[require(
|
||||
Camera,
|
||||
DebandDither::Enabled,
|
||||
CameraRenderGraph::new(Core3d),
|
||||
DebandDither(|| DebandDither::Enabled),
|
||||
CameraRenderGraph(|| CameraRenderGraph::new(Core3d)),
|
||||
Projection,
|
||||
Tonemapping,
|
||||
ColorGrading,
|
||||
@ -72,7 +72,7 @@ impl Default for Camera3d {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Reflect, Serialize, Deserialize)]
|
||||
#[reflect(Serialize, Deserialize, Clone)]
|
||||
#[reflect(Serialize, Deserialize)]
|
||||
pub struct Camera3dDepthTextureUsage(pub u32);
|
||||
|
||||
impl From<TextureUsages> for Camera3dDepthTextureUsage {
|
||||
@ -88,7 +88,7 @@ impl From<Camera3dDepthTextureUsage> for TextureUsages {
|
||||
|
||||
/// The depth clear operation to perform for the main 3d pass.
|
||||
#[derive(Reflect, Serialize, Deserialize, Clone, Debug)]
|
||||
#[reflect(Serialize, Deserialize, Clone, Default)]
|
||||
#[reflect(Serialize, Deserialize)]
|
||||
pub enum Camera3dDepthLoadOp {
|
||||
/// Clear with a specified value.
|
||||
/// Note that 0.0 is the far plane due to bevy's use of reverse-z projections.
|
||||
@ -119,7 +119,7 @@ impl From<Camera3dDepthLoadOp> for LoadOp<f32> {
|
||||
///
|
||||
/// **Note:** You can get better-looking results at any quality level by enabling TAA. See: [`TemporalAntiAliasPlugin`](crate::experimental::taa::TemporalAntiAliasPlugin).
|
||||
#[derive(Resource, Default, Clone, Copy, Reflect, PartialEq, PartialOrd, Debug)]
|
||||
#[reflect(Resource, Default, Clone, Debug, PartialEq)]
|
||||
#[reflect(Resource, Default, Debug, PartialEq)]
|
||||
pub enum ScreenSpaceTransmissionQuality {
|
||||
/// Best performance at the cost of quality. Suitable for lower end GPUs. (e.g. Mobile)
|
||||
///
|
||||
|
@ -28,7 +28,6 @@ pub mod graph {
|
||||
MainTransmissivePass,
|
||||
MainTransparentPass,
|
||||
EndMainPass,
|
||||
Wireframe,
|
||||
LateDownsampleDepth,
|
||||
Taa,
|
||||
MotionBlur,
|
||||
@ -87,7 +86,7 @@ use bevy_color::LinearRgba;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_image::BevyDefault;
|
||||
use bevy_math::FloatOrd;
|
||||
use bevy_platform::collections::{HashMap, HashSet};
|
||||
use bevy_platform_support::collections::{HashMap, HashSet};
|
||||
use bevy_render::{
|
||||
camera::{Camera, ExtractedCamera},
|
||||
extract_component::ExtractComponentPlugin,
|
||||
|
@ -23,7 +23,7 @@ use bevy_ecs::{
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
schedule::IntoSystemConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
@ -79,7 +79,7 @@ pub struct DepthOfFieldPlugin;
|
||||
///
|
||||
/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field
|
||||
#[derive(Component, Clone, Copy, Reflect)]
|
||||
#[reflect(Component, Clone, Default)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DepthOfField {
|
||||
/// The appearance of the effect.
|
||||
pub mode: DepthOfFieldMode,
|
||||
@ -123,7 +123,7 @@ pub struct DepthOfField {
|
||||
|
||||
/// Controls the appearance of the effect.
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
|
||||
#[reflect(Default, Clone, PartialEq)]
|
||||
#[reflect(Default, PartialEq)]
|
||||
pub enum DepthOfFieldMode {
|
||||
/// A more accurate simulation, in which circles of confusion generate
|
||||
/// "spots" of light.
|
||||
|
@ -7,10 +7,6 @@
|
||||
|
||||
use core::array;
|
||||
|
||||
use crate::core_3d::{
|
||||
graph::{Core3d, Node3d},
|
||||
prepare_core_3d_depth_textures,
|
||||
};
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_derive::{Deref, DerefMut};
|
||||
@ -20,12 +16,11 @@ use bevy_ecs::{
|
||||
prelude::{resource_exists, Without},
|
||||
query::{Or, QueryState, With},
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
schedule::IntoSystemConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Local, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
use bevy_math::{uvec2, UVec2, Vec4Swizzles as _};
|
||||
use bevy_render::batching::gpu_preprocessing::GpuPreprocessingSupport;
|
||||
use bevy_render::{
|
||||
experimental::occlusion_culling::{
|
||||
OcclusionCulling, OcclusionCullingSubview, OcclusionCullingSubviewEntities,
|
||||
@ -35,19 +30,23 @@ use bevy_render::{
|
||||
binding_types::{sampler, texture_2d, texture_2d_multisampled, texture_storage_2d},
|
||||
BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
|
||||
CachedComputePipelineId, ComputePassDescriptor, ComputePipeline, ComputePipelineDescriptor,
|
||||
Extent3d, IntoBinding, PipelineCache, PushConstantRange, Sampler, SamplerBindingType,
|
||||
SamplerDescriptor, Shader, ShaderStages, SpecializedComputePipeline,
|
||||
DownlevelFlags, Extent3d, IntoBinding, PipelineCache, PushConstantRange, Sampler,
|
||||
SamplerBindingType, SamplerDescriptor, Shader, ShaderStages, SpecializedComputePipeline,
|
||||
SpecializedComputePipelines, StorageTextureAccess, TextureAspect, TextureDescriptor,
|
||||
TextureDimension, TextureFormat, TextureSampleType, TextureUsages, TextureView,
|
||||
TextureViewDescriptor, TextureViewDimension,
|
||||
},
|
||||
renderer::{RenderContext, RenderDevice},
|
||||
renderer::{RenderAdapter, RenderContext, RenderDevice},
|
||||
texture::TextureCache,
|
||||
view::{ExtractedView, NoIndirectDrawing, ViewDepthTexture},
|
||||
Render, RenderApp, RenderSet,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use tracing::debug;
|
||||
|
||||
use crate::core_3d::{
|
||||
graph::{Core3d, Node3d},
|
||||
prepare_core_3d_depth_textures,
|
||||
};
|
||||
|
||||
/// Identifies the `downsample_depth.wgsl` shader.
|
||||
pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle<Shader> =
|
||||
@ -326,14 +325,26 @@ pub struct DownsampleDepthPipelines {
|
||||
sampler: Sampler,
|
||||
}
|
||||
|
||||
fn supports_compute_shaders(device: &RenderDevice, adapter: &RenderAdapter) -> bool {
|
||||
adapter
|
||||
.get_downlevel_capabilities()
|
||||
.flags
|
||||
.contains(DownlevelFlags::COMPUTE_SHADERS)
|
||||
// Even if the adapter supports compute, we might be simulating a lack of
|
||||
// compute via device limits (see `WgpuSettingsPriority::WebGL2` and
|
||||
// `wgpu::Limits::downlevel_webgl2_defaults()`). This will have set all the
|
||||
// `max_compute_*` limits to zero, so we arbitrarily pick one as a canary.
|
||||
&& (device.limits().max_compute_workgroup_storage_size != 0)
|
||||
}
|
||||
|
||||
/// Creates the [`DownsampleDepthPipelines`] if downsampling is supported on the
|
||||
/// current platform.
|
||||
fn create_downsample_depth_pipelines(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_adapter: Res<RenderAdapter>,
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut specialized_compute_pipelines: ResMut<SpecializedComputePipelines<DownsampleDepthPipeline>>,
|
||||
gpu_preprocessing_support: Res<GpuPreprocessingSupport>,
|
||||
mut has_run: Local<bool>,
|
||||
) {
|
||||
// Only run once.
|
||||
@ -345,8 +356,9 @@ fn create_downsample_depth_pipelines(
|
||||
}
|
||||
*has_run = true;
|
||||
|
||||
if !gpu_preprocessing_support.is_culling_supported() {
|
||||
debug!("Downsample depth is not supported on this platform.");
|
||||
// If we don't have compute shaders, we can't invoke the downsample depth
|
||||
// compute shader.
|
||||
if !supports_compute_shaders(&render_device, &render_adapter) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ mod node;
|
||||
pub use node::FxaaNode;
|
||||
|
||||
#[derive(Debug, Reflect, Eq, PartialEq, Hash, Clone, Copy)]
|
||||
#[reflect(PartialEq, Hash, Clone)]
|
||||
#[reflect(PartialEq, Hash)]
|
||||
pub enum Sensitivity {
|
||||
Low,
|
||||
Medium,
|
||||
@ -51,7 +51,7 @@ impl Sensitivity {
|
||||
/// A component for enabling Fast Approximate Anti-Aliasing (FXAA)
|
||||
/// for a [`bevy_render::camera::Camera`].
|
||||
#[derive(Reflect, Component, Clone, ExtractComponent)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[doc(alias = "FastApproximateAntiAliasing")]
|
||||
pub struct Fxaa {
|
||||
|
@ -9,10 +9,7 @@ use crate::{
|
||||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_ecs::{
|
||||
component::Component,
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
schedule::IntoScheduleConfigs,
|
||||
component::Component, query::With, reflect::ReflectComponent, schedule::IntoSystemConfigs,
|
||||
};
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_render::{
|
||||
@ -56,8 +53,9 @@ pub mod pipeline;
|
||||
/// ));
|
||||
/// # }
|
||||
/// ````
|
||||
#[derive(Reflect, Component, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[derive(Reflect, Component, Clone, ExtractComponent, ShaderType)]
|
||||
#[reflect(Component, Default)]
|
||||
#[extract_component_filter(With<Camera>)]
|
||||
#[require(DepthPrepass, MotionVectorPrepass)]
|
||||
pub struct MotionBlur {
|
||||
/// The strength of motion blur from `0.0` to `1.0`.
|
||||
@ -90,6 +88,9 @@ pub struct MotionBlur {
|
||||
/// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is
|
||||
/// equivalent to disabling motion blur.
|
||||
pub samples: u32,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
// WebGL2 structs must be 16 byte aligned.
|
||||
pub _webgl2_padding: bevy_math::Vec2,
|
||||
}
|
||||
|
||||
impl Default for MotionBlur {
|
||||
@ -97,35 +98,12 @@ impl Default for MotionBlur {
|
||||
Self {
|
||||
shutter_angle: 0.5,
|
||||
samples: 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractComponent for MotionBlur {
|
||||
type QueryData = &'static Self;
|
||||
type QueryFilter = With<Camera>;
|
||||
type Out = MotionBlurUniform;
|
||||
|
||||
fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
|
||||
Some(MotionBlurUniform {
|
||||
shutter_angle: item.shutter_angle,
|
||||
samples: item.samples,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
_webgl2_padding: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[derive(Component, ShaderType, Clone)]
|
||||
pub struct MotionBlurUniform {
|
||||
shutter_angle: f32,
|
||||
samples: u32,
|
||||
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
|
||||
// WebGL2 structs must be 16 byte aligned.
|
||||
_webgl2_padding: bevy_math::Vec2,
|
||||
}
|
||||
|
||||
pub const MOTION_BLUR_SHADER_HANDLE: Handle<Shader> =
|
||||
weak_handle!("d9ca74af-fa0a-4f11-b0f2-19613b618b93");
|
||||
|
||||
@ -141,7 +119,7 @@ impl Plugin for MotionBlurPlugin {
|
||||
);
|
||||
app.add_plugins((
|
||||
ExtractComponentPlugin::<MotionBlur>::default(),
|
||||
UniformComponentPlugin::<MotionBlurUniform>::default(),
|
||||
UniformComponentPlugin::<MotionBlur>::default(),
|
||||
));
|
||||
|
||||
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
|
||||
|
@ -15,7 +15,7 @@ use crate::prepass::ViewPrepassTextures;
|
||||
|
||||
use super::{
|
||||
pipeline::{MotionBlurPipeline, MotionBlurPipelineId},
|
||||
MotionBlurUniform,
|
||||
MotionBlur,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
@ -26,7 +26,7 @@ impl ViewNode for MotionBlurNode {
|
||||
&'static ViewTarget,
|
||||
&'static MotionBlurPipelineId,
|
||||
&'static ViewPrepassTextures,
|
||||
&'static MotionBlurUniform,
|
||||
&'static MotionBlur,
|
||||
&'static Msaa,
|
||||
);
|
||||
fn run(
|
||||
@ -42,7 +42,7 @@ impl ViewNode for MotionBlurNode {
|
||||
|
||||
let motion_blur_pipeline = world.resource::<MotionBlurPipeline>();
|
||||
let pipeline_cache = world.resource::<PipelineCache>();
|
||||
let settings_uniforms = world.resource::<ComponentUniforms<MotionBlurUniform>>();
|
||||
let settings_uniforms = world.resource::<ComponentUniforms<MotionBlur>>();
|
||||
let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
|
||||
return Ok(());
|
||||
};
|
||||
|
@ -26,7 +26,7 @@ use bevy_render::{
|
||||
|
||||
use crate::fullscreen_vertex_shader::fullscreen_shader_vertex_state;
|
||||
|
||||
use super::{MotionBlurUniform, MOTION_BLUR_SHADER_HANDLE};
|
||||
use super::{MotionBlur, MOTION_BLUR_SHADER_HANDLE};
|
||||
|
||||
#[derive(Resource)]
|
||||
pub struct MotionBlurPipeline {
|
||||
@ -49,7 +49,7 @@ impl MotionBlurPipeline {
|
||||
// Linear Sampler
|
||||
sampler(SamplerBindingType::Filtering),
|
||||
// Motion blur settings uniform input
|
||||
uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
|
||||
uniform_buffer_sized(false, Some(MotionBlur::min_size())),
|
||||
// Globals uniform input
|
||||
uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
|
||||
),
|
||||
@ -67,7 +67,7 @@ impl MotionBlurPipeline {
|
||||
// Linear Sampler
|
||||
sampler(SamplerBindingType::Filtering),
|
||||
// Motion blur settings uniform input
|
||||
uniform_buffer_sized(false, Some(MotionBlurUniform::min_size())),
|
||||
uniform_buffer_sized(false, Some(MotionBlur::min_size())),
|
||||
// Globals uniform input
|
||||
uniform_buffer_sized(false, Some(GlobalsUniform::min_size())),
|
||||
),
|
||||
@ -155,7 +155,7 @@ pub(crate) fn prepare_motion_blur_pipelines(
|
||||
pipeline_cache: Res<PipelineCache>,
|
||||
mut pipelines: ResMut<SpecializedRenderPipelines<MotionBlurPipeline>>,
|
||||
pipeline: Res<MotionBlurPipeline>,
|
||||
views: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlurUniform>>,
|
||||
views: Query<(Entity, &ExtractedView, &Msaa), With<MotionBlur>>,
|
||||
) {
|
||||
for (entity, view, msaa) in &views {
|
||||
let pipeline_id = pipelines.specialize(
|
||||
|
@ -4,9 +4,9 @@ use bevy_app::prelude::*;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_ecs::{component::*, prelude::*};
|
||||
use bevy_math::UVec2;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_platform::time::Instant;
|
||||
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_platform_support::time::Instant;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{
|
||||
camera::{Camera, ExtractedCamera},
|
||||
extract_component::{ExtractComponent, ExtractComponentPlugin},
|
||||
@ -44,7 +44,6 @@ pub const OIT_DRAW_SHADER_HANDLE: Handle<Shader> =
|
||||
// This should probably be done by adding an enum to this component.
|
||||
// We use the same struct to pass on the settings to the drawing shader.
|
||||
#[derive(Clone, Copy, ExtractComponent, Reflect, ShaderType)]
|
||||
#[reflect(Clone, Default)]
|
||||
pub struct OrderIndependentTransparencySettings {
|
||||
/// Controls how many layers will be used to compute the blending.
|
||||
/// The more layers you use the more memory it will use but it will also give better results.
|
||||
|
@ -6,7 +6,7 @@ use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, weak_handle, Handle};
|
||||
use bevy_derive::Deref;
|
||||
use bevy_ecs::{
|
||||
entity::{EntityHashMap, EntityHashSet},
|
||||
entity::{hash_map::EntityHashMap, hash_set::EntityHashSet},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_image::BevyDefault as _;
|
||||
|
@ -11,7 +11,7 @@ use bevy_ecs::{
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
schedule::IntoSystemConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
@ -98,7 +98,7 @@ pub struct PostProcessingPlugin;
|
||||
///
|
||||
/// [Gjøl & Svendsen 2016]: https://github.com/playdeadgames/publications/blob/master/INSIDE/rendering_inside_gdc2016.pdf
|
||||
#[derive(Reflect, Component, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct ChromaticAberration {
|
||||
/// The lookup texture that determines the color gradient.
|
||||
///
|
||||
|
@ -54,18 +54,18 @@ pub const MOTION_VECTOR_PREPASS_FORMAT: TextureFormat = TextureFormat::Rg16Float
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then depth values will be copied to a separate texture available to the main pass.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct DepthPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then vertex world normals will be copied to a separate texture available to the main pass.
|
||||
/// Normals will have normal map textures already applied.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct NormalPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then screen space motion vectors will be copied to a separate texture available to the main pass.
|
||||
#[derive(Component, Default, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct MotionVectorPrepass;
|
||||
|
||||
/// If added to a [`crate::prelude::Camera3d`] then deferred materials will be rendered to the deferred gbuffer texture and will be available to subsequent passes.
|
||||
|
@ -5,7 +5,7 @@ use bevy_ecs::{
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
};
|
||||
use bevy_image::{BevyDefault, Image};
|
||||
@ -90,7 +90,7 @@ impl Plugin for SkyboxPlugin {
|
||||
///
|
||||
/// See also <https://en.wikipedia.org/wiki/Skybox_(video_games)>.
|
||||
#[derive(Component, Clone, Reflect)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
pub struct Skybox {
|
||||
pub image: Handle<Image>,
|
||||
/// Scale factor applied to the skybox image.
|
||||
|
@ -46,7 +46,7 @@ use bevy_ecs::{
|
||||
query::{QueryItem, With},
|
||||
reflect::ReflectComponent,
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs as _,
|
||||
schedule::IntoSystemConfigs as _,
|
||||
system::{lifetimeless::Read, Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
@ -95,7 +95,7 @@ pub struct SmaaPlugin;
|
||||
/// A component for enabling Subpixel Morphological Anti-Aliasing (SMAA)
|
||||
/// for a [`bevy_render::camera::Camera`].
|
||||
#[derive(Clone, Copy, Default, Component, Reflect, ExtractComponent)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
#[doc(alias = "SubpixelMorphologicalAntiAliasing")]
|
||||
pub struct Smaa {
|
||||
/// A predefined set of SMAA parameters: i.e. a quality level.
|
||||
@ -110,7 +110,7 @@ pub struct Smaa {
|
||||
///
|
||||
/// The default value is *high*.
|
||||
#[derive(Clone, Copy, Reflect, Default, PartialEq, Eq, Hash)]
|
||||
#[reflect(Default, Clone, PartialEq, Hash)]
|
||||
#[reflect(Default)]
|
||||
pub enum SmaaPreset {
|
||||
/// Four search steps; no diagonal or corner detection.
|
||||
Low,
|
||||
|
@ -11,7 +11,7 @@ use bevy_ecs::{
|
||||
prelude::{Component, Entity, ReflectComponent},
|
||||
query::{QueryItem, With},
|
||||
resource::Resource,
|
||||
schedule::IntoScheduleConfigs,
|
||||
schedule::IntoSystemConfigs,
|
||||
system::{Commands, Query, Res, ResMut},
|
||||
world::{FromWorld, World},
|
||||
};
|
||||
@ -131,7 +131,7 @@ impl Plugin for TemporalAntiAliasPlugin {
|
||||
///
|
||||
/// If no [`MipBias`] component is attached to the camera, TAA will add a `MipBias(-1.0)` component.
|
||||
#[derive(Component, Reflect, Clone)]
|
||||
#[reflect(Component, Default, Clone)]
|
||||
#[reflect(Component, Default)]
|
||||
#[require(TemporalJitter, DepthPrepass, MotionVectorPrepass)]
|
||||
#[doc(alias = "Taa")]
|
||||
pub struct TemporalAntiAliasing {
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::blit::{BlitPipeline, BlitPipelineKey};
|
||||
use bevy_app::prelude::*;
|
||||
use bevy_ecs::prelude::*;
|
||||
use bevy_platform::collections::HashSet;
|
||||
use bevy_platform_support::collections::HashSet;
|
||||
use bevy_render::{
|
||||
camera::{CameraOutputMode, ExtractedCamera},
|
||||
render_resource::*,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user