Merge commit '5e3927ba489f597dd189f63286dc7985840db1b5' into dlss3

This commit is contained in:
JMS55 2025-07-07 21:40:01 -04:00
commit 1b714636ab
139 changed files with 2295 additions and 1126 deletions

View File

@ -294,7 +294,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Check for typos - name: Check for typos
uses: crate-ci/typos@v1.33.1 uses: crate-ci/typos@v1.34.0
- name: Typos info - name: Typos info
if: failure() if: failure()
run: | run: |

View File

@ -871,6 +871,7 @@ doc-scrape-examples = true
name = "Texture Atlas" name = "Texture Atlas"
description = "Generates a texture atlas (sprite sheet) from individual sprites" description = "Generates a texture atlas (sprite sheet) from individual sprites"
category = "2D Rendering" category = "2D Rendering"
# Loading asset folders is not supported in Wasm, but required to create the atlas.
wasm = false wasm = false
[[example]] [[example]]
@ -948,6 +949,7 @@ doc-scrape-examples = true
name = "2D Wireframe" name = "2D Wireframe"
description = "Showcases wireframes for 2d meshes" description = "Showcases wireframes for 2d meshes"
category = "2D Rendering" category = "2D Rendering"
# PolygonMode::Line wireframes are not supported by WebGL
wasm = false wasm = false
# 3D Rendering # 3D Rendering
@ -1015,6 +1017,7 @@ doc-scrape-examples = true
name = "Anti-aliasing" name = "Anti-aliasing"
description = "Compares different anti-aliasing techniques supported by Bevy" description = "Compares different anti-aliasing techniques supported by Bevy"
category = "3D Rendering" category = "3D Rendering"
# TAA not supported by WebGL
wasm = false wasm = false
[[example]] [[example]]
@ -1059,6 +1062,7 @@ doc-scrape-examples = true
name = "Auto Exposure" name = "Auto Exposure"
description = "A scene showcasing auto exposure" description = "A scene showcasing auto exposure"
category = "3D Rendering" category = "3D Rendering"
# Requires compute shaders, which are not supported by WebGL.
wasm = false wasm = false
[[example]] [[example]]
@ -1126,6 +1130,7 @@ doc-scrape-examples = true
name = "Screen Space Ambient Occlusion" name = "Screen Space Ambient Occlusion"
description = "A scene showcasing screen space ambient occlusion" description = "A scene showcasing screen space ambient occlusion"
category = "3D Rendering" category = "3D Rendering"
# Requires compute shaders, which are not supported by WebGL.
wasm = false wasm = false
[[example]] [[example]]
@ -1225,6 +1230,7 @@ doc-scrape-examples = true
name = "Order Independent Transparency" name = "Order Independent Transparency"
description = "Demonstrates how to use OIT" description = "Demonstrates how to use OIT"
category = "3D Rendering" category = "3D Rendering"
# Not supported by WebGL
wasm = false wasm = false
[[example]] [[example]]
@ -1324,7 +1330,7 @@ doc-scrape-examples = true
name = "Skybox" name = "Skybox"
description = "Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats." description = "Load a cubemap texture onto a cube like a skybox and cycle through different compressed texture formats."
category = "3D Rendering" category = "3D Rendering"
wasm = false wasm = true
[[example]] [[example]]
name = "solari" name = "solari"
@ -1435,6 +1441,7 @@ doc-scrape-examples = true
name = "Wireframe" name = "Wireframe"
description = "Showcases wireframe rendering" description = "Showcases wireframe rendering"
category = "3D Rendering" category = "3D Rendering"
# Not supported on WebGL
wasm = false wasm = false
[[example]] [[example]]
@ -1446,6 +1453,8 @@ doc-scrape-examples = true
name = "Irradiance Volumes" name = "Irradiance Volumes"
description = "Demonstrates irradiance volumes" description = "Demonstrates irradiance volumes"
category = "3D Rendering" category = "3D Rendering"
# On WebGL and WebGPU, the number of texture bindings is too low
# See <https://github.com/bevyengine/bevy/issues/11885>
wasm = false wasm = false
[[example]] [[example]]
@ -1458,6 +1467,7 @@ required-features = ["meshlet"]
name = "Meshlet" name = "Meshlet"
description = "Meshlet rendering for dense high-poly scenes (experimental)" description = "Meshlet rendering for dense high-poly scenes (experimental)"
category = "3D Rendering" category = "3D Rendering"
# Requires compute shaders and WGPU extensions, not supported by WebGL nor WebGPU.
wasm = false wasm = false
setup = [ setup = [
[ [
@ -1493,7 +1503,7 @@ doc-scrape-examples = true
name = "Lightmaps" name = "Lightmaps"
description = "Rendering a scene with baked lightmaps" description = "Rendering a scene with baked lightmaps"
category = "3D Rendering" category = "3D Rendering"
wasm = false wasm = true
[[example]] [[example]]
name = "no_prepass" name = "no_prepass"
@ -1646,6 +1656,7 @@ doc-scrape-examples = true
name = "Custom Loop" name = "Custom Loop"
description = "Demonstrates how to create a custom runner (to update an app manually)" description = "Demonstrates how to create a custom runner (to update an app manually)"
category = "Application" category = "Application"
# Doesn't render anything, doesn't create a canvas
wasm = false wasm = false
[[example]] [[example]]
@ -1657,6 +1668,7 @@ doc-scrape-examples = true
name = "Drag and Drop" name = "Drag and Drop"
description = "An example that shows how to handle drag and drop in an app" description = "An example that shows how to handle drag and drop in an app"
category = "Application" category = "Application"
# Browser drag and drop is not supported
wasm = false wasm = false
[[example]] [[example]]
@ -1668,6 +1680,7 @@ doc-scrape-examples = true
name = "Empty" name = "Empty"
description = "An empty application (does nothing)" description = "An empty application (does nothing)"
category = "Application" category = "Application"
# Doesn't render anything, doesn't create a canvas
wasm = false wasm = false
[[example]] [[example]]
@ -1691,6 +1704,7 @@ required-features = ["bevy_log"]
name = "Headless" name = "Headless"
description = "An application that runs without default plugins" description = "An application that runs without default plugins"
category = "Application" category = "Application"
# Doesn't render anything, doesn't create a canvas
wasm = false wasm = false
[[example]] [[example]]
@ -1713,6 +1727,8 @@ doc-scrape-examples = true
name = "Log layers" name = "Log layers"
description = "Illustrate how to add custom log layers" description = "Illustrate how to add custom log layers"
category = "Application" category = "Application"
# Accesses `time`, which is not available on the web
# Also doesn't render anything
wasm = false wasm = false
[[example]] [[example]]
@ -1724,6 +1740,7 @@ doc-scrape-examples = true
name = "Advanced log layers" name = "Advanced log layers"
description = "Illustrate how to transfer data between log layers and Bevy's ECS" description = "Illustrate how to transfer data between log layers and Bevy's ECS"
category = "Application" category = "Application"
# Doesn't render anything, doesn't create a canvas
wasm = false wasm = false
[[example]] [[example]]
@ -4328,6 +4345,14 @@ description = "Demonstrates specular tints and maps"
category = "3D Rendering" category = "3D Rendering"
wasm = true wasm = true
[[example]]
name = "test_invalid_skinned_mesh"
path = "tests/3d/test_invalid_skinned_mesh.rs"
doc-scrape-examples = true
[package.metadata.example.test_invalid_skinned_mesh]
hidden = true
[profile.wasm-release] [profile.wasm-release]
inherits = "release" inherits = "release"
opt-level = "z" opt-level = "z"

View File

@ -1,3 +1,5 @@
// For 2d replace `bevy_pbr::mesh_functions` with `bevy_sprite::mesh2d_functions`
// and `mesh_position_local_to_clip` with `mesh2d_position_local_to_clip`.
#import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip} #import bevy_pbr::mesh_functions::{get_world_from_local, mesh_position_local_to_clip}
struct CustomMaterial { struct CustomMaterial {

View File

@ -17,6 +17,12 @@
struct MyExtendedMaterial { struct MyExtendedMaterial {
quantize_steps: u32, quantize_steps: u32,
#ifdef SIXTEEN_BYTE_ALIGNMENT
// Web examples WebGL2 support: structs must be 16 byte aligned.
_webgl2_padding_8b: u32,
_webgl2_padding_12b: u32,
_webgl2_padding_16b: u32,
#endif
} }
@group(3) @binding(100) @group(3) @binding(100)

View File

@ -10,7 +10,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
let mut events = Events::default(); let mut events = Events::default();
for _ in 0..count { for _ in 0..count {
events.send(BenchEvent([0u8; SIZE])); events.write(BenchEvent([0u8; SIZE]));
} }
Self(events) Self(events)

View File

@ -1,5 +1,5 @@
mod iter; mod iter;
mod send; mod write;
use criterion::{criterion_group, Criterion}; use criterion::{criterion_group, Criterion};
@ -11,19 +11,19 @@ fn send(c: &mut Criterion) {
group.measurement_time(core::time::Duration::from_secs(4)); group.measurement_time(core::time::Duration::from_secs(4));
for count in [100, 1_000, 10_000] { for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_4_events_{count}"), |b| { group.bench_function(format!("size_4_events_{count}"), |b| {
let mut bench = send::Benchmark::<4>::new(count); let mut bench = write::Benchmark::<4>::new(count);
b.iter(move || bench.run()); b.iter(move || bench.run());
}); });
} }
for count in [100, 1_000, 10_000] { for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_16_events_{count}"), |b| { group.bench_function(format!("size_16_events_{count}"), |b| {
let mut bench = send::Benchmark::<16>::new(count); let mut bench = write::Benchmark::<16>::new(count);
b.iter(move || bench.run()); b.iter(move || bench.run());
}); });
} }
for count in [100, 1_000, 10_000] { for count in [100, 1_000, 10_000] {
group.bench_function(format!("size_512_events_{count}"), |b| { group.bench_function(format!("size_512_events_{count}"), |b| {
let mut bench = send::Benchmark::<512>::new(count); let mut bench = write::Benchmark::<512>::new(count);
b.iter(move || bench.run()); b.iter(move || bench.run());
}); });
} }

View File

@ -21,7 +21,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
// Force both internal buffers to be allocated. // Force both internal buffers to be allocated.
for _ in 0..2 { for _ in 0..2 {
for _ in 0..count { for _ in 0..count {
events.send(BenchEvent([0u8; SIZE])); events.write(BenchEvent([0u8; SIZE]));
} }
events.update(); events.update();
} }
@ -32,7 +32,7 @@ impl<const SIZE: usize> Benchmark<SIZE> {
pub fn run(&mut self) { pub fn run(&mut self) {
for _ in 0..self.count { for _ in 0..self.count {
self.events self.events
.send(core::hint::black_box(BenchEvent([0u8; SIZE]))); .write(core::hint::black_box(BenchEvent([0u8; SIZE])));
} }
self.events.update(); self.events.update();
} }

View File

@ -14,7 +14,6 @@ bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" } bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" } bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [

View File

@ -146,10 +146,10 @@
* * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below). * * (See SMAA_INCLUDE_VS and SMAA_INCLUDE_PS below).
* *
* And four presets: * And four presets:
* SMAA_PRESET_LOW (%60 of the quality) * SMAA_PRESET_LOW (60% of the quality)
* SMAA_PRESET_MEDIUM (%80 of the quality) * SMAA_PRESET_MEDIUM (80% of the quality)
* SMAA_PRESET_HIGH (%95 of the quality) * SMAA_PRESET_HIGH (95% of the quality)
* SMAA_PRESET_ULTRA (%99 of the quality) * SMAA_PRESET_ULTRA (99% of the quality)
* *
* For example: * For example:
* #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0) * #define SMAA_RT_METRICS float4(1.0 / 1280.0, 1.0 / 720.0, 1280.0, 720.0)

View File

@ -1864,7 +1864,7 @@ mod tests {
app.update(); app.update();
// Sending one event // Sending one event
app.world_mut().send_event(TestEvent); app.world_mut().write_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>(); let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 1); assert_eq!(test_events.len(), 1);
@ -1872,8 +1872,8 @@ mod tests {
app.update(); app.update();
// Sending two events on the next frame // Sending two events on the next frame
app.world_mut().send_event(TestEvent); app.world_mut().write_event(TestEvent);
app.world_mut().send_event(TestEvent); app.world_mut().write_event(TestEvent);
let test_events = app.world().resource::<Events<TestEvent>>(); let test_events = app.world().resource::<Events<TestEvent>>();
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3 assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3

View File

@ -12,7 +12,7 @@ use core::fmt::Debug;
#[cfg(feature = "trace")] #[cfg(feature = "trace")]
use tracing::info_span; use tracing::info_span;
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>; type ExtractFn = Box<dyn FnMut(&mut World, &mut World) + Send>;
/// A secondary application with its own [`World`]. These can run independently of each other. /// A secondary application with its own [`World`]. These can run independently of each other.
/// ///
@ -160,7 +160,7 @@ impl SubApp {
/// The first argument is the `World` to extract data from, the second argument is the app `World`. /// The first argument is the `World` to extract data from, the second argument is the app `World`.
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
where where
F: Fn(&mut World, &mut World) + Send + 'static, F: FnMut(&mut World, &mut World) + Send + 'static,
{ {
self.extract = Some(Box::new(extract)); self.extract = Some(Box::new(extract));
self self
@ -177,13 +177,13 @@ impl SubApp {
/// ``` /// ```
/// # use bevy_app::SubApp; /// # use bevy_app::SubApp;
/// # let mut app = SubApp::new(); /// # let mut app = SubApp::new();
/// let default_fn = app.take_extract(); /// let mut default_fn = app.take_extract();
/// app.set_extract(move |main, render| { /// app.set_extract(move |main, render| {
/// // Do pre-extract custom logic /// // Do pre-extract custom logic
/// // [...] /// // [...]
/// ///
/// // Call Bevy's default, which executes the Extract phase /// // Call Bevy's default, which executes the Extract phase
/// if let Some(f) = default_fn.as_ref() { /// if let Some(f) = default_fn.as_mut() {
/// f(main, render); /// f(main, render);
/// } /// }
/// ///

View File

@ -167,7 +167,7 @@ impl AssetServer {
fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) { fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) {
world world
.resource_mut::<Events<AssetEvent<A>>>() .resource_mut::<Events<AssetEvent<A>>>()
.send(AssetEvent::LoadedWithDependencies { id: id.typed() }); .write(AssetEvent::LoadedWithDependencies { id: id.typed() });
} }
fn failed_sender<A: Asset>( fn failed_sender<A: Asset>(
world: &mut World, world: &mut World,
@ -177,7 +177,7 @@ impl AssetServer {
) { ) {
world world
.resource_mut::<Events<AssetLoadFailedEvent<A>>>() .resource_mut::<Events<AssetLoadFailedEvent<A>>>()
.send(AssetLoadFailedEvent { .write(AssetLoadFailedEvent {
id: id.typed(), id: id.typed(),
path, path,
error, error,
@ -1685,7 +1685,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
} }
if !untyped_failures.is_empty() { if !untyped_failures.is_empty() {
world.send_event_batch(untyped_failures); world.write_event_batch(untyped_failures);
} }
fn queue_ancestors( fn queue_ancestors(

View File

@ -16,7 +16,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
# other # other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`. # TODO: Remove `coreaudio-sys` dep below when updating `cpal`.

View File

@ -3,6 +3,7 @@ use bevy_asset::{Asset, Handle};
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_math::Vec3; use bevy_math::Vec3;
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
/// The way Bevy manages the sound playback. /// The way Bevy manages the sound playback.
#[derive(Debug, Clone, Copy, Reflect)] #[derive(Debug, Clone, Copy, Reflect)]
@ -10,10 +11,10 @@ use bevy_reflect::prelude::*;
pub enum PlaybackMode { pub enum PlaybackMode {
/// Play the sound once. Do nothing when it ends. /// Play the sound once. Do nothing when it ends.
/// ///
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and /// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained. /// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
/// ///
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and /// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
/// added again. /// added again.
Once, Once,
/// Repeat the sound forever. /// Repeat the sound forever.
@ -27,7 +28,7 @@ pub enum PlaybackMode {
/// Initial settings to be used when audio starts playing. /// Initial settings to be used when audio starts playing.
/// ///
/// If you would like to control the audio while it is playing, query for the /// If you would like to control the audio while it is playing, query for the
/// [`AudioSink`][crate::AudioSink] or [`SpatialAudioSink`][crate::SpatialAudioSink] /// [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink)
/// components. Changes to this component will *not* be applied to already-playing audio. /// components. Changes to this component will *not* be applied to already-playing audio.
#[derive(Component, Clone, Copy, Debug, Reflect)] #[derive(Component, Clone, Copy, Debug, Reflect)]
#[reflect(Clone, Default, Component, Debug)] #[reflect(Clone, Default, Component, Debug)]
@ -78,10 +79,10 @@ impl Default for PlaybackSettings {
impl PlaybackSettings { impl PlaybackSettings {
/// Will play the associated audio source once. /// Will play the associated audio source once.
/// ///
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and /// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained. /// the underlying [`AudioSink`](crate::AudioSink) or [`SpatialAudioSink`](crate::SpatialAudioSink) has been drained.
/// ///
/// To replay a sound, the audio components provided by `AudioPlayer` must be removed and /// To replay a sound, the audio components provided by [`AudioPlayer`] must be removed and
/// added again. /// added again.
pub const ONCE: PlaybackSettings = PlaybackSettings { pub const ONCE: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Once, mode: PlaybackMode::Once,
@ -164,14 +165,15 @@ impl PlaybackSettings {
/// Settings for the listener for spatial audio sources. /// Settings for the listener for spatial audio sources.
/// ///
/// This must be accompanied by `Transform` and `GlobalTransform`. /// This is accompanied by [`Transform`] and [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
/// Only one entity with a `SpatialListener` should be present at any given time. /// Only one entity with a [`SpatialListener`] should be present at any given time.
#[derive(Component, Clone, Debug, Reflect)] #[derive(Component, Clone, Debug, Reflect)]
#[require(Transform)]
#[reflect(Clone, Default, Component, Debug)] #[reflect(Clone, Default, Component, Debug)]
pub struct SpatialListener { pub struct SpatialListener {
/// Left ear position relative to the `GlobalTransform`. /// Left ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
pub left_ear_offset: Vec3, pub left_ear_offset: Vec3,
/// Right ear position relative to the `GlobalTransform`. /// Right ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
pub right_ear_offset: Vec3, pub right_ear_offset: Vec3,
} }
@ -182,7 +184,7 @@ impl Default for SpatialListener {
} }
impl SpatialListener { impl SpatialListener {
/// Creates a new `SpatialListener` component. /// Creates a new [`SpatialListener`] component.
/// ///
/// `gap` is the distance between the left and right "ears" of the listener. Ears are /// `gap` is the distance between the left and right "ears" of the listener. Ears are
/// positioned on the x axis. /// positioned on the x axis.
@ -203,12 +205,12 @@ impl SpatialListener {
pub struct SpatialScale(pub Vec3); pub struct SpatialScale(pub Vec3);
impl SpatialScale { impl SpatialScale {
/// Create a new `SpatialScale` with the same value for all 3 dimensions. /// Create a new [`SpatialScale`] with the same value for all 3 dimensions.
pub const fn new(scale: f32) -> Self { pub const fn new(scale: f32) -> Self {
Self(Vec3::splat(scale)) Self(Vec3::splat(scale))
} }
/// Create a new `SpatialScale` with the same value for `x` and `y`, and `0.0` /// Create a new [`SpatialScale`] with the same value for `x` and `y`, and `0.0`
/// for `z`. /// for `z`.
pub const fn new_2d(scale: f32) -> Self { pub const fn new_2d(scale: f32) -> Self {
Self(Vec3::new(scale, scale, 0.0)) Self(Vec3::new(scale, scale, 0.0))
@ -238,11 +240,11 @@ pub struct DefaultSpatialScale(pub SpatialScale);
/// If the handle refers to an unavailable asset (such as if it has not finished loading yet), /// If the handle refers to an unavailable asset (such as if it has not finished loading yet),
/// the audio will not begin playing immediately. The audio will play when the asset is ready. /// the audio will not begin playing immediately. The audio will play when the asset is ready.
/// ///
/// When Bevy begins the audio playback, an [`AudioSink`][crate::AudioSink] component will be /// When Bevy begins the audio playback, an [`AudioSink`](crate::AudioSink) component will be
/// added to the entity. You can use that component to control the audio settings during playback. /// added to the entity. You can use that component to control the audio settings during playback.
/// ///
/// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the /// Playback can be configured using the [`PlaybackSettings`] component. Note that changes to the
/// `PlaybackSettings` component will *not* affect already-playing audio. /// [`PlaybackSettings`] component will *not* affect already-playing audio.
#[derive(Component, Reflect)] #[derive(Component, Reflect)]
#[reflect(Component, Clone)] #[reflect(Component, Clone)]
#[require(PlaybackSettings)] #[require(PlaybackSettings)]

View File

@ -103,7 +103,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
Entity, Entity,
&AudioPlayer<Source>, &AudioPlayer<Source>,
&PlaybackSettings, &PlaybackSettings,
Option<&GlobalTransform>, &GlobalTransform,
), ),
(Without<AudioSink>, Without<SpatialAudioSink>), (Without<AudioSink>, Without<SpatialAudioSink>),
>, >,
@ -118,7 +118,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
return; return;
}; };
for (entity, source_handle, settings, maybe_emitter_transform) in &query_nonplaying { for (entity, source_handle, settings, emitter_transform) in &query_nonplaying {
let Some(audio_source) = audio_sources.get(&source_handle.0) else { let Some(audio_source) = audio_sources.get(&source_handle.0) else {
continue; continue;
}; };
@ -136,14 +136,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
} }
let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0; let scale = settings.spatial_scale.unwrap_or(default_spatial_scale.0).0;
let emitter_translation = (emitter_transform.translation() * scale).into();
let emitter_translation = if let Some(emitter_transform) = maybe_emitter_transform {
(emitter_transform.translation() * scale).into()
} else {
warn!("Spatial AudioPlayer with no GlobalTransform component. Using zero.");
Vec3::ZERO.into()
};
let sink = match SpatialSink::try_new( let sink = match SpatialSink::try_new(
stream_handle, stream_handle,
emitter_translation, emitter_translation,

View File

@ -24,7 +24,6 @@ bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" } bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" } bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" } bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
@ -39,14 +38,12 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea
"serialize", "serialize",
] } ] }
serde = { version = "1", features = ["derive"] }
bitflags = "2.3" bitflags = "2.3"
radsort = "0.1" radsort = "0.1"
nonmax = "0.5" nonmax = "0.5"
smallvec = { version = "1", default-features = false } smallvec = { version = "1", default-features = false }
thiserror = { version = "2", default-features = false } thiserror = { version = "2", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["std"] } tracing = { version = "0.1", default-features = false, features = ["std"] }
bytemuck = { version = "1" }
[lints] [lints]
workspace = true workspace = true

View File

@ -18,8 +18,6 @@ bevy_input_focus = { path = "../bevy_input_focus", version = "0.17.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" } bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [ bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev", features = [
"bevy_ui_picking_backend", "bevy_ui_picking_backend",
] } ] }

View File

@ -176,7 +176,7 @@ impl Default for SliderRange {
} }
/// Defines the amount by which to increment or decrement the slider value when using keyboard /// Defines the amount by which to increment or decrement the slider value when using keyboard
/// shorctuts. Defaults to 1.0. /// shortcuts. Defaults to 1.0.
#[derive(Component, Debug, PartialEq, Clone)] #[derive(Component, Debug, PartialEq, Clone)]
#[component(immutable)] #[component(immutable)]
pub struct SliderStep(pub f32); pub struct SliderStep(pub f32);

View File

@ -18,14 +18,12 @@ bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev" } bevy_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" } bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_text = { path = "../bevy_text", version = "0.17.0-dev" } bevy_text = { path = "../bevy_text", version = "0.17.0-dev" }
bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" } bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_state = { path = "../bevy_state", version = "0.17.0-dev" } bevy_state = { path = "../bevy_state", version = "0.17.0-dev" }

View File

@ -18,7 +18,7 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local<u32>) {
debug!("Handling event: {:?}", event); debug!("Handling event: {:?}", event);
match event { match event {
CiTestingEvent::AppExit => { CiTestingEvent::AppExit => {
world.send_event(AppExit::Success); world.write_event(AppExit::Success);
info!("Exiting after {} frames. Test successful!", *current_frame); info!("Exiting after {} frames. Test successful!", *current_frame);
} }
CiTestingEvent::ScreenshotAndExit => { CiTestingEvent::ScreenshotAndExit => {
@ -53,7 +53,7 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local<u32>) {
} }
// Custom events are forwarded to the world. // Custom events are forwarded to the world.
CiTestingEvent::Custom(event_string) => { CiTestingEvent::Custom(event_string) => {
world.send_event(CiTestingCustomEvent(event_string)); world.write_event(CiTestingCustomEvent(event_string));
} }
} }
} }

View File

@ -56,7 +56,6 @@ critical-section = [
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false } bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false } bevy_time = { path = "../bevy_time", version = "0.17.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false } bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"alloc", "alloc",

View File

@ -630,7 +630,7 @@ impl Archetype {
#[inline] #[inline]
pub fn len(&self) -> u32 { pub fn len(&self) -> u32 {
// No entity may have more than one archetype row, so there are no duplicates, // No entity may have more than one archetype row, so there are no duplicates,
// and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity. // and there may only ever be u32::MAX entities, so the length never exceeds u32's capacity.
self.entities.len() as u32 self.entities.len() as u32
} }

View File

@ -1584,7 +1584,7 @@ impl<'w> BundleRemover<'w> {
// Handle sparse set removes // Handle sparse set removes
for component_id in self.bundle_info.as_ref().iter_explicit_components() { for component_id in self.bundle_info.as_ref().iter_explicit_components() {
if self.old_archetype.as_ref().contains(component_id) { if self.old_archetype.as_ref().contains(component_id) {
world.removed_components.send(component_id, entity); world.removed_components.write(component_id, entity);
// Make sure to drop components stored in sparse sets. // Make sure to drop components stored in sparse sets.
// Dense components are dropped later in `move_to_and_drop_missing_unchecked`. // Dense components are dropped later in `move_to_and_drop_missing_unchecked`.

View File

@ -795,47 +795,34 @@ impl<'w> EntityClonerBuilder<'w, OptOut> {
/// this behavior. /// this behavior.
pub fn deny<T: Bundle>(&mut self) -> &mut Self { pub fn deny<T: Bundle>(&mut self) -> &mut Self {
let bundle_id = self.world.register_bundle::<T>().id(); let bundle_id = self.world.register_bundle::<T>().id();
self.deny_by_bundle_id(bundle_id) self.deny_by_ids(bundle_id)
}
/// Disallows all components of the bundle ID from being cloned.
///
/// If component `A` is denied here and component `B` requires `A`, then `A`
/// is denied as well. See [`Self::without_required_by_components`] to alter
/// this behavior.
pub fn deny_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
if let Some(bundle) = self.world.bundles().get(bundle_id) {
let ids = bundle.explicit_components().iter();
for &id in ids {
self.filter.filter_deny(id, self.world);
}
}
self
} }
/// Extends the list of components that shouldn't be cloned. /// Extends the list of components that shouldn't be cloned.
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
/// ///
/// If component `A` is denied here and component `B` requires `A`, then `A` /// If component `A` is denied here and component `B` requires `A`, then `A`
/// is denied as well. See [`Self::without_required_by_components`] to alter /// is denied as well. See [`Self::without_required_by_components`] to alter
/// this behavior. /// this behavior.
pub fn deny_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self { pub fn deny_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
for id in ids { ids.filter_ids(&mut |ids| match ids {
self.filter.filter_deny(id, self.world); FilterableId::Type(type_id) => {
} if let Some(id) = self.world.components().get_valid_id(type_id) {
self self.filter.filter_deny(id, self.world);
} }
/// Extends the list of components that shouldn't be cloned by type ids.
///
/// If component `A` is denied here and component `B` requires `A`, then `A`
/// is denied as well. See [`Self::without_required_by_components`] to alter
/// this behavior.
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
for type_id in ids {
if let Some(id) = self.world.components().get_valid_id(type_id) {
self.filter.filter_deny(id, self.world);
} }
} FilterableId::Component(component_id) => {
self.filter.filter_deny(component_id, self.world);
}
FilterableId::Bundle(bundle_id) => {
if let Some(bundle) = self.world.bundles().get(bundle_id) {
let ids = bundle.explicit_components().iter();
for &id in ids {
self.filter.filter_deny(id, self.world);
}
}
}
});
self self
} }
} }
@ -865,7 +852,7 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
/// to alter this behavior. /// to alter this behavior.
pub fn allow<T: Bundle>(&mut self) -> &mut Self { pub fn allow<T: Bundle>(&mut self) -> &mut Self {
let bundle_id = self.world.register_bundle::<T>().id(); let bundle_id = self.world.register_bundle::<T>().id();
self.allow_by_bundle_id(bundle_id) self.allow_by_ids(bundle_id)
} }
/// Adds all components of the bundle to the list of components to clone if /// Adds all components of the bundle to the list of components to clone if
@ -876,94 +863,55 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
/// to alter this behavior. /// to alter this behavior.
pub fn allow_if_new<T: Bundle>(&mut self) -> &mut Self { pub fn allow_if_new<T: Bundle>(&mut self) -> &mut Self {
let bundle_id = self.world.register_bundle::<T>().id(); let bundle_id = self.world.register_bundle::<T>().id();
self.allow_by_bundle_id_if_new(bundle_id) self.allow_by_ids_if_new(bundle_id)
}
/// Adds all components of the bundle ID to the list of components to clone.
///
/// If component `A` is allowed here and requires component `B`, then `B`
/// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior.
pub fn allow_by_bundle_id(&mut self, bundle_id: BundleId) -> &mut Self {
if let Some(bundle) = self.world.bundles().get(bundle_id) {
let ids = bundle.explicit_components().iter();
for &id in ids {
self.filter
.filter_allow(id, self.world, InsertMode::Replace);
}
}
self
}
/// Adds all components of the bundle ID to the list of components to clone
/// if the target does not contain them.
///
/// If component `A` is allowed here and requires component `B`, then `B`
/// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior.
pub fn allow_by_bundle_id_if_new(&mut self, bundle_id: BundleId) -> &mut Self {
if let Some(bundle) = self.world.bundles().get(bundle_id) {
let ids = bundle.explicit_components().iter();
for &id in ids {
self.filter.filter_allow(id, self.world, InsertMode::Keep);
}
}
self
} }
/// Extends the list of components to clone. /// Extends the list of components to clone.
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
/// ///
/// If component `A` is allowed here and requires component `B`, then `B` /// If component `A` is allowed here and requires component `B`, then `B`
/// is allowed as well. See [`Self::without_required_components`] /// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior. /// to alter this behavior.
pub fn allow_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self { pub fn allow_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
for id in ids { self.allow_by_ids_inner(ids, InsertMode::Replace);
self.filter
.filter_allow(id, self.world, InsertMode::Replace);
}
self self
} }
/// Extends the list of components to clone if the target does not contain them. /// Extends the list of components to clone if the target does not contain them.
/// Supports filtering by [`TypeId`], [`ComponentId`], [`BundleId`], and [`IntoIterator`] yielding one of these.
/// ///
/// If component `A` is allowed here and requires component `B`, then `B` /// If component `A` is allowed here and requires component `B`, then `B`
/// is allowed as well. See [`Self::without_required_components`] /// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior. /// to alter this behavior.
pub fn allow_by_ids_if_new(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self { pub fn allow_by_ids_if_new<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
for id in ids { self.allow_by_ids_inner(ids, InsertMode::Keep);
self.filter.filter_allow(id, self.world, InsertMode::Keep);
}
self self
} }
/// Extends the list of components to clone using [`TypeId`]s. fn allow_by_ids_inner<M: Marker>(
/// &mut self,
/// If component `A` is allowed here and requires component `B`, then `B` ids: impl FilterableIds<M>,
/// is allowed as well. See [`Self::without_required_components`] insert_mode: InsertMode,
/// to alter this behavior. ) {
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self { ids.filter_ids(&mut |id| match id {
for type_id in ids { FilterableId::Type(type_id) => {
if let Some(id) = self.world.components().get_valid_id(type_id) { if let Some(id) = self.world.components().get_valid_id(type_id) {
self.filter.filter_allow(id, self.world, insert_mode);
}
}
FilterableId::Component(component_id) => {
self.filter self.filter
.filter_allow(id, self.world, InsertMode::Replace); .filter_allow(component_id, self.world, insert_mode);
} }
} FilterableId::Bundle(bundle_id) => {
self if let Some(bundle) = self.world.bundles().get(bundle_id) {
} let ids = bundle.explicit_components().iter();
for &id in ids {
/// Extends the list of components to clone using [`TypeId`]s if the target self.filter.filter_allow(id, self.world, insert_mode);
/// does not contain them. }
/// }
/// If component `A` is allowed here and requires component `B`, then `B`
/// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior.
pub fn allow_by_type_ids_if_new(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
for type_id in ids {
if let Some(id) = self.world.components().get_valid_id(type_id) {
self.filter.filter_allow(id, self.world, InsertMode::Keep);
} }
} });
self
} }
} }
@ -1309,6 +1257,77 @@ impl Required {
} }
} }
mod private {
use super::*;
/// Marker trait to allow multiple blanket implementations for [`FilterableIds`].
pub trait Marker {}
/// Marker struct for [`FilterableIds`] implementation for single-value types.
pub struct ScalarType {}
impl Marker for ScalarType {}
/// Marker struct for [`FilterableIds`] implementation for [`IntoIterator`] types.
pub struct VectorType {}
impl Marker for VectorType {}
/// Defines types of ids that [`EntityClonerBuilder`] can filter components by.
#[derive(From)]
pub enum FilterableId {
Type(TypeId),
Component(ComponentId),
Bundle(BundleId),
}
impl<'a, T> From<&'a T> for FilterableId
where
T: Into<FilterableId> + Copy,
{
#[inline]
fn from(value: &'a T) -> Self {
(*value).into()
}
}
/// A trait to allow [`EntityClonerBuilder`] filter by any supported id type and their iterators,
/// reducing the number of method permutations required for all id types.
///
/// The supported id types that can be used to filter components are defined by [`FilterableId`], which allows following types: [`TypeId`], [`ComponentId`] and [`BundleId`].
///
/// `M` is a generic marker to allow multiple blanket implementations of this trait.
/// This works because `FilterableId<M1>` is a different trait from `FilterableId<M2>`, so multiple blanket implementations for different `M` are allowed.
/// The reason this is required is because supporting `IntoIterator` requires blanket implementation, but that will conflict with implementation for `TypeId`
/// since `IntoIterator` can technically be implemented for `TypeId` in the future.
/// Functions like `allow_by_ids` rely on type inference to automatically select proper type for `M` at call site.
pub trait FilterableIds<M: Marker> {
/// Takes in a function that processes all types of [`FilterableId`] one-by-one.
fn filter_ids(self, ids: &mut impl FnMut(FilterableId));
}
impl<I, T> FilterableIds<VectorType> for I
where
I: IntoIterator<Item = T>,
T: Into<FilterableId>,
{
#[inline]
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
for id in self.into_iter() {
ids(id.into());
}
}
}
impl<T> FilterableIds<ScalarType> for T
where
T: Into<FilterableId>,
{
#[inline]
fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) {
ids(self.into());
}
}
}
use private::{FilterableId, FilterableIds, Marker};
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;

View File

@ -54,8 +54,8 @@ use {
/// // run this once per update/frame /// // run this once per update/frame
/// events.update(); /// events.update();
/// ///
/// // somewhere else: send an event /// // somewhere else: write an event
/// events.send(MyEvent { value: 1 }); /// events.write(MyEvent { value: 1 });
/// ///
/// // somewhere else: read the events /// // somewhere else: read the events
/// for event in cursor.read(&events) { /// for event in cursor.read(&events) {
@ -118,22 +118,22 @@ impl<E: BufferedEvent> Events<E> {
self.events_a.start_event_count self.events_a.start_event_count
} }
/// "Sends" an `event` by writing it to the current event buffer. /// Writes an `event` to the current event buffer.
/// [`EventReader`](super::EventReader)s can then read the event. /// [`EventReader`](super::EventReader)s can then read the event.
/// This method returns the [ID](`EventId`) of the sent `event`. /// This method returns the [ID](`EventId`) of the written `event`.
#[track_caller] #[track_caller]
pub fn send(&mut self, event: E) -> EventId<E> { pub fn write(&mut self, event: E) -> EventId<E> {
self.send_with_caller(event, MaybeLocation::caller()) self.write_with_caller(event, MaybeLocation::caller())
} }
pub(crate) fn send_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId<E> { pub(crate) fn write_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId<E> {
let event_id = EventId { let event_id = EventId {
id: self.event_count, id: self.event_count,
caller, caller,
_marker: PhantomData, _marker: PhantomData,
}; };
#[cfg(feature = "detailed_trace")] #[cfg(feature = "detailed_trace")]
tracing::trace!("Events::send() -> id: {}", event_id); tracing::trace!("Events::write() -> id: {}", event_id);
let event_instance = EventInstance { event_id, event }; let event_instance = EventInstance { event_id, event };
@ -143,30 +143,59 @@ impl<E: BufferedEvent> Events<E> {
event_id event_id
} }
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. /// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than sending each event individually. /// This is more efficient than writing each event individually.
/// This method returns the [IDs](`EventId`) of the sent `events`. /// This method returns the [IDs](`EventId`) of the written `events`.
#[track_caller] #[track_caller]
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> { pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
let last_count = self.event_count; let last_count = self.event_count;
self.extend(events); self.extend(events);
SendBatchIds { WriteBatchIds {
last_count, last_count,
event_count: self.event_count, event_count: self.event_count,
_marker: PhantomData, _marker: PhantomData,
} }
} }
/// Writes the default value of the event. Useful when the event is an empty struct.
/// This method returns the [ID](`EventId`) of the written `event`.
#[track_caller]
pub fn write_default(&mut self) -> EventId<E>
where
E: Default,
{
self.write(Default::default())
}
/// "Sends" an `event` by writing it to the current event buffer.
/// [`EventReader`](super::EventReader)s can then read the event.
/// This method returns the [ID](`EventId`) of the sent `event`.
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write` instead.")]
#[track_caller]
pub fn send(&mut self, event: E) -> EventId<E> {
self.write(event)
}
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than sending each event individually.
/// This method returns the [IDs](`EventId`) of the sent `events`.
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write_batch` instead.")]
#[track_caller]
pub fn send_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
self.write_batch(events)
}
/// Sends the default value of the event. Useful when the event is an empty struct. /// Sends the default value of the event. Useful when the event is an empty struct.
/// This method returns the [ID](`EventId`) of the sent `event`. /// This method returns the [ID](`EventId`) of the sent `event`.
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write_default` instead.")]
#[track_caller] #[track_caller]
pub fn send_default(&mut self) -> EventId<E> pub fn send_default(&mut self) -> EventId<E>
where where
E: Default, E: Default,
{ {
self.send(Default::default()) self.write_default()
} }
/// Gets a new [`EventCursor`]. This will include all events already in the event buffers. /// Gets a new [`EventCursor`]. This will include all events already in the event buffers.
@ -351,14 +380,18 @@ impl<E: BufferedEvent> DerefMut for EventSequence<E> {
} }
} }
/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch. /// [`Iterator`] over written [`EventIds`](`EventId`) from a batch.
pub struct SendBatchIds<E> { pub struct WriteBatchIds<E> {
last_count: usize, last_count: usize,
event_count: usize, event_count: usize,
_marker: PhantomData<E>, _marker: PhantomData<E>,
} }
impl<E: BufferedEvent> Iterator for SendBatchIds<E> { /// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch.
#[deprecated(since = "0.17.0", note = "Use `WriteBatchIds` instead.")]
pub type SendBatchIds<E> = WriteBatchIds<E>;
impl<E: BufferedEvent> Iterator for WriteBatchIds<E> {
type Item = EventId<E>; type Item = EventId<E>;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
@ -378,7 +411,7 @@ impl<E: BufferedEvent> Iterator for SendBatchIds<E> {
} }
} }
impl<E: BufferedEvent> ExactSizeIterator for SendBatchIds<E> { impl<E: BufferedEvent> ExactSizeIterator for WriteBatchIds<E> {
fn len(&self) -> usize { fn len(&self) -> usize {
self.event_count.saturating_sub(self.last_count) self.event_count.saturating_sub(self.last_count)
} }
@ -400,22 +433,22 @@ mod tests {
assert_eq!(test_events.iter_current_update_events().count(), 0); assert_eq!(test_events.iter_current_update_events().count(), 0);
test_events.update(); test_events.update();
// Sending one event // Writing one event
test_events.send(TestEvent); test_events.write(TestEvent);
assert_eq!(test_events.len(), 1); assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1); assert_eq!(test_events.iter_current_update_events().count(), 1);
test_events.update(); test_events.update();
// Sending two events on the next frame // Writing two events on the next frame
test_events.send(TestEvent); test_events.write(TestEvent);
test_events.send(TestEvent); test_events.write(TestEvent);
assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3 assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3
assert_eq!(test_events.iter_current_update_events().count(), 2); assert_eq!(test_events.iter_current_update_events().count(), 2);
test_events.update(); test_events.update();
// Sending zero events // Writing zero events
assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2 assert_eq!(test_events.len(), 2); // Events are double-buffered, so we see 2 + 0 = 2
assert_eq!(test_events.iter_current_update_events().count(), 0); assert_eq!(test_events.iter_current_update_events().count(), 0);
} }

View File

@ -41,7 +41,7 @@ use core::marker::PhantomData;
/// } /// }
/// ///
/// for event in events_to_resend { /// for event in events_to_resend {
/// events.send(MyEvent); /// events.write(MyEvent);
/// } /// }
/// } /// }
/// ///

View File

@ -13,7 +13,8 @@ mod writer;
pub(crate) use base::EventInstance; pub(crate) use base::EventInstance;
pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey}; pub use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey};
pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event}; pub use bevy_ecs_macros::{BufferedEvent, EntityEvent, Event};
pub use collections::{Events, SendBatchIds}; #[expect(deprecated, reason = "`SendBatchIds` was renamed to `WriteBatchIds`.")]
pub use collections::{Events, SendBatchIds, WriteBatchIds};
pub use event_cursor::EventCursor; pub use event_cursor::EventCursor;
#[cfg(feature = "multi_threaded")] #[cfg(feature = "multi_threaded")]
pub use iterators::EventParIter; pub use iterators::EventParIter;
@ -68,7 +69,7 @@ mod tests {
let mut reader_a: EventCursor<TestEvent> = events.get_cursor(); let mut reader_a: EventCursor<TestEvent> = events.get_cursor();
events.send(event_0); events.write(event_0);
assert_eq!( assert_eq!(
get_events(&events, &mut reader_a), get_events(&events, &mut reader_a),
@ -94,7 +95,7 @@ mod tests {
"second iteration of reader_b created after event results in zero events" "second iteration of reader_b created after event results in zero events"
); );
events.send(event_1); events.write(event_1);
let mut reader_c = events.get_cursor(); let mut reader_c = events.get_cursor();
@ -119,7 +120,7 @@ mod tests {
let mut reader_d = events.get_cursor(); let mut reader_d = events.get_cursor();
events.send(event_2); events.write(event_2);
assert_eq!( assert_eq!(
get_events(&events, &mut reader_a), get_events(&events, &mut reader_a),
@ -153,17 +154,17 @@ mod tests {
assert!(reader.read(&events).next().is_none()); assert!(reader.read(&events).next().is_none());
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert_eq!(*reader.read(&events).next().unwrap(), TestEvent { i: 0 }); assert_eq!(*reader.read(&events).next().unwrap(), TestEvent { i: 0 });
assert_eq!(reader.read(&events).next(), None); assert_eq!(reader.read(&events).next(), None);
events.send(TestEvent { i: 1 }); events.write(TestEvent { i: 1 });
clear_func(&mut events); clear_func(&mut events);
assert!(reader.read(&events).next().is_none()); assert!(reader.read(&events).next().is_none());
events.send(TestEvent { i: 2 }); events.write(TestEvent { i: 2 });
events.update(); events.update();
events.send(TestEvent { i: 3 }); events.write(TestEvent { i: 3 });
assert!(reader assert!(reader
.read(&events) .read(&events)
@ -185,22 +186,22 @@ mod tests {
} }
#[test] #[test]
fn test_events_send_default() { fn test_events_write_default() {
let mut events = Events::<EmptyTestEvent>::default(); let mut events = Events::<EmptyTestEvent>::default();
events.send_default(); events.write_default();
let mut reader = events.get_cursor(); let mut reader = events.get_cursor();
assert_eq!(get_events(&events, &mut reader), vec![EmptyTestEvent]); assert_eq!(get_events(&events, &mut reader), vec![EmptyTestEvent]);
} }
#[test] #[test]
fn test_send_events_ids() { fn test_write_events_ids() {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
let event_0 = TestEvent { i: 0 }; let event_0 = TestEvent { i: 0 };
let event_1 = TestEvent { i: 1 }; let event_1 = TestEvent { i: 1 };
let event_2 = TestEvent { i: 2 }; let event_2 = TestEvent { i: 2 };
let event_0_id = events.send(event_0); let event_0_id = events.write(event_0);
assert_eq!( assert_eq!(
events.get_event(event_0_id.id), events.get_event(event_0_id.id),
@ -208,7 +209,7 @@ mod tests {
"Getting a sent event by ID should return the original event" "Getting a sent event by ID should return the original event"
); );
let mut event_ids = events.send_batch([event_1, event_2]); let mut event_ids = events.write_batch([event_1, event_2]);
let event_id = event_ids.next().expect("Event 1 must have been sent"); let event_id = event_ids.next().expect("Event 1 must have been sent");
@ -253,14 +254,14 @@ mod tests {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
let mut reader = events.get_cursor(); let mut reader = events.get_cursor();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
events.send(TestEvent { i: 1 }); events.write(TestEvent { i: 1 });
assert_eq!(reader.read(&events).count(), 2); assert_eq!(reader.read(&events).count(), 2);
let mut old_events = Vec::from_iter(events.update_drain()); let mut old_events = Vec::from_iter(events.update_drain());
assert!(old_events.is_empty()); assert!(old_events.is_empty());
events.send(TestEvent { i: 2 }); events.write(TestEvent { i: 2 });
assert_eq!(reader.read(&events).count(), 1); assert_eq!(reader.read(&events).count(), 1);
old_events.extend(events.update_drain()); old_events.extend(events.update_drain());
@ -278,7 +279,7 @@ mod tests {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
assert!(events.is_empty()); assert!(events.is_empty());
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert!(!events.is_empty()); assert!(!events.is_empty());
events.update(); events.update();
@ -308,12 +309,12 @@ mod tests {
let mut cursor = events.get_cursor(); let mut cursor = events.get_cursor();
assert!(cursor.read(&events).next().is_none()); assert!(cursor.read(&events).next().is_none());
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
let sent_event = cursor.read(&events).next().unwrap(); let sent_event = cursor.read(&events).next().unwrap();
assert_eq!(sent_event, &TestEvent { i: 0 }); assert_eq!(sent_event, &TestEvent { i: 0 });
assert!(cursor.read(&events).next().is_none()); assert!(cursor.read(&events).next().is_none());
events.send(TestEvent { i: 2 }); events.write(TestEvent { i: 2 });
let sent_event = cursor.read(&events).next().unwrap(); let sent_event = cursor.read(&events).next().unwrap();
assert_eq!(sent_event, &TestEvent { i: 2 }); assert_eq!(sent_event, &TestEvent { i: 2 });
assert!(cursor.read(&events).next().is_none()); assert!(cursor.read(&events).next().is_none());
@ -330,7 +331,7 @@ mod tests {
assert!(write_cursor.read_mut(&mut events).next().is_none()); assert!(write_cursor.read_mut(&mut events).next().is_none());
assert!(read_cursor.read(&events).next().is_none()); assert!(read_cursor.read(&events).next().is_none());
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
let sent_event = write_cursor.read_mut(&mut events).next().unwrap(); let sent_event = write_cursor.read_mut(&mut events).next().unwrap();
assert_eq!(sent_event, &mut TestEvent { i: 0 }); assert_eq!(sent_event, &mut TestEvent { i: 0 });
*sent_event = TestEvent { i: 1 }; // Mutate whole event *sent_event = TestEvent { i: 1 }; // Mutate whole event
@ -340,7 +341,7 @@ mod tests {
); );
assert!(read_cursor.read(&events).next().is_none()); assert!(read_cursor.read(&events).next().is_none());
events.send(TestEvent { i: 2 }); events.write(TestEvent { i: 2 });
let sent_event = write_cursor.read_mut(&mut events).next().unwrap(); let sent_event = write_cursor.read_mut(&mut events).next().unwrap();
assert_eq!(sent_event, &mut TestEvent { i: 2 }); assert_eq!(sent_event, &mut TestEvent { i: 2 });
sent_event.i = 3; // Mutate sub value sent_event.i = 3; // Mutate sub value
@ -360,7 +361,7 @@ mod tests {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
let mut reader = events.get_cursor(); let mut reader = events.get_cursor();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 1); assert_eq!(reader.len(&events), 1);
reader.clear(&events); reader.clear(&events);
assert_eq!(reader.len(&events), 0); assert_eq!(reader.len(&events), 0);
@ -369,12 +370,12 @@ mod tests {
#[test] #[test]
fn test_event_cursor_len_update() { fn test_event_cursor_len_update() {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
let reader = events.get_cursor(); let reader = events.get_cursor();
assert_eq!(reader.len(&events), 2); assert_eq!(reader.len(&events), 2);
events.update(); events.update();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 3); assert_eq!(reader.len(&events), 3);
events.update(); events.update();
assert_eq!(reader.len(&events), 1); assert_eq!(reader.len(&events), 1);
@ -385,10 +386,10 @@ mod tests {
#[test] #[test]
fn test_event_cursor_len_current() { fn test_event_cursor_len_current() {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
let reader = events.get_cursor_current(); let reader = events.get_cursor_current();
assert!(reader.is_empty(&events)); assert!(reader.is_empty(&events));
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 1); assert_eq!(reader.len(&events), 1);
assert!(!reader.is_empty(&events)); assert!(!reader.is_empty(&events));
} }
@ -396,9 +397,9 @@ mod tests {
#[test] #[test]
fn test_event_cursor_iter_len_updated() { fn test_event_cursor_iter_len_updated() {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
events.send(TestEvent { i: 1 }); events.write(TestEvent { i: 1 });
events.send(TestEvent { i: 2 }); events.write(TestEvent { i: 2 });
let mut reader = events.get_cursor(); let mut reader = events.get_cursor();
let mut iter = reader.read(&events); let mut iter = reader.read(&events);
assert_eq!(iter.len(), 3); assert_eq!(iter.len(), 3);
@ -420,7 +421,7 @@ mod tests {
#[test] #[test]
fn test_event_cursor_len_filled() { fn test_event_cursor_len_filled() {
let mut events = Events::<TestEvent>::default(); let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 }); events.write(TestEvent { i: 0 });
assert_eq!(events.get_cursor().len(&events), 1); assert_eq!(events.get_cursor().len(&events), 1);
assert!(!events.get_cursor().is_empty(&events)); assert!(!events.get_cursor().is_empty(&events));
} }
@ -437,7 +438,7 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.init_resource::<Events<TestEvent>>(); world.init_resource::<Events<TestEvent>>();
for _ in 0..100 { for _ in 0..100 {
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
} }
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
@ -479,7 +480,7 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.init_resource::<Events<TestEvent>>(); world.init_resource::<Events<TestEvent>>();
for _ in 0..100 { for _ in 0..100 {
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
} }
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
schedule.add_systems( schedule.add_systems(
@ -531,13 +532,13 @@ mod tests {
let last = reader.run((), &mut world).unwrap(); let last = reader.run((), &mut world).unwrap();
assert!(last.is_none(), "EventReader should be empty"); assert!(last.is_none(), "EventReader should be empty");
world.send_event(TestEvent { i: 0 }); world.write_event(TestEvent { i: 0 });
let last = reader.run((), &mut world).unwrap(); let last = reader.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 0 })); assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 }); world.write_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 }); world.write_event(TestEvent { i: 3 });
let last = reader.run((), &mut world).unwrap(); let last = reader.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 3 })); assert_eq!(last, Some(TestEvent { i: 3 }));
@ -561,13 +562,13 @@ mod tests {
let last = mutator.run((), &mut world).unwrap(); let last = mutator.run((), &mut world).unwrap();
assert!(last.is_none(), "EventMutator should be empty"); assert!(last.is_none(), "EventMutator should be empty");
world.send_event(TestEvent { i: 0 }); world.write_event(TestEvent { i: 0 });
let last = mutator.run((), &mut world).unwrap(); let last = mutator.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 0 })); assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 }); world.write_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 }); world.write_event(TestEvent { i: 3 });
let last = mutator.run((), &mut world).unwrap(); let last = mutator.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 3 })); assert_eq!(last, Some(TestEvent { i: 3 }));
@ -582,11 +583,11 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.init_resource::<Events<TestEvent>>(); world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 }); world.write_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 }); world.write_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 }); world.write_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 }); world.write_event(TestEvent { i: 4 });
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| { schedule.add_systems(|mut events: EventReader<TestEvent>| {
@ -608,11 +609,11 @@ mod tests {
let mut world = World::new(); let mut world = World::new();
world.init_resource::<Events<TestEvent>>(); world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 }); world.write_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 }); world.write_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 }); world.write_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 }); world.write_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 }); world.write_event(TestEvent { i: 4 });
let mut schedule = Schedule::default(); let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| { schedule.add_systems(|mut events: EventReader<TestEvent>| {

View File

@ -89,7 +89,7 @@ impl<'w, 's, E: BufferedEvent> EventMutator<'w, 's, E> {
/// }); /// });
/// }); /// });
/// for value in 0..100 { /// for value in 0..100 {
/// world.send_event(MyEvent { value }); /// world.write_event(MyEvent { value });
/// } /// }
/// schedule.run(&mut world); /// schedule.run(&mut world);
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap(); /// let Counter(counter) = world.remove_resource::<Counter>().unwrap();

View File

@ -61,7 +61,7 @@ impl<'w, 's, E: BufferedEvent> EventReader<'w, 's, E> {
/// }); /// });
/// }); /// });
/// for value in 0..100 { /// for value in 0..100 {
/// world.send_event(MyEvent { value }); /// world.write_event(MyEvent { value });
/// } /// }
/// schedule.run(&mut world); /// schedule.run(&mut world);
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap(); /// let Counter(counter) = world.remove_resource::<Counter>().unwrap();

View File

@ -1,9 +1,9 @@
use bevy_ecs::{ use bevy_ecs::{
event::{BufferedEvent, EventId, Events, SendBatchIds}, event::{BufferedEvent, EventId, Events, WriteBatchIds},
system::{ResMut, SystemParam}, system::{ResMut, SystemParam},
}; };
/// Sends [`BufferedEvent`]s of type `T`. /// Writes [`BufferedEvent`]s of type `T`.
/// ///
/// # Usage /// # Usage
/// ///
@ -34,14 +34,14 @@ use bevy_ecs::{
/// ///
/// `EventWriter` can only write events of one specific type, which must be known at compile-time. /// `EventWriter` can only write events of one specific type, which must be known at compile-time.
/// This is not a problem most of the time, but you may find a situation where you cannot know /// This is not a problem most of the time, but you may find a situation where you cannot know
/// ahead of time every kind of event you'll need to send. In this case, you can use the "type-erased event" pattern. /// ahead of time every kind of event you'll need to write. In this case, you can use the "type-erased event" pattern.
/// ///
/// ``` /// ```
/// # use bevy_ecs::{prelude::*, event::Events}; /// # use bevy_ecs::{prelude::*, event::Events};
/// # #[derive(Event, BufferedEvent)] /// # #[derive(Event, BufferedEvent)]
/// # pub struct MyEvent; /// # pub struct MyEvent;
/// fn send_untyped(mut commands: Commands) { /// fn write_untyped(mut commands: Commands) {
/// // Send an event of a specific type without having to declare that /// // Write an event of a specific type without having to declare that
/// // type as a SystemParam. /// // type as a SystemParam.
/// // /// //
/// // Effectively, we're just moving the type parameter from the /type/ to the /method/, /// // Effectively, we're just moving the type parameter from the /type/ to the /method/,
@ -51,7 +51,7 @@ use bevy_ecs::{
/// // NOTE: the event won't actually be sent until commands get applied during /// // NOTE: the event won't actually be sent until commands get applied during
/// // apply_deferred. /// // apply_deferred.
/// commands.queue(|w: &mut World| { /// commands.queue(|w: &mut World| {
/// w.send_event(MyEvent); /// w.write_event(MyEvent);
/// }); /// });
/// } /// }
/// ``` /// ```
@ -72,18 +72,18 @@ impl<'w, E: BufferedEvent> EventWriter<'w, E> {
#[doc(alias = "send")] #[doc(alias = "send")]
#[track_caller] #[track_caller]
pub fn write(&mut self, event: E) -> EventId<E> { pub fn write(&mut self, event: E) -> EventId<E> {
self.events.send(event) self.events.write(event)
} }
/// Sends a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s. /// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than sending each event individually. /// This is more efficient than writing each event individually.
/// This method returns the [IDs](`EventId`) of the written `events`. /// This method returns the [IDs](`EventId`) of the written `events`.
/// ///
/// See [`Events`] for details. /// See [`Events`] for details.
#[doc(alias = "send_batch")] #[doc(alias = "send_batch")]
#[track_caller] #[track_caller]
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> { pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
self.events.send_batch(events) self.events.write_batch(events)
} }
/// Writes the default value of the event. Useful when the event is an empty struct. /// Writes the default value of the event. Useful when the event is an empty struct.
@ -96,6 +96,6 @@ impl<'w, E: BufferedEvent> EventWriter<'w, E> {
where where
E: Default, E: Default,
{ {
self.events.send_default() self.events.write_default()
} }
} }

View File

@ -455,15 +455,13 @@ pub fn validate_parent_has_component<C: Component>(
let Some(child_of) = entity_ref.get::<ChildOf>() else { let Some(child_of) = entity_ref.get::<ChildOf>() else {
return; return;
}; };
if !world let parent = child_of.parent();
.get_entity(child_of.parent()) if !world.get_entity(parent).is_ok_and(|e| e.contains::<C>()) {
.is_ok_and(|e| e.contains::<C>())
{
// TODO: print name here once Name lives in bevy_ecs // TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = None; let name: Option<String> = None;
let debug_name = DebugName::type_name::<C>(); let debug_name = DebugName::type_name::<C>();
warn!( warn!(
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\ "warning[B0004]: {}{name} with the {ty_name} component has a parent ({parent}) without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004", This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(), caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = debug_name.shortname(), ty_name = debug_name.shortname(),

View File

@ -465,10 +465,16 @@ impl RemovedComponentEvents {
} }
/// Sends a removal event for the specified component. /// Sends a removal event for the specified component.
#[deprecated(since = "0.17.0", note = "Use `RemovedComponentEvents:write` instead.")]
pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) { pub fn send(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
self.write(component_id, entity);
}
/// Writes a removal event for the specified component.
pub fn write(&mut self, component_id: impl Into<ComponentId>, entity: Entity) {
self.event_sets self.event_sets
.get_or_insert_with(component_id.into(), Default::default) .get_or_insert_with(component_id.into(), Default::default)
.send(RemovedComponentEntity(entity)); .write(RemovedComponentEntity(entity));
} }
} }

View File

@ -133,7 +133,7 @@ pub trait Relationship: Component + Sized {
.and_modify(move |mut relationship_target| { .and_modify(move |mut relationship_target| {
relationship_target.collection_mut_risky().add(entity); relationship_target.collection_mut_risky().add(entity);
}) })
.or_insert_with(|| { .or_insert_with(move || {
let mut target = Self::RelationshipTarget::with_capacity(1); let mut target = Self::RelationshipTarget::with_capacity(1);
target.collection_mut_risky().add(entity); target.collection_mut_risky().add(entity);
target target

View File

@ -875,7 +875,7 @@ pub mod common_conditions {
/// app.run(&mut world); /// app.run(&mut world);
/// assert_eq!(world.resource::<Counter>().0, 0); /// assert_eq!(world.resource::<Counter>().0, 0);
/// ///
/// world.resource_mut::<Events<MyEvent>>().send(MyEvent); /// world.resource_mut::<Events<MyEvent>>().write(MyEvent);
/// ///
/// // A `MyEvent` event has been pushed so `my_system` will run /// // A `MyEvent` event has been pushed so `my_system` will run
/// app.run(&mut world); /// app.run(&mut world);

View File

@ -597,7 +597,7 @@ impl Table {
#[inline] #[inline]
pub fn entity_count(&self) -> u32 { pub fn entity_count(&self) -> u32 {
// No entity may have more than one table row, so there are no duplicates, // No entity may have more than one table row, so there are no duplicates,
// and there may only ever be u32::MAX entities, so the length never exceeds u32's cappacity. // and there may only ever be u32::MAX entities, so the length never exceeds u32's capacity.
self.entities.len() as u32 self.entities.len() as u32
} }

View File

@ -229,12 +229,19 @@ pub fn trigger_targets(
} }
} }
/// A [`Command`] that sends an arbitrary [`BufferedEvent`]. /// A [`Command`] that writes an arbitrary [`BufferedEvent`].
#[track_caller] #[track_caller]
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command { pub fn write_event<E: BufferedEvent>(event: E) -> impl Command {
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
move |world: &mut World| { move |world: &mut World| {
let mut events = world.resource_mut::<Events<E>>(); let mut events = world.resource_mut::<Events<E>>();
events.send_with_caller(event, caller); events.write_with_caller(event, caller);
} }
} }
/// A [`Command`] that writes an arbitrary [`BufferedEvent`].
#[track_caller]
#[deprecated(since = "0.17.0", note = "Use `write_event` instead.")]
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command {
write_event(event)
}

View File

@ -143,12 +143,38 @@ pub unsafe fn insert_by_id<T: Send + 'static>(
/// An [`EntityCommand`] that adds a component to an entity using /// An [`EntityCommand`] that adds a component to an entity using
/// the component's [`FromWorld`] implementation. /// the component's [`FromWorld`] implementation.
///
/// `T::from_world` will only be invoked if the component will actually be inserted.
/// In other words, `T::from_world` will *not* be invoked if `mode` is [`InsertMode::Keep`]
/// and the entity already has the component.
#[track_caller] #[track_caller]
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand { pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
let caller = MaybeLocation::caller(); let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| { move |mut entity: EntityWorldMut| {
let value = entity.world_scope(|world| T::from_world(world)); if !(mode == InsertMode::Keep && entity.contains::<T>()) {
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run); let value = entity.world_scope(|world| T::from_world(world));
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
}
}
}
/// An [`EntityCommand`] that adds a component to an entity using
/// some function that returns the component.
///
/// The function will only be invoked if the component will actually be inserted.
/// In other words, the function will *not* be invoked if `mode` is [`InsertMode::Keep`]
/// and the entity already has the component.
#[track_caller]
pub fn insert_with<T: Component, F>(component_fn: F, mode: InsertMode) -> impl EntityCommand
where
F: FnOnce() -> T + Send + 'static,
{
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
let value = component_fn();
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
}
} }
} }

View File

@ -1124,9 +1124,9 @@ impl<'w, 's> Commands<'w, 's> {
self.spawn(Observer::new(observer)) self.spawn(Observer::new(observer))
} }
/// Sends an arbitrary [`BufferedEvent`]. /// Writes an arbitrary [`BufferedEvent`].
/// ///
/// This is a convenience method for sending events /// This is a convenience method for writing events
/// without requiring an [`EventWriter`](crate::event::EventWriter). /// without requiring an [`EventWriter`](crate::event::EventWriter).
/// ///
/// # Performance /// # Performance
@ -1137,11 +1137,29 @@ impl<'w, 's> Commands<'w, 's> {
/// If these events are performance-critical or very frequently sent, /// If these events are performance-critical or very frequently sent,
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead. /// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
#[track_caller] #[track_caller]
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self { pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
self.queue(command::send_event(event)); self.queue(command::write_event(event));
self self
} }
/// Writes an arbitrary [`BufferedEvent`].
///
/// This is a convenience method for writing events
/// without requiring an [`EventWriter`](crate::event::EventWriter).
///
/// # Performance
///
/// Since this is a command, exclusive world access is used, which means that it will not profit from
/// system-level parallelism on supported platforms.
///
/// If these events are performance-critical or very frequently sent,
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
#[track_caller]
#[deprecated(since = "0.17.0", note = "Use `Commands::write_event` instead.")]
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
self.write_event(event)
}
/// Runs the schedule corresponding to the given [`ScheduleLabel`]. /// Runs the schedule corresponding to the given [`ScheduleLabel`].
/// ///
/// Calls [`World::try_run_schedule`](World::try_run_schedule). /// Calls [`World::try_run_schedule`](World::try_run_schedule).
@ -2273,35 +2291,53 @@ impl<'a, T: Component> EntityEntryCommands<'a, T> {
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, /// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
/// if `T` is not already present. /// if `T` is not already present.
///
/// `default` will only be invoked if the component will actually be inserted.
#[track_caller] #[track_caller]
pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { pub fn or_insert_with<F>(&mut self, default: F) -> &mut Self
self.or_insert(default()) where
F: FnOnce() -> T + Send + 'static,
{
self.entity_commands
.queue(entity_command::insert_with(default, InsertMode::Keep));
self
} }
/// [Insert](EntityCommands::insert) the value returned from `default` into this entity, /// [Insert](EntityCommands::insert) the value returned from `default` into this entity,
/// if `T` is not already present. /// if `T` is not already present.
/// ///
/// `default` will only be invoked if the component will actually be inserted.
///
/// # Note /// # Note
/// ///
/// If the entity does not exist when this command is executed, /// If the entity does not exist when this command is executed,
/// the resulting error will be ignored. /// the resulting error will be ignored.
#[track_caller] #[track_caller]
pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self { pub fn or_try_insert_with<F>(&mut self, default: F) -> &mut Self
self.or_try_insert(default()) where
F: FnOnce() -> T + Send + 'static,
{
self.entity_commands
.queue_silenced(entity_command::insert_with(default, InsertMode::Keep));
self
} }
/// [Insert](EntityCommands::insert) `T::default` into this entity, /// [Insert](EntityCommands::insert) `T::default` into this entity,
/// if `T` is not already present. /// if `T` is not already present.
///
/// `T::default` will only be invoked if the component will actually be inserted.
#[track_caller] #[track_caller]
pub fn or_default(&mut self) -> &mut Self pub fn or_default(&mut self) -> &mut Self
where where
T: Default, T: Default,
{ {
self.or_insert(T::default()) self.or_insert_with(T::default)
} }
/// [Insert](EntityCommands::insert) `T::from_world` into this entity, /// [Insert](EntityCommands::insert) `T::from_world` into this entity,
/// if `T` is not already present. /// if `T` is not already present.
///
/// `T::from_world` will only be invoked if the component will actually be inserted.
#[track_caller] #[track_caller]
pub fn or_from_world(&mut self) -> &mut Self pub fn or_from_world(&mut self) -> &mut Self
where where
@ -2396,6 +2432,12 @@ mod tests {
} }
} }
impl Default for W<u8> {
fn default() -> Self {
unreachable!()
}
}
#[test] #[test]
fn entity_commands_entry() { fn entity_commands_entry() {
let mut world = World::default(); let mut world = World::default();
@ -2435,6 +2477,17 @@ mod tests {
let id = commands.entity(entity).entry::<W<u64>>().entity().id(); let id = commands.entity(entity).entry::<W<u64>>().entity().id();
queue.apply(&mut world); queue.apply(&mut world);
assert_eq!(id, entity); assert_eq!(id, entity);
let mut commands = Commands::new(&mut queue, &world);
commands
.entity(entity)
.entry::<W<u8>>()
.or_insert_with(|| W(5))
.or_insert_with(|| unreachable!())
.or_try_insert_with(|| unreachable!())
.or_default()
.or_from_world();
queue.apply(&mut world);
assert_eq!(5, world.get::<W<u8>>(entity).unwrap().0);
} }
#[test] #[test]

View File

@ -7,7 +7,7 @@ use crate::{
change_detection::{MaybeLocation, MutUntyped}, change_detection::{MaybeLocation, MutUntyped},
component::{ComponentId, Mutable}, component::{ComponentId, Mutable},
entity::Entity, entity::Entity,
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, SendBatchIds}, event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, WriteBatchIds},
lifecycle::{HookContext, INSERT, REPLACE}, lifecycle::{HookContext, INSERT, REPLACE},
observer::{Observers, TriggerTargets}, observer::{Observers, TriggerTargets},
prelude::{Component, QueryState}, prelude::{Component, QueryState},
@ -507,30 +507,51 @@ impl<'w> DeferredWorld<'w> {
unsafe { self.world.get_non_send_resource_mut() } unsafe { self.world.get_non_send_resource_mut() }
} }
/// Sends a [`BufferedEvent`]. /// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the sent `event`, /// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
self.write_event_batch(core::iter::once(event))?.next()
}
/// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(since = "0.17.0", note = "Use `DeferredWorld::write_event` instead.")]
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> { pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
self.send_event_batch(core::iter::once(event))?.next() self.write_event(event)
} }
/// Sends the default value of the [`BufferedEvent`] of type `E`. /// Writes the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the sent `event`, /// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn write_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
self.write_event(E::default())
}
/// Writes the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(
since = "0.17.0",
note = "Use `DeferredWorld::write_event_default` instead."
)]
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> { pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
self.send_event(E::default()) self.write_event_default::<E>()
} }
/// Sends a batch of [`BufferedEvent`]s from an iterator. /// Writes a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the sent `events`, /// This method returns the [IDs](`EventId`) of the written `events`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn send_event_batch<E: BufferedEvent>( pub fn write_event_batch<E: BufferedEvent>(
&mut self, &mut self,
events: impl IntoIterator<Item = E>, events: impl IntoIterator<Item = E>,
) -> Option<SendBatchIds<E>> { ) -> Option<WriteBatchIds<E>> {
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else { let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
log::error!( log::error!(
"Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ",
@ -538,7 +559,22 @@ impl<'w> DeferredWorld<'w> {
); );
return None; return None;
}; };
Some(events_resource.send_batch(events)) Some(events_resource.write_batch(events))
}
/// Writes a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the written `events`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(
since = "0.17.0",
note = "Use `DeferredWorld::write_event_batch` instead."
)]
pub fn send_event_batch<E: BufferedEvent>(
&mut self,
events: impl IntoIterator<Item = E>,
) -> Option<WriteBatchIds<E>> {
self.write_event_batch(events)
} }
/// Gets a pointer to the resource with the id [`ComponentId`] if it exists. /// Gets a pointer to the resource with the id [`ComponentId`] if it exists.

View File

@ -2418,7 +2418,7 @@ impl<'w> EntityWorldMut<'w> {
} }
for component_id in archetype.components() { for component_id in archetype.components() {
world.removed_components.send(component_id, self.entity); world.removed_components.write(component_id, self.entity);
} }
// Observers and on_remove hooks may reserve new entities, which // Observers and on_remove hooks may reserve new entities, which

View File

@ -50,7 +50,7 @@ use crate::{
}, },
entity::{Entities, Entity, EntityDoesNotExistError}, entity::{Entities, Entity, EntityDoesNotExistError},
entity_disabling::DefaultQueryFilters, entity_disabling::DefaultQueryFilters,
event::{Event, EventId, Events, SendBatchIds}, event::{Event, EventId, Events, WriteBatchIds},
lifecycle::RemovedComponentEvents, lifecycle::RemovedComponentEvents,
observer::Observers, observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState}, query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
@ -2626,30 +2626,48 @@ impl World {
Some(result) Some(result)
} }
/// Sends a [`BufferedEvent`]. /// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the sent `event`, /// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
self.write_event_batch(core::iter::once(event))?.next()
}
/// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(since = "0.17.0", note = "Use `World::write_event` instead.")]
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> { pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> Option<EventId<E>> {
self.send_event_batch(core::iter::once(event))?.next() self.write_event(event)
} }
/// Sends the default value of the [`BufferedEvent`] of type `E`. /// Writes the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the sent `event`, /// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn write_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
self.write_event(E::default())
}
/// Writes the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(since = "0.17.0", note = "Use `World::write_event_default` instead.")]
pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> { pub fn send_event_default<E: BufferedEvent + Default>(&mut self) -> Option<EventId<E>> {
self.send_event(E::default()) self.write_event_default::<E>()
} }
/// Sends a batch of [`BufferedEvent`]s from an iterator. /// Writes a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the sent `events`, /// This method returns the [IDs](`EventId`) of the written `events`,
/// or [`None`] if the `event` could not be sent. /// or [`None`] if the `event` could not be written.
#[inline] #[inline]
pub fn send_event_batch<E: BufferedEvent>( pub fn write_event_batch<E: BufferedEvent>(
&mut self, &mut self,
events: impl IntoIterator<Item = E>, events: impl IntoIterator<Item = E>,
) -> Option<SendBatchIds<E>> { ) -> Option<WriteBatchIds<E>> {
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else { let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
log::error!( log::error!(
"Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ", "Unable to send event `{}`\n\tEvent must be added to the app with `add_event()`\n\thttps://docs.rs/bevy/*/bevy/app/struct.App.html#method.add_event ",
@ -2657,7 +2675,19 @@ impl World {
); );
return None; return None;
}; };
Some(events_resource.send_batch(events)) Some(events_resource.write_batch(events))
}
/// Writes a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the written `events`,
/// or [`None`] if the `event` could not be written.
#[inline]
#[deprecated(since = "0.17.0", note = "Use `World::write_event_batch` instead.")]
pub fn send_event_batch<E: BufferedEvent>(
&mut self,
events: impl IntoIterator<Item = E>,
) -> Option<WriteBatchIds<E>> {
self.write_event_batch(events)
} }
/// Inserts a new resource with the given `value`. Will replace the value if it already existed. /// Inserts a new resource with the given `value`. Will replace the value if it already existed.

View File

@ -13,7 +13,6 @@ keywords = ["bevy"]
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" } bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_input = { path = "../bevy_input", version = "0.17.0-dev" } bevy_input = { path = "../bevy_input", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" } bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std", "std",

View File

@ -16,7 +16,6 @@ proc-macro = true
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" } bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
syn = "2.0" syn = "2.0"
proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
[lints] [lints]

View File

@ -36,7 +36,6 @@ bevy_scene = { path = "../bevy_scene", version = "0.17.0-dev", features = [
] } ] }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std", "std",
"serialize", "serialize",

View File

@ -63,7 +63,6 @@ libm = ["bevy_math/libm"]
bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false } bevy_app = { path = "../bevy_app", version = "0.17.0-dev", default-features = false }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev", default-features = false }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false } bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"glam", "glam",
], default-features = false, optional = true } ], default-features = false, optional = true }

View File

@ -2223,7 +2223,7 @@ mod tests {
self.app self.app
.world_mut() .world_mut()
.resource_mut::<Events<GamepadConnectionEvent>>() .resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new( .write(GamepadConnectionEvent::new(
gamepad, gamepad,
Connected { Connected {
name: "Test gamepad".to_string(), name: "Test gamepad".to_string(),
@ -2238,14 +2238,14 @@ mod tests {
self.app self.app
.world_mut() .world_mut()
.resource_mut::<Events<GamepadConnectionEvent>>() .resource_mut::<Events<GamepadConnectionEvent>>()
.send(GamepadConnectionEvent::new(gamepad, Disconnected)); .write(GamepadConnectionEvent::new(gamepad, Disconnected));
} }
pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) { pub fn send_raw_gamepad_event(&mut self, event: RawGamepadEvent) {
self.app self.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send(event); .write(event);
} }
pub fn send_raw_gamepad_event_batch( pub fn send_raw_gamepad_event_batch(
@ -2255,7 +2255,7 @@ mod tests {
self.app self.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
} }
} }
@ -2449,7 +2449,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch([ .write_batch([
RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new( RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new(
entity, entity,
GamepadAxis::LeftStickY, GamepadAxis::LeftStickY,
@ -2513,7 +2513,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
assert_eq!( assert_eq!(
ctx.app ctx.app
@ -2550,7 +2550,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
assert_eq!( assert_eq!(
ctx.app ctx.app
@ -2598,7 +2598,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
let events = ctx let events = ctx
@ -2654,7 +2654,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
assert_eq!( assert_eq!(
ctx.app ctx.app
@ -2692,7 +2692,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
let events = ctx let events = ctx
@ -2728,7 +2728,7 @@ mod tests {
ctx.app ctx.app
.world_mut() .world_mut()
.resource_mut::<Events<RawGamepadEvent>>() .resource_mut::<Events<RawGamepadEvent>>()
.send_batch(events); .write_batch(events);
ctx.update(); ctx.update();
assert_eq!( assert_eq!(

View File

@ -546,7 +546,7 @@ mod tests {
assert!(!app.world().is_focus_visible(child_of_b)); assert!(!app.world().is_focus_visible(child_of_b));
// entity_a should receive this event // entity_a should receive this event
app.world_mut().send_event(key_a_event()); app.world_mut().write_event(key_a_event());
app.update(); app.update();
assert_eq!(get_gathered(&app, entity_a), "A"); assert_eq!(get_gathered(&app, entity_a), "A");
@ -559,7 +559,7 @@ mod tests {
assert!(!app.world().is_focus_visible(entity_a)); assert!(!app.world().is_focus_visible(entity_a));
// This event should be lost // This event should be lost
app.world_mut().send_event(key_a_event()); app.world_mut().write_event(key_a_event());
app.update(); app.update();
assert_eq!(get_gathered(&app, entity_a), "A"); assert_eq!(get_gathered(&app, entity_a), "A");
@ -580,7 +580,7 @@ mod tests {
// These events should be received by entity_b and child_of_b // These events should be received by entity_b and child_of_b
app.world_mut() app.world_mut()
.send_event_batch(core::iter::repeat_n(key_a_event(), 4)); .write_event_batch(core::iter::repeat_n(key_a_event(), 4));
app.update(); app.update();
assert_eq!(get_gathered(&app, entity_a), "A"); assert_eq!(get_gathered(&app, entity_a), "A");

View File

@ -202,6 +202,10 @@ bevy_ui = ["dep:bevy_ui", "bevy_image"]
bevy_ui_render = ["dep:bevy_ui_render"] bevy_ui_render = ["dep:bevy_ui_render"]
bevy_image = ["dep:bevy_image"] bevy_image = ["dep:bevy_image"]
bevy_mesh = ["dep:bevy_mesh", "bevy_image"]
bevy_camera = ["dep:bevy_camera", "bevy_mesh"]
bevy_light = ["dep:bevy_light", "bevy_camera"]
# Used to disable code that is unsupported when Bevy is dynamically linked # Used to disable code that is unsupported when Bevy is dynamically linked
dynamic_linking = ["bevy_diagnostic/dynamic_linking"] dynamic_linking = ["bevy_diagnostic/dynamic_linking"]
@ -218,7 +222,7 @@ bevy_render = [
"dep:bevy_render", "dep:bevy_render",
"bevy_scene?/bevy_render", "bevy_scene?/bevy_render",
"bevy_gizmos?/bevy_render", "bevy_gizmos?/bevy_render",
"bevy_image", "bevy_camera",
"bevy_color/wgpu-types", "bevy_color/wgpu-types",
"bevy_color/encase", "bevy_color/encase",
] ]
@ -431,6 +435,9 @@ bevy_gizmos = { path = "../bevy_gizmos", optional = true, version = "0.17.0-dev"
bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" } bevy_gltf = { path = "../bevy_gltf", optional = true, version = "0.17.0-dev" }
bevy_feathers = { path = "../bevy_feathers", optional = true, version = "0.17.0-dev" } bevy_feathers = { path = "../bevy_feathers", optional = true, version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" } bevy_image = { path = "../bevy_image", optional = true, version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", optional = true, version = "0.17.0-dev" }
bevy_camera = { path = "../bevy_camera", optional = true, version = "0.17.0-dev" }
bevy_light = { path = "../bevy_light", optional = true, version = "0.17.0-dev" }
bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [ bevy_input_focus = { path = "../bevy_input_focus", optional = true, version = "0.17.0-dev", default-features = false, features = [
"bevy_reflect", "bevy_reflect",
] } ] }

View File

@ -25,6 +25,8 @@ pub use bevy_app as app;
pub use bevy_asset as asset; pub use bevy_asset as asset;
#[cfg(feature = "bevy_audio")] #[cfg(feature = "bevy_audio")]
pub use bevy_audio as audio; pub use bevy_audio as audio;
#[cfg(feature = "bevy_camera")]
pub use bevy_camera as camera;
#[cfg(feature = "bevy_color")] #[cfg(feature = "bevy_color")]
pub use bevy_color as color; pub use bevy_color as color;
#[cfg(feature = "bevy_core_pipeline")] #[cfg(feature = "bevy_core_pipeline")]
@ -48,9 +50,13 @@ pub use bevy_image as image;
pub use bevy_input as input; pub use bevy_input as input;
#[cfg(feature = "bevy_input_focus")] #[cfg(feature = "bevy_input_focus")]
pub use bevy_input_focus as input_focus; pub use bevy_input_focus as input_focus;
#[cfg(feature = "bevy_light")]
pub use bevy_light as light;
#[cfg(feature = "bevy_log")] #[cfg(feature = "bevy_log")]
pub use bevy_log as log; pub use bevy_log as log;
pub use bevy_math as math; pub use bevy_math as math;
#[cfg(feature = "bevy_mesh")]
pub use bevy_mesh as mesh;
#[cfg(feature = "bevy_pbr")] #[cfg(feature = "bevy_pbr")]
pub use bevy_pbr as pbr; pub use bevy_pbr as pbr;
#[cfg(feature = "bevy_picking")] #[cfg(feature = "bevy_picking")]

View File

@ -0,0 +1,42 @@
[package]
name = "bevy_light"
version = "0.17.0-dev"
edition = "2024"
description = "Keeps the lights on at Bevy Engine"
homepage = "https://bevy.org"
repository = "https://github.com/bevyengine/bevy"
license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[dependencies]
# bevy
bevy_app = { path = "../bevy_app", version = "0.17.0-dev" }
bevy_asset = { path = "../bevy_asset", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev" }
bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [
"serialize",
] }
# other
tracing = { version = "0.1", default-features = false }
[features]
default = []
experimental_pbr_pcss = []
webgl = []
webgpu = []
[lints]
workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["-Zunstable-options", "--generate-link-to-definition"]
all-features = true

View File

@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@ -0,0 +1,19 @@
MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -2,7 +2,6 @@ use bevy_camera::Camera;
use bevy_color::Color; use bevy_color::Color;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_render::{extract_component::ExtractComponent, extract_resource::ExtractResource};
/// An ambient light, which lights the entire scene equally. /// An ambient light, which lights the entire scene equally.
/// ///
@ -16,14 +15,14 @@ use bevy_render::{extract_component::ExtractComponent, extract_resource::Extract
/// ///
/// ``` /// ```
/// # use bevy_ecs::system::ResMut; /// # use bevy_ecs::system::ResMut;
/// # use bevy_pbr::AmbientLight; /// # use bevy_light::AmbientLight;
/// fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) { /// fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
/// ambient_light.brightness = 100.0; /// ambient_light.brightness = 100.0;
/// } /// }
/// ``` /// ```
/// ///
/// [`LightPlugin`]: crate::LightPlugin /// [`LightPlugin`]: crate::LightPlugin
#[derive(Resource, Component, Clone, Debug, ExtractResource, ExtractComponent, Reflect)] #[derive(Resource, Component, Clone, Debug, Reflect)]
#[reflect(Resource, Component, Debug, Default, Clone)] #[reflect(Resource, Component, Debug, Default, Clone)]
#[require(Camera)] #[require(Camera)]
pub struct AmbientLight { pub struct AmbientLight {

View File

@ -11,8 +11,8 @@ use crate::{DirectionalLight, DirectionalLightShadowMap};
/// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance. /// Prefer using [`CascadeShadowConfigBuilder`] to construct an instance.
/// ///
/// ``` /// ```
/// # use bevy_pbr::CascadeShadowConfig; /// # use bevy_light::CascadeShadowConfig;
/// # use bevy_pbr::CascadeShadowConfigBuilder; /// # use bevy_light::CascadeShadowConfigBuilder;
/// # use bevy_utils::default; /// # use bevy_utils::default;
/// # /// #
/// let config: CascadeShadowConfig = CascadeShadowConfigBuilder { /// let config: CascadeShadowConfig = CascadeShadowConfigBuilder {

View File

@ -22,10 +22,7 @@ use super::{
ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings, ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings,
GlobalVisibleClusterableObjects, VisibleClusterableObjects, GlobalVisibleClusterableObjects, VisibleClusterableObjects,
}; };
use crate::{ use crate::{EnvironmentMapLight, LightProbe, PointLight, SpotLight, VolumetricLight};
prelude::EnvironmentMapLight, ExtractedPointLight, LightProbe, PointLight, SpotLight,
VolumetricLight,
};
const NDC_MIN: Vec2 = Vec2::NEG_ONE; const NDC_MIN: Vec2 = Vec2::NEG_ONE;
const NDC_MAX: Vec2 = Vec2::ONE; const NDC_MAX: Vec2 = Vec2::ONE;
@ -57,7 +54,7 @@ impl ClusterableObjectAssignmentData {
/// Data needed to assign objects to clusters that's specific to the type of /// Data needed to assign objects to clusters that's specific to the type of
/// clusterable object. /// clusterable object.
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub(crate) enum ClusterableObjectType { pub enum ClusterableObjectType {
/// Data needed to assign point lights to clusters. /// Data needed to assign point lights to clusters.
PointLight { PointLight {
/// Whether shadows are enabled for this point light. /// Whether shadows are enabled for this point light.
@ -105,7 +102,7 @@ impl ClusterableObjectType {
/// Generally, we sort first by type, then, for lights, by whether shadows /// Generally, we sort first by type, then, for lights, by whether shadows
/// are enabled (enabled before disabled), and then whether volumetrics are /// are enabled (enabled before disabled), and then whether volumetrics are
/// enabled (enabled before disabled). /// enabled (enabled before disabled).
pub(crate) fn ordering(&self) -> (u8, bool, bool) { pub fn ordering(&self) -> (u8, bool, bool) {
match *self { match *self {
ClusterableObjectType::PointLight { ClusterableObjectType::PointLight {
shadows_enabled, shadows_enabled,
@ -121,23 +118,6 @@ impl ClusterableObjectType {
ClusterableObjectType::Decal => (4, false, false), ClusterableObjectType::Decal => (4, false, false),
} }
} }
/// Creates the [`ClusterableObjectType`] data for a point or spot light.
pub(crate) fn from_point_or_spot_light(
point_light: &ExtractedPointLight,
) -> ClusterableObjectType {
match point_light.spot_light_angles {
Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
outer_angle,
shadows_enabled: point_light.shadows_enabled,
volumetric: point_light.volumetric,
},
None => ClusterableObjectType::PointLight {
shadows_enabled: point_light.shadows_enabled,
volumetric: point_light.volumetric,
},
}
}
} }
// NOTE: Run this before update_point_light_frusta! // NOTE: Run this before update_point_light_frusta!

View File

@ -17,15 +17,10 @@ use bevy_image::Image;
use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _}; use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
use bevy_platform::collections::HashSet; use bevy_platform::collections::HashSet;
use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::extract_component::ExtractComponent;
use bevy_transform::components::Transform; use bevy_transform::components::Transform;
use tracing::warn; use tracing::warn;
pub(crate) use crate::cluster::assign::assign_objects_to_clusters; pub mod assign;
pub(crate) mod assign;
mod extract_and_prepare;
pub use extract_and_prepare::*;
#[cfg(test)] #[cfg(test)]
mod test; mod test;
@ -106,14 +101,14 @@ pub enum ClusterConfig {
#[derive(Component, Debug, Default)] #[derive(Component, Debug, Default)]
pub struct Clusters { pub struct Clusters {
/// Tile size /// Tile size
pub(crate) tile_size: UVec2, pub tile_size: UVec2,
/// Number of clusters in `X` / `Y` / `Z` in the view frustum /// Number of clusters in `X` / `Y` / `Z` in the view frustum
pub(crate) dimensions: UVec3, pub dimensions: UVec3,
/// Distance to the far plane of the first depth slice. The first depth slice is special /// Distance to the far plane of the first depth slice. The first depth slice is special
/// and explicitly-configured to avoid having unnecessarily many slices close to the camera. /// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
pub(crate) near: f32, pub near: f32,
pub(crate) far: f32, pub far: f32,
pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>, pub clusterable_objects: Vec<VisibleClusterableObjects>,
} }
/// The [`VisibilityClass`] used for clusterables (decals, point lights, directional lights, and spot lights). /// The [`VisibilityClass`] used for clusterables (decals, point lights, directional lights, and spot lights).
@ -123,8 +118,8 @@ pub struct ClusterVisibilityClass;
#[derive(Clone, Component, Debug, Default)] #[derive(Clone, Component, Debug, Default)]
pub struct VisibleClusterableObjects { pub struct VisibleClusterableObjects {
pub(crate) entities: Vec<Entity>, pub entities: Vec<Entity>,
counts: ClusterableObjectCounts, pub counts: ClusterableObjectCounts,
} }
#[derive(Resource, Default)] #[derive(Resource, Default)]
@ -137,17 +132,17 @@ pub struct GlobalVisibleClusterableObjects {
/// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if /// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if
/// fewer than 3 SSBOs are available, which usually means on WebGL 2. /// fewer than 3 SSBOs are available, which usually means on WebGL 2.
#[derive(Clone, Copy, Default, Debug)] #[derive(Clone, Copy, Default, Debug)]
struct ClusterableObjectCounts { pub struct ClusterableObjectCounts {
/// The number of point lights in the cluster. /// The number of point lights in the cluster.
point_lights: u32, pub point_lights: u32,
/// The number of spot lights in the cluster. /// The number of spot lights in the cluster.
spot_lights: u32, pub spot_lights: u32,
/// The number of reflection probes in the cluster. /// The number of reflection probes in the cluster.
reflection_probes: u32, pub reflection_probes: u32,
/// The number of irradiance volumes in the cluster. /// The number of irradiance volumes in the cluster.
irradiance_volumes: u32, pub irradiance_volumes: u32,
/// The number of decals in the cluster. /// The number of decals in the cluster.
decals: u32, pub decals: u32,
} }
/// An object that projects a decal onto surfaces within its bounds. /// An object that projects a decal onto surfaces within its bounds.
@ -160,7 +155,7 @@ struct ClusterableObjectCounts {
/// but they require bindless textures. This means that they presently can't be /// but they require bindless textures. This means that they presently can't be
/// used on WebGL 2, WebGPU, macOS, or iOS. Bevy's clustered decals can be used /// used on WebGL 2, WebGPU, macOS, or iOS. Bevy's clustered decals can be used
/// with forward or deferred rendering and don't require a prepass. /// with forward or deferred rendering and don't require a prepass.
#[derive(Component, Debug, Clone, Reflect, ExtractComponent)] #[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Debug, Clone)] #[reflect(Component, Debug, Clone)]
#[require(Transform, Visibility, VisibilityClass)] #[require(Transform, Visibility, VisibilityClass)]
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)] #[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]

View File

@ -1,6 +1,6 @@
use bevy_math::UVec2; use bevy_math::UVec2;
use crate::{ClusterConfig, Clusters}; use super::{ClusterConfig, Clusters};
fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters { fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters {
let dims = config.dimensions_for_screen_size(screen_size); let dims = config.dimensions_for_screen_size(screen_size);

View File

@ -10,8 +10,9 @@ use bevy_image::Image;
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_transform::components::Transform; use bevy_transform::components::Transform;
use super::{cascade::CascadeShadowConfig, light_consts, Cascades}; use super::{
use crate::cluster::ClusterVisibilityClass; cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,
};
/// A Directional light. /// A Directional light.
/// ///
@ -172,7 +173,7 @@ pub struct DirectionalLightTexture {
/// ///
/// ``` /// ```
/// # use bevy_app::prelude::*; /// # use bevy_app::prelude::*;
/// # use bevy_pbr::DirectionalLightShadowMap; /// # use bevy_light::DirectionalLightShadowMap;
/// App::new() /// App::new()
/// .insert_resource(DirectionalLightShadowMap { size: 4096 }); /// .insert_resource(DirectionalLightShadowMap { size: 4096 });
/// ``` /// ```

View File

@ -1,3 +1,5 @@
#![expect(missing_docs, reason = "Not all docs are written yet, see #3492.")]
use bevy_app::{App, Plugin, PostUpdate}; use bevy_app::{App, Plugin, PostUpdate};
use bevy_camera::{ use bevy_camera::{
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
@ -10,21 +12,27 @@ use bevy_camera::{
}; };
use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_ecs::{entity::EntityHashSet, prelude::*};
use bevy_math::Vec3A; use bevy_math::Vec3A;
use bevy_mesh::Mesh3d;
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_render::{extract_component::ExtractComponent, mesh::Mesh3d};
use bevy_transform::{components::GlobalTransform, TransformSystems}; use bevy_transform::{components::GlobalTransform, TransformSystems};
use bevy_utils::Parallel; use bevy_utils::Parallel;
use core::ops::DerefMut; use core::ops::DerefMut;
use crate::cluster::{add_clusters, assign_objects_to_clusters, VisibleClusterableObjects}; pub mod cluster;
pub use cluster::ClusteredDecal;
use cluster::{
add_clusters, assign::assign_objects_to_clusters, ClusterConfig,
GlobalVisibleClusterableObjects, VisibleClusterableObjects,
};
mod ambient_light; mod ambient_light;
pub use ambient_light::AmbientLight; pub use ambient_light::AmbientLight;
mod probe;
pub use probe::{EnvironmentMapLight, IrradianceVolume, LightProbe};
mod volumetric;
pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
pub mod cascade; pub mod cascade;
use cascade::{ use cascade::{build_directional_light_cascades, clear_directional_light_cascades};
build_directional_light_cascades, clear_directional_light_cascades, CascadeShadowConfig, pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
Cascades,
};
mod point_light; mod point_light;
pub use point_light::{ pub use point_light::{
update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture, update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
@ -111,9 +119,16 @@ impl Plugin for LightPlugin {
.register_type::<NotShadowCaster>() .register_type::<NotShadowCaster>()
.register_type::<NotShadowReceiver>() .register_type::<NotShadowReceiver>()
.register_type::<PointLight>() .register_type::<PointLight>()
.register_type::<LightProbe>()
.register_type::<EnvironmentMapLight>()
.register_type::<IrradianceVolume>()
.register_type::<VolumetricFog>()
.register_type::<VolumetricLight>()
.register_type::<PointLightShadowMap>() .register_type::<PointLightShadowMap>()
.register_type::<SpotLight>() .register_type::<SpotLight>()
.register_type::<ShadowFilteringMethod>() .register_type::<ShadowFilteringMethod>()
.register_type::<ClusterConfig>()
.init_resource::<GlobalVisibleClusterableObjects>()
.init_resource::<AmbientLight>() .init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>() .init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>() .init_resource::<PointLightShadowMap>()
@ -182,7 +197,7 @@ impl Plugin for LightPlugin {
} }
/// A convenient alias for `Or<(With<PointLight>, With<SpotLight>, /// A convenient alias for `Or<(With<PointLight>, With<SpotLight>,
/// With<DirectionalLight>)>`, for use with [`bevy_render::view::VisibleEntities`]. /// With<DirectionalLight>)>`, for use with [`bevy_camera::visibility::VisibleEntities`].
pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>; pub type WithLight = Or<(With<PointLight>, With<SpotLight>, With<DirectionalLight>)>;
/// Add this component to make a [`Mesh3d`] not cast shadows. /// Add this component to make a [`Mesh3d`] not cast shadows.
@ -197,10 +212,10 @@ pub struct NotShadowCaster;
#[derive(Debug, Component, Reflect, Default)] #[derive(Debug, Component, Reflect, Default)]
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
pub struct NotShadowReceiver; pub struct NotShadowReceiver;
/// Add this component to make a [`Mesh3d`] using a PBR material with [`diffuse_transmission`](crate::pbr_material::StandardMaterial::diffuse_transmission)`> 0.0` /// Add this component to make a [`Mesh3d`] using a PBR material with `StandardMaterial::diffuse_transmission > 0.0`
/// receive shadows on its diffuse transmission lobe. (i.e. its “backside”) /// receive shadows on its diffuse transmission lobe. (i.e. its “backside”)
/// ///
/// Not enabled by default, as it requires carefully setting up [`thickness`](crate::pbr_material::StandardMaterial::thickness) /// Not enabled by default, as it requires carefully setting up `StandardMaterial::thickness`
/// (and potentially even baking a thickness texture!) to match the geometry of the mesh, in order to avoid self-shadow artifacts. /// (and potentially even baking a thickness texture!) to match the geometry of the mesh, in order to avoid self-shadow artifacts.
/// ///
/// **Note:** Using [`NotShadowReceiver`] overrides this component. /// **Note:** Using [`NotShadowReceiver`] overrides this component.
@ -208,12 +223,12 @@ pub struct NotShadowReceiver;
#[reflect(Component, Default, Debug)] #[reflect(Component, Default, Debug)]
pub struct TransmittedShadowReceiver; pub struct TransmittedShadowReceiver;
/// Add this component to a [`Camera3d`](bevy_core_pipeline::core_3d::Camera3d) /// Add this component to a [`Camera3d`](bevy_camera::Camera3d)
/// to control how to anti-alias shadow edges. /// to control how to anti-alias shadow edges.
/// ///
/// The different modes use different approaches to /// The different modes use different approaches to
/// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing). /// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing).
#[derive(Debug, Component, ExtractComponent, Reflect, Clone, Copy, PartialEq, Eq, Default)] #[derive(Debug, Component, Reflect, Clone, Copy, PartialEq, Eq, Default)]
#[reflect(Component, Default, Debug, PartialEq, Clone)] #[reflect(Component, Default, Debug, PartialEq, Clone)]
pub enum ShadowFilteringMethod { pub enum ShadowFilteringMethod {
/// Hardware 2x2. /// Hardware 2x2.

View File

@ -166,7 +166,7 @@ pub struct PointLightTexture {
/// ///
/// ``` /// ```
/// # use bevy_app::prelude::*; /// # use bevy_app::prelude::*;
/// # use bevy_pbr::PointLightShadowMap; /// # use bevy_light::PointLightShadowMap;
/// App::new() /// App::new()
/// .insert_resource(PointLightShadowMap { size: 2048 }); /// .insert_resource(PointLightShadowMap { size: 2048 });
/// ``` /// ```

View File

@ -0,0 +1,156 @@
use bevy_asset::Handle;
use bevy_camera::visibility::Visibility;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::Quat;
use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
/// A marker component for a light probe, which is a cuboid region that provides
/// global illumination to all fragments inside it.
///
/// Note that a light probe will have no effect unless the entity contains some
/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
/// `IrradianceVolume`.
///
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
/// origin. The [`Transform`] applied to this entity can scale, rotate, or translate
/// that cube so that it contains all fragments that should take this light probe into account.
///
/// When multiple sources of indirect illumination can be applied to a fragment,
/// the highest-quality one is chosen. Diffuse and specular illumination are
/// considered separately, so, for example, Bevy may decide to sample the
/// diffuse illumination from an irradiance volume and the specular illumination
/// from a reflection probe. From highest priority to lowest priority, the
/// ranking is as follows:
///
/// | Rank | Diffuse | Specular |
/// | ---- | -------------------- | -------------------- |
/// | 1 | Lightmap | Lightmap |
/// | 2 | Irradiance volume | Reflection probe |
/// | 3 | Reflection probe | View environment map |
/// | 4 | View environment map | |
///
/// Note that ambient light is always added to the diffuse component and does
/// not participate in the ranking. That is, ambient light is applied in
/// addition to, not instead of, the light sources above.
///
/// A terminology note: Unfortunately, there is little agreement across game and
/// graphics engines as to what to call the various techniques that Bevy groups
/// under the term *light probe*. In Bevy, a *light probe* is the generic term
/// that encompasses both *reflection probes* and *irradiance volumes*. In
/// object-oriented terms, *light probe* is the superclass, and *reflection
/// probe* and *irradiance volume* are subclasses. In other engines, you may see
/// the term *light probe* refer to an irradiance volume with a single voxel, or
/// perhaps some other technique, while in Bevy *light probe* refers not to a
/// specific technique but rather to a class of techniques. Developers familiar
/// with other engines should be aware of this terminology difference.
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(Transform, Visibility)]
pub struct LightProbe;
impl LightProbe {
/// Creates a new light probe component.
#[inline]
pub fn new() -> Self {
Self
}
}
/// A pair of cubemap textures that represent the surroundings of a specific
/// area in space.
///
/// See `bevy_pbr::environment_map` for detailed information.
#[derive(Clone, Component, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct EnvironmentMapLight {
/// The blurry image that represents diffuse radiance surrounding a region.
pub diffuse_map: Handle<Image>,
/// The typically-sharper, mipmapped image that represents specular radiance
/// surrounding a region.
pub specular_map: Handle<Image>,
/// Scale factor applied to the diffuse and specular light generated by this component.
///
/// After applying this multiplier, the resulting values should
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
///
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
pub intensity: f32,
/// World space rotation applied to the environment light cubemaps.
/// This is useful for users who require a different axis, such as the Z-axis, to serve
/// as the vertical axis.
pub rotation: Quat,
/// Whether the light from this environment map contributes diffuse lighting
/// to meshes with lightmaps.
///
/// Set this to false if your lightmap baking tool bakes the diffuse light
/// from this environment light into the lightmaps in order to avoid
/// counting the radiance from this environment map twice.
///
/// By default, this is set to true.
pub affects_lightmapped_mesh_diffuse: bool,
}
impl Default for EnvironmentMapLight {
fn default() -> Self {
EnvironmentMapLight {
diffuse_map: Handle::default(),
specular_map: Handle::default(),
intensity: 0.0,
rotation: Quat::IDENTITY,
affects_lightmapped_mesh_diffuse: true,
}
}
}
/// The component that defines an irradiance volume.
///
/// See `bevy_pbr::irradiance_volume` for detailed information.
///
/// This component requires the [`LightProbe`] component, and is typically used with
/// [`bevy_transform::components::Transform`] to place the volume appropriately.
#[derive(Clone, Reflect, Component, Debug)]
#[reflect(Component, Default, Debug, Clone)]
#[require(LightProbe)]
pub struct IrradianceVolume {
/// The 3D texture that represents the ambient cubes, encoded in the format
/// described in `bevy_pbr::irradiance_volume`.
pub voxels: Handle<Image>,
/// Scale factor applied to the diffuse and specular light generated by this component.
///
/// After applying this multiplier, the resulting values should
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
///
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
pub intensity: f32,
/// Whether the light from this irradiance volume has an effect on meshes
/// with lightmaps.
///
/// Set this to false if your lightmap baking tool bakes the light from this
/// irradiance volume into the lightmaps in order to avoid counting the
/// irradiance twice. Frequently, applications use irradiance volumes as a
/// lower-quality alternative to lightmaps for capturing indirect
/// illumination on dynamic objects, and such applications will want to set
/// this value to false.
///
/// By default, this is set to true.
pub affects_lightmapped_meshes: bool,
}
impl Default for IrradianceVolume {
#[inline]
fn default() -> Self {
IrradianceVolume {
voxels: Handle::default(),
intensity: 0.0,
affects_lightmapped_meshes: true,
}
}
}

View File

@ -1,14 +1,13 @@
use bevy_asset::Handle; use bevy_asset::Handle;
use bevy_camera::{ use bevy_camera::{
primitives::Frustum, primitives::Frustum,
visibility::{self, Visibility, VisibilityClass}, visibility::{self, Visibility, VisibilityClass, VisibleMeshEntities},
}; };
use bevy_color::Color; use bevy_color::Color;
use bevy_ecs::prelude::*; use bevy_ecs::prelude::*;
use bevy_image::Image; use bevy_image::Image;
use bevy_math::{Mat4, Vec4}; use bevy_math::{Mat4, Vec4};
use bevy_reflect::prelude::*; use bevy_reflect::prelude::*;
use bevy_render::view::VisibleMeshEntities;
use bevy_transform::components::{GlobalTransform, Transform}; use bevy_transform::components::{GlobalTransform, Transform};
use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects}; use crate::cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects};

View File

@ -0,0 +1,157 @@
use bevy_asset::Handle;
use bevy_camera::visibility::Visibility;
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::Vec3;
use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
/// (`shadows_enabled: true`) to make volumetric fog interact with it.
///
/// This allows the light to generate light shafts/god rays.
#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct VolumetricLight;
/// When placed on a [`bevy_camera::Camera3d`], enables
/// volumetric fog and volumetric lighting, also known as light shafts or god
/// rays.
#[derive(Clone, Copy, Component, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct VolumetricFog {
/// Color of the ambient light.
///
/// This is separate from Bevy's [`AmbientLight`](crate::AmbientLight) because an
/// [`EnvironmentMapLight`](crate::EnvironmentMapLight) is
/// still considered an ambient light for the purposes of volumetric fog. If you're using a
/// [`EnvironmentMapLight`](crate::EnvironmentMapLight), for best results,
/// this should be a good approximation of the average color of the environment map.
///
/// Defaults to white.
pub ambient_color: Color,
/// The brightness of the ambient light.
///
/// If there's no [`EnvironmentMapLight`](crate::EnvironmentMapLight),
/// set this to 0.
///
/// Defaults to 0.1.
pub ambient_intensity: f32,
/// The maximum distance to offset the ray origin randomly by, in meters.
///
/// This is intended for use with temporal antialiasing. It helps fog look
/// less blocky by varying the start position of the ray, using interleaved
/// gradient noise.
pub jitter: f32,
/// The number of raymarching steps to perform.
///
/// Higher values produce higher-quality results with less banding, but
/// reduce performance.
///
/// The default value is 64.
pub step_count: u32,
}
impl Default for VolumetricFog {
fn default() -> Self {
Self {
step_count: 64,
// Matches `AmbientLight` defaults.
ambient_color: Color::WHITE,
ambient_intensity: 0.1,
jitter: 0.0,
}
}
}
#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(Transform, Visibility)]
pub struct FogVolume {
/// The color of the fog.
///
/// Note that the fog must be lit by a [`VolumetricLight`] or ambient light
/// in order for this color to appear.
///
/// Defaults to white.
pub fog_color: Color,
/// The density of fog, which measures how dark the fog is.
///
/// The default value is 0.1.
pub density_factor: f32,
/// Optional 3D voxel density texture for the fog.
pub density_texture: Option<Handle<Image>>,
/// Configurable offset of the density texture in UVW coordinates.
///
/// This can be used to scroll a repeating density texture in a direction over time
/// to create effects like fog moving in the wind. Make sure to configure the texture
/// to use `ImageAddressMode::Repeat` if this is your intention.
///
/// Has no effect when no density texture is present.
///
/// The default value is (0, 0, 0).
pub density_texture_offset: Vec3,
/// The absorption coefficient, which measures what fraction of light is
/// absorbed by the fog at each step.
///
/// Increasing this value makes the fog darker.
///
/// The default value is 0.3.
pub absorption: f32,
/// The scattering coefficient, which measures the fraction of light that's
/// scattered toward, and away from, the viewer.
///
/// The default value is 0.3.
pub scattering: f32,
/// Measures the fraction of light that's scattered *toward* the camera, as
/// opposed to *away* from the camera.
///
/// Increasing this value makes light shafts become more prominent when the
/// camera is facing toward their source and less prominent when the camera
/// is facing away. Essentially, a high value here means the light shafts
/// will fade into view as the camera focuses on them and fade away when the
/// camera is pointing away.
///
/// The default value is 0.8.
pub scattering_asymmetry: f32,
/// Applies a nonphysical color to the light.
///
/// This can be useful for artistic purposes but is nonphysical.
///
/// The default value is white.
pub light_tint: Color,
/// Scales the light by a fixed fraction.
///
/// This can be useful for artistic purposes but is nonphysical.
///
/// The default value is 1.0, which results in no adjustment.
pub light_intensity: f32,
}
impl Default for FogVolume {
fn default() -> Self {
Self {
absorption: 0.3,
scattering: 0.3,
density_factor: 0.1,
density_texture: None,
density_texture_offset: Vec3::ZERO,
scattering_asymmetry: 0.5,
fog_color: Color::WHITE,
light_tint: Color::WHITE,
light_intensity: 1.0,
}
}
}

View File

@ -4,6 +4,7 @@ use crate::{
}; };
use core::f32::consts::FRAC_1_SQRT_2; use core::f32::consts::FRAC_1_SQRT_2;
use core::fmt;
use derive_more::derive::Into; use derive_more::derive::Into;
#[cfg(feature = "bevy_reflect")] #[cfg(feature = "bevy_reflect")]
@ -325,6 +326,12 @@ impl core::ops::Mul<Dir2> for Rot2 {
} }
} }
impl fmt::Display for Dir2 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(any(feature = "approx", test))] #[cfg(any(feature = "approx", test))]
impl approx::AbsDiffEq for Dir2 { impl approx::AbsDiffEq for Dir2 {
type Epsilon = f32; type Epsilon = f32;
@ -587,6 +594,12 @@ impl core::ops::Mul<Dir3> for Quat {
} }
} }
impl fmt::Display for Dir3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "approx")] #[cfg(feature = "approx")]
impl approx::AbsDiffEq for Dir3 { impl approx::AbsDiffEq for Dir3 {
type Epsilon = f32; type Epsilon = f32;
@ -834,6 +847,12 @@ impl core::ops::Mul<Dir3A> for Quat {
} }
} }
impl fmt::Display for Dir3A {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "approx")] #[cfg(feature = "approx")]
impl approx::AbsDiffEq for Dir3A { impl approx::AbsDiffEq for Dir3A {
type Epsilon = f32; type Epsilon = f32;
@ -1022,6 +1041,12 @@ impl core::ops::Mul<Dir4> for f32 {
} }
} }
impl fmt::Display for Dir4 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[cfg(feature = "approx")] #[cfg(feature = "approx")]
impl approx::AbsDiffEq for Dir4 { impl approx::AbsDiffEq for Dir4 {
type Epsilon = f32; type Epsilon = f32;

View File

@ -18,7 +18,6 @@ bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" } bevy_mikktspace = { path = "../bevy_mikktspace", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std", "std",
"serialize", "serialize",

View File

@ -9,19 +9,19 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"] keywords = ["bevy"]
[features] [features]
webgl = [] webgl = ["bevy_light/webgl"]
webgpu = [] webgpu = ["bevy_light/webgpu"]
pbr_transmission_textures = [] pbr_transmission_textures = []
pbr_multi_layer_material_textures = [] pbr_multi_layer_material_textures = []
pbr_anisotropy_texture = [] pbr_anisotropy_texture = []
experimental_pbr_pcss = [] experimental_pbr_pcss = ["bevy_light/experimental_pbr_pcss"]
pbr_specular_textures = [] pbr_specular_textures = []
pbr_clustered_decals = [] pbr_clustered_decals = []
pbr_light_textures = [] pbr_light_textures = []
shader_format_glsl = ["bevy_render/shader_format_glsl"] shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"] trace = ["bevy_render/trace"]
# Enables the meshlet renderer for dense high-poly scenes (experimental) # Enables the meshlet renderer for dense high-poly scenes (experimental)
meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:half", "dep:bevy_tasks"] meshlet = ["dep:lz4_flex", "dep:range-alloc", "dep:bevy_tasks"]
# Enables processing meshes into meshlet meshes # Enables processing meshes into meshlet meshes
meshlet_processor = [ meshlet_processor = [
"meshlet", "meshlet",
@ -40,15 +40,17 @@ bevy_core_pipeline = { path = "../bevy_core_pipeline", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" } bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" } bevy_diagnostic = { path = "../bevy_diagnostic", version = "0.17.0-dev" }
bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" } bevy_ecs = { path = "../bevy_ecs", version = "0.17.0-dev" }
bevy_light = { path = "../bevy_light", version = "0.17.0-dev" }
bevy_image = { path = "../bevy_image", version = "0.17.0-dev" } bevy_image = { path = "../bevy_image", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" } bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } bevy_render = { path = "../bevy_render", features = [
"bevy_light",
], version = "0.17.0-dev" }
bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" } bevy_camera = { path = "../bevy_camera", version = "0.17.0-dev" }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true } bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" } bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std", "std",
] } ] }
@ -63,14 +65,12 @@ lz4_flex = { version = "0.11", default-features = false, features = [
"frame", "frame",
], optional = true } ], optional = true }
range-alloc = { version = "0.1.3", optional = true } range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.4.1", optional = true } meshopt = { version = "0.4.1", optional = true }
metis = { version = "0.2", optional = true } metis = { version = "0.2", optional = true }
itertools = { version = "0.14", optional = true } itertools = { version = "0.14", optional = true }
bitvec = { version = "1", optional = true } bitvec = { version = "1", optional = true }
# direct dependency required for derive macro # direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive", "must_cast"] } bytemuck = { version = "1", features = ["derive", "must_cast"] }
radsort = "0.1"
smallvec = { version = "1", default-features = false } smallvec = { version = "1", default-features = false }
nonmax = "0.5" nonmax = "0.5"
static_assertions = "1" static_assertions = "1"

View File

@ -2,6 +2,7 @@ use core::num::NonZero;
use bevy_camera::Camera; use bevy_camera::Camera;
use bevy_ecs::{entity::EntityHashMap, prelude::*}; use bevy_ecs::{entity::EntityHashMap, prelude::*};
use bevy_light::cluster::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
use bevy_math::{uvec4, UVec3, UVec4, Vec4}; use bevy_math::{uvec4, UVec3, UVec4, Vec4};
use bevy_render::{ use bevy_render::{
render_resource::{ render_resource::{
@ -13,7 +14,6 @@ use bevy_render::{
}; };
use tracing::warn; use tracing::warn;
use super::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
use crate::MeshPipeline; use crate::MeshPipeline;
// NOTE: this must be kept in sync with the same constants in // NOTE: this must be kept in sync with the same constants in

View File

@ -27,6 +27,8 @@ use bevy_ecs::{
system::{Query, Res, ResMut}, system::{Query, Res, ResMut},
}; };
use bevy_image::Image; use bevy_image::Image;
pub use bevy_light::cluster::ClusteredDecal;
use bevy_light::{DirectionalLightTexture, PointLightTexture, SpotLightTexture};
use bevy_math::Mat4; use bevy_math::Mat4;
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
pub use bevy_render::primitives::CubemapLayout; pub use bevy_render::primitives::CubemapLayout;
@ -47,9 +49,7 @@ use bevy_render::{
use bevy_transform::components::GlobalTransform; use bevy_transform::components::GlobalTransform;
use bytemuck::{Pod, Zeroable}; use bytemuck::{Pod, Zeroable};
pub use crate::ClusteredDecal;
use crate::{binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta}; use crate::{binding_arrays_are_usable, prepare_lights, GlobalClusterableObjectMeta};
pub use crate::{DirectionalLightTexture, PointLightTexture, SpotLightTexture};
/// The maximum number of decals that can be present in a view. /// The maximum number of decals that can be present in a view.
/// ///

View File

@ -1,14 +1,11 @@
use crate::{ use crate::{
graph::NodePbr, irradiance_volume::IrradianceVolume, prelude::EnvironmentMapLight, graph::NodePbr, irradiance_volume::IrradianceVolume, MeshPipeline, MeshViewBindGroup,
MeshPipeline, MeshViewBindGroup, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, RenderViewLightProbes, ScreenSpaceAmbientOcclusion, ScreenSpaceReflectionsUniform,
ScreenSpaceReflectionsUniform, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset, ViewEnvironmentMapUniformOffset, ViewLightProbesUniformOffset,
ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX, ViewScreenSpaceReflectionsUniformOffset, TONEMAPPING_LUT_SAMPLER_BINDING_INDEX,
TONEMAPPING_LUT_TEXTURE_BINDING_INDEX, TONEMAPPING_LUT_TEXTURE_BINDING_INDEX,
}; };
use crate::{ use crate::{DistanceFog, MeshPipelineKey, ViewFogUniformOffset, ViewLightsUniformOffset};
DistanceFog, MeshPipelineKey, ShadowFilteringMethod, ViewFogUniformOffset,
ViewLightsUniformOffset,
};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_asset::{embedded_asset, load_embedded_asset, Handle};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
@ -21,6 +18,7 @@ use bevy_core_pipeline::{
}; };
use bevy_ecs::{prelude::*, query::QueryItem}; use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_image::BevyDefault as _; use bevy_image::BevyDefault as _;
use bevy_light::{EnvironmentMapLight, ShadowFilteringMethod};
use bevy_render::{ use bevy_render::{
extract_component::{ extract_component::{
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin, ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,

View File

@ -31,7 +31,6 @@ pub mod decal;
pub mod deferred; pub mod deferred;
mod extended_material; mod extended_material;
mod fog; mod fog;
mod light;
mod light_probe; mod light_probe;
mod lightmap; mod lightmap;
mod material; mod material;
@ -48,12 +47,19 @@ mod volumetric_fog;
use bevy_color::{Color, LinearRgba}; use bevy_color::{Color, LinearRgba};
pub use atmosphere::*; pub use atmosphere::*;
use bevy_light::SimulationLightSystems;
pub use bevy_light::{
light_consts, AmbientLight, CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades,
ClusteredDecal, DirectionalLight, DirectionalLightShadowMap, DirectionalLightTexture,
FogVolume, IrradianceVolume, LightPlugin, LightProbe, NotShadowCaster, NotShadowReceiver,
PointLight, PointLightShadowMap, PointLightTexture, ShadowFilteringMethod, SpotLight,
SpotLightTexture, TransmittedShadowReceiver, VolumetricFog, VolumetricLight,
};
pub use cluster::*; pub use cluster::*;
pub use components::*; pub use components::*;
pub use decal::clustered::ClusteredDecalPlugin; pub use decal::clustered::ClusteredDecalPlugin;
pub use extended_material::*; pub use extended_material::*;
pub use fog::*; pub use fog::*;
pub use light::*;
pub use light_probe::*; pub use light_probe::*;
pub use lightmap::*; pub use lightmap::*;
pub use material::*; pub use material::*;
@ -65,7 +71,7 @@ pub use prepass::*;
pub use render::*; pub use render::*;
pub use ssao::*; pub use ssao::*;
pub use ssr::*; pub use ssr::*;
pub use volumetric_fog::{FogVolume, VolumetricFog, VolumetricFogPlugin, VolumetricLight}; pub use volumetric_fog::VolumetricFogPlugin;
/// The PBR prelude. /// The PBR prelude.
/// ///
@ -74,14 +80,17 @@ pub mod prelude {
#[doc(hidden)] #[doc(hidden)]
pub use crate::{ pub use crate::{
fog::{DistanceFog, FogFalloff}, fog::{DistanceFog, FogFalloff},
light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight},
light_probe::{environment_map::EnvironmentMapLight, LightProbe},
material::{Material, MaterialPlugin}, material::{Material, MaterialPlugin},
mesh_material::MeshMaterial3d, mesh_material::MeshMaterial3d,
parallax::ParallaxMappingMethod, parallax::ParallaxMappingMethod,
pbr_material::StandardMaterial, pbr_material::StandardMaterial,
ssao::ScreenSpaceAmbientOcclusionPlugin, ssao::ScreenSpaceAmbientOcclusionPlugin,
}; };
#[doc(hidden)]
pub use bevy_light::{
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight, LightProbe, PointLight,
SpotLight,
};
} }
pub mod graph { pub mod graph {
@ -122,7 +131,6 @@ pub mod graph {
} }
} }
pub use crate::cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr}; use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr};
use bevy_app::prelude::*; use bevy_app::prelude::*;
use bevy_asset::{AssetApp, AssetPath, Assets, Handle}; use bevy_asset::{AssetApp, AssetPath, Assets, Handle};
@ -203,8 +211,6 @@ impl Plugin for PbrPlugin {
load_shader_library!(app, "meshlet/dummy_visibility_buffer_resolve.wgsl"); load_shader_library!(app, "meshlet/dummy_visibility_buffer_resolve.wgsl");
app.register_asset_reflect::<StandardMaterial>() app.register_asset_reflect::<StandardMaterial>()
.register_type::<ClusterConfig>()
.init_resource::<GlobalVisibleClusterableObjects>()
.register_type::<DefaultOpaqueRendererMethod>() .register_type::<DefaultOpaqueRendererMethod>()
.init_resource::<DefaultOpaqueRendererMethod>() .init_resource::<DefaultOpaqueRendererMethod>()
.add_plugins(( .add_plugins((

View File

@ -44,13 +44,10 @@
//! //!
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments //! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
use bevy_asset::{AssetId, Handle}; use bevy_asset::AssetId;
use bevy_ecs::{ use bevy_ecs::{query::QueryItem, system::lifetimeless::Read};
component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read,
};
use bevy_image::Image; use bevy_image::Image;
use bevy_math::Quat; use bevy_light::EnvironmentMapLight;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
extract_instances::ExtractInstance, extract_instances::ExtractInstance,
render_asset::RenderAssets, render_asset::RenderAssets,
@ -72,56 +69,6 @@ use crate::{
use super::{LightProbeComponent, RenderViewLightProbes}; use super::{LightProbeComponent, RenderViewLightProbes};
/// A pair of cubemap textures that represent the surroundings of a specific
/// area in space.
///
/// See [`crate::environment_map`] for detailed information.
#[derive(Clone, Component, Reflect)]
#[reflect(Component, Default, Clone)]
pub struct EnvironmentMapLight {
/// The blurry image that represents diffuse radiance surrounding a region.
pub diffuse_map: Handle<Image>,
/// The typically-sharper, mipmapped image that represents specular radiance
/// surrounding a region.
pub specular_map: Handle<Image>,
/// Scale factor applied to the diffuse and specular light generated by this component.
///
/// After applying this multiplier, the resulting values should
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
///
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
pub intensity: f32,
/// World space rotation applied to the environment light cubemaps.
/// This is useful for users who require a different axis, such as the Z-axis, to serve
/// as the vertical axis.
pub rotation: Quat,
/// Whether the light from this environment map contributes diffuse lighting
/// to meshes with lightmaps.
///
/// Set this to false if your lightmap baking tool bakes the diffuse light
/// from this environment light into the lightmaps in order to avoid
/// counting the radiance from this environment map twice.
///
/// By default, this is set to true.
pub affects_lightmapped_mesh_diffuse: bool,
}
impl Default for EnvironmentMapLight {
fn default() -> Self {
EnvironmentMapLight {
diffuse_map: Handle::default(),
specular_map: Handle::default(),
intensity: 0.0,
rotation: Quat::IDENTITY,
affects_lightmapped_mesh_diffuse: true,
}
}
}
/// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles. /// Like [`EnvironmentMapLight`], but contains asset IDs instead of handles.
/// ///
/// This is for use in the render app. /// This is for use in the render app.

View File

@ -133,8 +133,8 @@
//! //!
//! [Why ambient cubes?]: #why-ambient-cubes //! [Why ambient cubes?]: #why-ambient-cubes
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_image::Image; use bevy_image::Image;
pub use bevy_light::IrradianceVolume;
use bevy_render::{ use bevy_render::{
render_asset::RenderAssets, render_asset::RenderAssets,
render_resource::{ render_resource::{
@ -144,71 +144,22 @@ use bevy_render::{
renderer::{RenderAdapter, RenderDevice}, renderer::{RenderAdapter, RenderDevice},
texture::{FallbackImage, GpuImage}, texture::{FallbackImage, GpuImage},
}; };
use bevy_utils::default;
use core::{num::NonZero, ops::Deref}; use core::{num::NonZero, ops::Deref};
use bevy_asset::{AssetId, Handle}; use bevy_asset::AssetId;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use crate::{ use crate::{
add_cubemap_texture_view, binding_arrays_are_usable, RenderViewLightProbes, add_cubemap_texture_view, binding_arrays_are_usable, RenderViewLightProbes,
MAX_VIEW_LIGHT_PROBES, MAX_VIEW_LIGHT_PROBES,
}; };
use super::{LightProbe, LightProbeComponent}; use super::LightProbeComponent;
/// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can /// On WebGL and WebGPU, we must disable irradiance volumes, as otherwise we can
/// overflow the number of texture bindings when deferred rendering is in use /// overflow the number of texture bindings when deferred rendering is in use
/// (see issue #11885). /// (see issue #11885).
pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "wasm32")); pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "wasm32"));
/// The component that defines an irradiance volume.
///
/// See [`crate::irradiance_volume`] for detailed information.
///
/// This component requires the [`LightProbe`] component, and is typically used with
/// [`bevy_transform::components::Transform`] to place the volume appropriately.
#[derive(Clone, Reflect, Component, Debug)]
#[reflect(Component, Default, Debug, Clone)]
#[require(LightProbe)]
pub struct IrradianceVolume {
/// The 3D texture that represents the ambient cubes, encoded in the format
/// described in [`crate::irradiance_volume`].
pub voxels: Handle<Image>,
/// Scale factor applied to the diffuse and specular light generated by this component.
///
/// After applying this multiplier, the resulting values should
/// be in units of [cd/m^2](https://en.wikipedia.org/wiki/Candela_per_square_metre).
///
/// See also <https://google.github.io/filament/Filament.html#lighting/imagebasedlights/iblunit>.
pub intensity: f32,
/// Whether the light from this irradiance volume has an effect on meshes
/// with lightmaps.
///
/// Set this to false if your lightmap baking tool bakes the light from this
/// irradiance volume into the lightmaps in order to avoid counting the
/// irradiance twice. Frequently, applications use irradiance volumes as a
/// lower-quality alternative to lightmaps for capturing indirect
/// illumination on dynamic objects, and such applications will want to set
/// this value to false.
///
/// By default, this is set to true.
pub affects_lightmapped_meshes: bool,
}
impl Default for IrradianceVolume {
#[inline]
fn default() -> Self {
IrradianceVolume {
voxels: default(),
intensity: 0.0,
affects_lightmapped_meshes: true,
}
}
}
/// All the bind group entries necessary for PBR shaders to access the /// All the bind group entries necessary for PBR shaders to access the
/// irradiance volumes exposed to a view. /// irradiance volumes exposed to a view.
pub(crate) enum RenderViewIrradianceVolumeBindGroupEntries<'a> { pub(crate) enum RenderViewIrradianceVolumeBindGroupEntries<'a> {

View File

@ -8,15 +8,14 @@ use bevy_ecs::{
component::Component, component::Component,
entity::Entity, entity::Entity,
query::With, query::With,
reflect::ReflectComponent,
resource::Resource, resource::Resource,
schedule::IntoScheduleConfigs, schedule::IntoScheduleConfigs,
system::{Commands, Local, Query, Res, ResMut}, system::{Commands, Local, Query, Res, ResMut},
}; };
use bevy_image::Image; use bevy_image::Image;
use bevy_light::{EnvironmentMapLight, LightProbe};
use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4}; use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4};
use bevy_platform::collections::HashMap; use bevy_platform::collections::HashMap;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
extract_instances::ExtractInstancesPlugin, extract_instances::ExtractInstancesPlugin,
load_shader_library, load_shader_library,
@ -27,7 +26,7 @@ use bevy_render::{
settings::WgpuFeatures, settings::WgpuFeatures,
sync_world::RenderEntity, sync_world::RenderEntity,
texture::{FallbackImage, GpuImage}, texture::{FallbackImage, GpuImage},
view::{ExtractedView, Visibility}, view::ExtractedView,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems, Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
}; };
use bevy_transform::{components::Transform, prelude::GlobalTransform}; use bevy_transform::{components::Transform, prelude::GlobalTransform};
@ -35,7 +34,7 @@ use tracing::error;
use core::{hash::Hash, ops::Deref}; use core::{hash::Hash, ops::Deref};
use crate::light_probe::environment_map::{EnvironmentMapIds, EnvironmentMapLight}; use crate::light_probe::environment_map::EnvironmentMapIds;
use self::irradiance_volume::IrradianceVolume; use self::irradiance_volume::IrradianceVolume;
@ -59,50 +58,6 @@ const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
/// cubemaps applied to all objects that a view renders. /// cubemaps applied to all objects that a view renders.
pub struct LightProbePlugin; pub struct LightProbePlugin;
/// A marker component for a light probe, which is a cuboid region that provides
/// global illumination to all fragments inside it.
///
/// Note that a light probe will have no effect unless the entity contains some
/// kind of illumination, which can either be an [`EnvironmentMapLight`] or an
/// [`IrradianceVolume`].
///
/// The light probe range is conceptually a unit cube (1×1×1) centered on the
/// origin. The [`Transform`] applied to this entity can scale, rotate, or translate
/// that cube so that it contains all fragments that should take this light probe into account.
///
/// When multiple sources of indirect illumination can be applied to a fragment,
/// the highest-quality one is chosen. Diffuse and specular illumination are
/// considered separately, so, for example, Bevy may decide to sample the
/// diffuse illumination from an irradiance volume and the specular illumination
/// from a reflection probe. From highest priority to lowest priority, the
/// ranking is as follows:
///
/// | Rank | Diffuse | Specular |
/// | ---- | -------------------- | -------------------- |
/// | 1 | Lightmap | Lightmap |
/// | 2 | Irradiance volume | Reflection probe |
/// | 3 | Reflection probe | View environment map |
/// | 4 | View environment map | |
///
/// Note that ambient light is always added to the diffuse component and does
/// not participate in the ranking. That is, ambient light is applied in
/// addition to, not instead of, the light sources above.
///
/// A terminology note: Unfortunately, there is little agreement across game and
/// graphics engines as to what to call the various techniques that Bevy groups
/// under the term *light probe*. In Bevy, a *light probe* is the generic term
/// that encompasses both *reflection probes* and *irradiance volumes*. In
/// object-oriented terms, *light probe* is the superclass, and *reflection
/// probe* and *irradiance volume* are subclasses. In other engines, you may see
/// the term *light probe* refer to an irradiance volume with a single voxel, or
/// perhaps some other technique, while in Bevy *light probe* refers not to a
/// specific technique but rather to a class of techniques. Developers familiar
/// with other engines should be aware of this terminology difference.
#[derive(Component, Debug, Clone, Copy, Default, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(Transform, Visibility)]
pub struct LightProbe;
/// A GPU type that stores information about a light probe. /// A GPU type that stores information about a light probe.
#[derive(Clone, Copy, ShaderType, Default)] #[derive(Clone, Copy, ShaderType, Default)]
struct RenderLightProbe { struct RenderLightProbe {
@ -302,14 +257,6 @@ pub trait LightProbeComponent: Send + Sync + Component + Sized {
) -> RenderViewLightProbes<Self>; ) -> RenderViewLightProbes<Self>;
} }
impl LightProbe {
/// Creates a new light probe component.
#[inline]
pub fn new() -> Self {
Self
}
}
/// The uniform struct extracted from [`EnvironmentMapLight`]. /// The uniform struct extracted from [`EnvironmentMapLight`].
/// Will be available for use in the Environment Map shader. /// Will be available for use in the Environment Map shader.
#[derive(Component, ShaderType, Clone)] #[derive(Component, ShaderType, Clone)]
@ -341,10 +288,7 @@ impl Plugin for LightProbePlugin {
load_shader_library!(app, "environment_map.wgsl"); load_shader_library!(app, "environment_map.wgsl");
load_shader_library!(app, "irradiance_volume.wgsl"); load_shader_library!(app, "irradiance_volume.wgsl");
app.register_type::<LightProbe>() app.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
.register_type::<EnvironmentMapLight>()
.register_type::<IrradianceVolume>()
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
} }
fn finish(&self, app: &mut App) { fn finish(&self, app: &mut App) {

View File

@ -2,13 +2,14 @@ use super::{
instance_manager::InstanceManager, pipelines::MeshletPipelines, instance_manager::InstanceManager, pipelines::MeshletPipelines,
resource_manager::ResourceManager, resource_manager::ResourceManager,
}; };
use crate::{environment_map::EnvironmentMapLight, irradiance_volume::IrradianceVolume, *}; use crate::{irradiance_volume::IrradianceVolume, *};
use bevy_core_pipeline::{ use bevy_core_pipeline::{
core_3d::Camera3d, core_3d::Camera3d,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass}, prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
tonemapping::{DebandDither, Tonemapping}, tonemapping::{DebandDither, Tonemapping},
}; };
use bevy_derive::{Deref, DerefMut}; use bevy_derive::{Deref, DerefMut};
use bevy_light::EnvironmentMapLight;
use bevy_platform::collections::{HashMap, HashSet}; use bevy_platform::collections::{HashMap, HashSet};
use bevy_render::erased_render_asset::ErasedRenderAssets; use bevy_render::erased_render_asset::ErasedRenderAssets;
use bevy_render::{ use bevy_render::{

View File

@ -15,7 +15,6 @@ fn vertex(@builtin(vertex_index) vertex_input: u32) -> @builtin(position) vec4<f
return vec4(uv_to_ndc(uv), material_depth, 1.0); return vec4(uv_to_ndc(uv), material_depth, 1.0);
} }
#ifdef PREPASS_FRAGMENT
@fragment @fragment
fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> { fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
let vertex_output = resolve_vertex_output(frag_coord); let vertex_output = resolve_vertex_output(frag_coord);
@ -23,7 +22,6 @@ fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32>
let color = vec3(rand_f(&rng), rand_f(&rng), rand_f(&rng)); let color = vec3(rand_f(&rng), rand_f(&rng), rand_f(&rng));
return vec4(color, 1.0); return vec4(color, 1.0);
} }
#endif
#ifdef PREPASS_FRAGMENT #ifdef PREPASS_FRAGMENT
@fragment @fragment

View File

@ -1,6 +1,3 @@
use self::assign::ClusterableObjectType;
use crate::assign::calculate_cluster_factors;
use crate::cascade::{Cascade, CascadeShadowConfig, Cascades};
use crate::*; use crate::*;
use bevy_asset::UntypedAssetId; use bevy_asset::UntypedAssetId;
pub use bevy_camera::primitives::{face_index_to_name, CubeMapFace, CUBE_MAP_FACES}; pub use bevy_camera::primitives::{face_index_to_name, CubeMapFace, CUBE_MAP_FACES};
@ -14,6 +11,13 @@ use bevy_ecs::{
prelude::*, prelude::*,
system::lifetimeless::Read, system::lifetimeless::Read,
}; };
use bevy_light::cascade::Cascade;
use bevy_light::cluster::assign::{calculate_cluster_factors, ClusterableObjectType};
use bevy_light::cluster::GlobalVisibleClusterableObjects;
use bevy_light::{
spot_light_clip_from_view, spot_light_world_from_view, DirectionalLightShadowMap,
NotShadowCaster, PointLightShadowMap,
};
use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles}; use bevy_math::{ops, Mat4, UVec4, Vec3, Vec3Swizzles, Vec4, Vec4Swizzles};
use bevy_platform::collections::{HashMap, HashSet}; use bevy_platform::collections::{HashMap, HashSet};
use bevy_platform::hash::FixedHasher; use bevy_platform::hash::FixedHasher;
@ -798,7 +802,7 @@ pub fn prepare_lights(
// - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded. // - then by entity as a stable key to ensure that a consistent set of lights are chosen if the light count limit is exceeded.
point_lights.sort_by_cached_key(|(entity, _, light, _)| { point_lights.sort_by_cached_key(|(entity, _, light, _)| {
( (
ClusterableObjectType::from_point_or_spot_light(light).ordering(), point_or_spot_light_to_clusterable(light).ordering(),
*entity, *entity,
) )
}); });
@ -2265,3 +2269,18 @@ impl ShadowPassNode {
Ok(()) Ok(())
} }
} }
/// Creates the [`ClusterableObjectType`] data for a point or spot light.
fn point_or_spot_light_to_clusterable(point_light: &ExtractedPointLight) -> ClusterableObjectType {
match point_light.spot_light_angles {
Some((_, outer_angle)) => ClusterableObjectType::SpotLight {
outer_angle,
shadows_enabled: point_light.shadows_enabled,
volumetric: point_light.volumetric,
},
None => ClusterableObjectType::PointLight {
shadows_enabled: point_light.shadows_enabled,
volumetric: point_light.volumetric,
},
}
}

View File

@ -15,6 +15,9 @@ use bevy_ecs::{
system::{lifetimeless::*, SystemParamItem, SystemState}, system::{lifetimeless::*, SystemParamItem, SystemState},
}; };
use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo}; use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo};
use bevy_light::{
EnvironmentMapLight, NotShadowCaster, NotShadowReceiver, TransmittedShadowReceiver,
};
use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4}; use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4};
use bevy_platform::collections::{hash_map::Entry, HashMap}; use bevy_platform::collections::{hash_map::Entry, HashMap};
use bevy_render::{ use bevy_render::{
@ -52,7 +55,6 @@ use material_bind_groups::MaterialBindingId;
use tracing::{error, warn}; use tracing::{error, warn};
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE; use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
use crate::environment_map::EnvironmentMapLight;
use crate::irradiance_volume::IrradianceVolume; use crate::irradiance_volume::IrradianceVolume;
use crate::{ use crate::{
render::{ render::{

View File

@ -17,6 +17,7 @@ use bevy_ecs::{
world::{FromWorld, World}, world::{FromWorld, World},
}; };
use bevy_image::BevyDefault as _; use bevy_image::BevyDefault as _;
use bevy_light::EnvironmentMapLight;
use bevy_math::Vec4; use bevy_math::Vec4;
use bevy_render::{ use bevy_render::{
globals::{GlobalsBuffer, GlobalsUniform}, globals::{GlobalsBuffer, GlobalsUniform},
@ -30,7 +31,6 @@ use bevy_render::{
}, },
}; };
use core::{array, num::NonZero}; use core::{array, num::NonZero};
use environment_map::EnvironmentMapLight;
use crate::{ use crate::{
decal::{ decal::{

View File

@ -6,14 +6,14 @@
//! for light beams from directional lights to shine through, creating what is //! for light beams from directional lights to shine through, creating what is
//! known as *light shafts* or *god rays*. //! known as *light shafts* or *god rays*.
//! //!
//! To add volumetric fog to a scene, add [`VolumetricFog`] to the //! To add volumetric fog to a scene, add [`crate::VolumetricFog`] to the
//! camera, and add [`VolumetricLight`] to directional lights that you wish to //! camera, and add [`crate::VolumetricLight`] to directional lights that you wish to
//! be volumetric. [`VolumetricFog`] feature numerous settings that //! be volumetric. [`crate::VolumetricFog`] feature numerous settings that
//! allow you to define the accuracy of the simulation, as well as the look of //! allow you to define the accuracy of the simulation, as well as the look of
//! the fog. Currently, only interaction with directional lights that have //! the fog. Currently, only interaction with directional lights that have
//! shadow maps is supported. Note that the overhead of the effect scales //! shadow maps is supported. Note that the overhead of the effect scales
//! directly with the number of directional lights in use, so apply //! directly with the number of directional lights in use, so apply
//! [`VolumetricLight`] sparingly for the best results. //! [`crate::VolumetricLight`] sparingly for the best results.
//! //!
//! The overall algorithm, which is implemented as a postprocessing effect, is a //! The overall algorithm, which is implemented as a postprocessing effect, is a
//! combination of the techniques described in [Scratchapixel] and [this blog //! combination of the techniques described in [Scratchapixel] and [this blog
@ -30,30 +30,24 @@
//! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction //! [Henyey-Greenstein phase function]: https://www.pbr-book.org/4ed/Volume_Scattering/Phase_Functions#TheHenyeyndashGreensteinPhaseFunction
use bevy_app::{App, Plugin}; use bevy_app::{App, Plugin};
use bevy_asset::{embedded_asset, Assets, Handle}; use bevy_asset::{embedded_asset, Assets};
use bevy_color::Color;
use bevy_core_pipeline::core_3d::{ use bevy_core_pipeline::core_3d::{
graph::{Core3d, Node3d}, graph::{Core3d, Node3d},
prepare_core_3d_depth_textures, prepare_core_3d_depth_textures,
}; };
use bevy_ecs::{ use bevy_ecs::schedule::IntoScheduleConfigs as _;
component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs as _, use bevy_light::FogVolume;
};
use bevy_image::Image;
use bevy_math::{ use bevy_math::{
primitives::{Cuboid, Plane3d}, primitives::{Cuboid, Plane3d},
Vec2, Vec3, Vec2, Vec3,
}; };
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{ use bevy_render::{
mesh::{Mesh, Meshable}, mesh::{Mesh, Meshable},
render_graph::{RenderGraphExt, ViewNodeRunner}, render_graph::{RenderGraphExt, ViewNodeRunner},
render_resource::SpecializedRenderPipelines, render_resource::SpecializedRenderPipelines,
sync_component::SyncComponentPlugin, sync_component::SyncComponentPlugin,
view::Visibility,
ExtractSchedule, Render, RenderApp, RenderSystems, ExtractSchedule, Render, RenderApp, RenderSystems,
}; };
use bevy_transform::components::Transform;
use render::{ use render::{
VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH, VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH,
}; };
@ -65,127 +59,6 @@ pub mod render;
/// A plugin that implements volumetric fog. /// A plugin that implements volumetric fog.
pub struct VolumetricFogPlugin; pub struct VolumetricFogPlugin;
/// Add this component to a [`DirectionalLight`](crate::DirectionalLight) with a shadow map
/// (`shadows_enabled: true`) to make volumetric fog interact with it.
///
/// This allows the light to generate light shafts/god rays.
#[derive(Clone, Copy, Component, Default, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct VolumetricLight;
/// When placed on a [`bevy_core_pipeline::core_3d::Camera3d`], enables
/// volumetric fog and volumetric lighting, also known as light shafts or god
/// rays.
#[derive(Clone, Copy, Component, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
pub struct VolumetricFog {
/// Color of the ambient light.
///
/// This is separate from Bevy's [`AmbientLight`](crate::light::AmbientLight) because an
/// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight) is
/// still considered an ambient light for the purposes of volumetric fog. If you're using a
/// [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight), for best results,
/// this should be a good approximation of the average color of the environment map.
///
/// Defaults to white.
pub ambient_color: Color,
/// The brightness of the ambient light.
///
/// If there's no [`EnvironmentMapLight`](crate::environment_map::EnvironmentMapLight),
/// set this to 0.
///
/// Defaults to 0.1.
pub ambient_intensity: f32,
/// The maximum distance to offset the ray origin randomly by, in meters.
///
/// This is intended for use with temporal antialiasing. It helps fog look
/// less blocky by varying the start position of the ray, using interleaved
/// gradient noise.
pub jitter: f32,
/// The number of raymarching steps to perform.
///
/// Higher values produce higher-quality results with less banding, but
/// reduce performance.
///
/// The default value is 64.
pub step_count: u32,
}
#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(Transform, Visibility)]
pub struct FogVolume {
/// The color of the fog.
///
/// Note that the fog must be lit by a [`VolumetricLight`] or ambient light
/// in order for this color to appear.
///
/// Defaults to white.
pub fog_color: Color,
/// The density of fog, which measures how dark the fog is.
///
/// The default value is 0.1.
pub density_factor: f32,
/// Optional 3D voxel density texture for the fog.
pub density_texture: Option<Handle<Image>>,
/// Configurable offset of the density texture in UVW coordinates.
///
/// This can be used to scroll a repeating density texture in a direction over time
/// to create effects like fog moving in the wind. Make sure to configure the texture
/// to use `ImageAddressMode::Repeat` if this is your intention.
///
/// Has no effect when no density texture is present.
///
/// The default value is (0, 0, 0).
pub density_texture_offset: Vec3,
/// The absorption coefficient, which measures what fraction of light is
/// absorbed by the fog at each step.
///
/// Increasing this value makes the fog darker.
///
/// The default value is 0.3.
pub absorption: f32,
/// The scattering coefficient, which measures the fraction of light that's
/// scattered toward, and away from, the viewer.
///
/// The default value is 0.3.
pub scattering: f32,
/// Measures the fraction of light that's scattered *toward* the camera, as
/// opposed to *away* from the camera.
///
/// Increasing this value makes light shafts become more prominent when the
/// camera is facing toward their source and less prominent when the camera
/// is facing away. Essentially, a high value here means the light shafts
/// will fade into view as the camera focuses on them and fade away when the
/// camera is pointing away.
///
/// The default value is 0.8.
pub scattering_asymmetry: f32,
/// Applies a nonphysical color to the light.
///
/// This can be useful for artistic purposes but is nonphysical.
///
/// The default value is white.
pub light_tint: Color,
/// Scales the light by a fixed fraction.
///
/// This can be useful for artistic purposes but is nonphysical.
///
/// The default value is 1.0, which results in no adjustment.
pub light_intensity: f32,
}
impl Plugin for VolumetricFogPlugin { impl Plugin for VolumetricFogPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
embedded_asset!(app, "volumetric_fog.wgsl"); embedded_asset!(app, "volumetric_fog.wgsl");
@ -194,9 +67,6 @@ impl Plugin for VolumetricFogPlugin {
meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into()); meshes.insert(&PLANE_MESH, Plane3d::new(Vec3::Z, Vec2::ONE).mesh().into());
meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into()); meshes.insert(&CUBE_MESH, Cuboid::new(1.0, 1.0, 1.0).mesh().into());
app.register_type::<VolumetricFog>()
.register_type::<VolumetricLight>();
app.add_plugins(SyncComponentPlugin::<FogVolume>::default()); app.add_plugins(SyncComponentPlugin::<FogVolume>::default());
let Some(render_app) = app.get_sub_app_mut(RenderApp) else { let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
@ -238,31 +108,3 @@ impl Plugin for VolumetricFogPlugin {
); );
} }
} }
impl Default for VolumetricFog {
fn default() -> Self {
Self {
step_count: 64,
// Matches `AmbientLight` defaults.
ambient_color: Color::WHITE,
ambient_intensity: 0.1,
jitter: 0.0,
}
}
}
impl Default for FogVolume {
fn default() -> Self {
Self {
absorption: 0.3,
scattering: 0.3,
density_factor: 0.1,
density_texture: None,
density_texture_offset: Vec3::ZERO,
scattering_asymmetry: 0.5,
fog_color: Color::WHITE,
light_tint: Color::WHITE,
light_intensity: 1.0,
}
}
}

View File

@ -24,7 +24,6 @@ bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } bevy_render = { path = "../bevy_render", version = "0.17.0-dev" }
bevy_time = { path = "../bevy_time", version = "0.17.0-dev" } bevy_time = { path = "../bevy_time", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" }
bevy_window = { path = "../bevy_window", version = "0.17.0-dev" } bevy_window = { path = "../bevy_window", version = "0.17.0-dev" }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std", "std",

View File

@ -344,7 +344,7 @@ pub fn update_is_hovered(
} }
// Algorithm: for each entity having a `Hovered` component, we want to know if the current // Algorithm: for each entity having a `Hovered` component, we want to know if the current
// entry in the hover map is "within" (that is, in the set of descenants of) that entity. Rather // entry in the hover map is "within" (that is, in the set of descendants of) that entity. Rather
// than doing an expensive breadth-first traversal of children, instead start with the hovermap // than doing an expensive breadth-first traversal of children, instead start with the hovermap
// entry and search upwards. We can make this even cheaper by building a set of ancestors for // entry and search upwards. We can make this even cheaper by building a set of ancestors for
// the hovermap entry, and then testing each `Hovered` entity against that set. // the hovermap entry, and then testing each `Hovered` entity against that set.

View File

@ -39,24 +39,30 @@ pub mod prelude {
pub use crate::input::PointerInputPlugin; pub use crate::input::PointerInputPlugin;
} }
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
/// that you can replace with your own plugin as needed.
///
/// [`crate::PickingPlugin::is_input_enabled`] can be used to toggle whether
/// the core picking plugin processes the inputs sent by this, or other input plugins, in one place.
///
/// This plugin contains several settings, and is added to the world as a resource after initialization.
/// You can configure pointer input settings at runtime by accessing the resource.
#[derive(Copy, Clone, Resource, Debug, Reflect)] #[derive(Copy, Clone, Resource, Debug, Reflect)]
#[reflect(Resource, Default, Clone)] #[reflect(Resource, Default, Clone)]
pub struct PointerInputPlugin { /// Settings for enabling and disabling updating mouse and touch inputs for picking
///
/// ## Custom initialization
/// ```
/// # use bevy_app::App;
/// # use bevy_picking::input::{PointerInputSettings,PointerInputPlugin};
/// App::new()
/// .insert_resource(PointerInputSettings {
/// is_touch_enabled: false,
/// is_mouse_enabled: true,
/// })
/// // or DefaultPlugins
/// .add_plugins(PointerInputPlugin);
/// ```
pub struct PointerInputSettings {
/// Should touch inputs be updated? /// Should touch inputs be updated?
pub is_touch_enabled: bool, pub is_touch_enabled: bool,
/// Should mouse inputs be updated? /// Should mouse inputs be updated?
pub is_mouse_enabled: bool, pub is_mouse_enabled: bool,
} }
impl PointerInputPlugin { impl PointerInputSettings {
fn is_mouse_enabled(state: Res<Self>) -> bool { fn is_mouse_enabled(state: Res<Self>) -> bool {
state.is_mouse_enabled state.is_mouse_enabled
} }
@ -66,7 +72,7 @@ impl PointerInputPlugin {
} }
} }
impl Default for PointerInputPlugin { impl Default for PointerInputSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
is_touch_enabled: true, is_touch_enabled: true,
@ -75,25 +81,35 @@ impl Default for PointerInputPlugin {
} }
} }
/// Adds mouse and touch inputs for picking pointers to your app. This is a default input plugin,
/// that you can replace with your own plugin as needed.
///
/// Toggling mouse input or touch input can be done at runtime by modifying
/// [`PointerInputSettings`] resource.
///
/// [`PointerInputSettings`] can be initialized with custom values, but will be
/// initialized with default values if it is not present at the moment this is
/// added to the app.
pub struct PointerInputPlugin;
impl Plugin for PointerInputPlugin { impl Plugin for PointerInputPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(*self) app.init_resource::<PointerInputSettings>()
.register_type::<PointerInputSettings>()
.add_systems(Startup, spawn_mouse_pointer) .add_systems(Startup, spawn_mouse_pointer)
.add_systems( .add_systems(
First, First,
( (
mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled), mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled), touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
) )
.chain() .chain()
.in_set(PickingSystems::Input), .in_set(PickingSystems::Input),
) )
.add_systems( .add_systems(
Last, Last,
deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled), deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
) );
.register_type::<Self>()
.register_type::<PointerInputPlugin>();
} }
} }

View File

@ -293,20 +293,31 @@ pub struct DefaultPickingPlugins;
impl PluginGroup for DefaultPickingPlugins { impl PluginGroup for DefaultPickingPlugins {
fn build(self) -> PluginGroupBuilder { fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>() PluginGroupBuilder::start::<Self>()
.add(input::PointerInputPlugin::default()) .add(input::PointerInputPlugin)
.add(PickingPlugin::default()) .add(PickingPlugin)
.add(InteractionPlugin) .add(InteractionPlugin)
} }
} }
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
/// types used by other picking plugins.
///
/// This plugin contains several settings, and is added to the world as a resource after initialization. You
/// can configure picking settings at runtime through the resource.
#[derive(Copy, Clone, Debug, Resource, Reflect)] #[derive(Copy, Clone, Debug, Resource, Reflect)]
#[reflect(Resource, Default, Debug, Clone)] #[reflect(Resource, Default, Debug, Clone)]
pub struct PickingPlugin { /// Controls the behavior of picking
///
/// ## Custom initialization
/// ```
/// # use bevy_app::App;
/// # use bevy_picking::{PickingSettings, PickingPlugin};
/// App::new()
/// .insert_resource(PickingSettings {
/// is_enabled: true,
/// is_input_enabled: false,
/// is_hover_enabled: true,
/// is_window_picking_enabled: false,
/// })
/// // or DefaultPlugins
/// .add_plugins(PickingPlugin);
/// ```
pub struct PickingSettings {
/// Enables and disables all picking features. /// Enables and disables all picking features.
pub is_enabled: bool, pub is_enabled: bool,
/// Enables and disables input collection. /// Enables and disables input collection.
@ -317,7 +328,7 @@ pub struct PickingPlugin {
pub is_window_picking_enabled: bool, pub is_window_picking_enabled: bool,
} }
impl PickingPlugin { impl PickingSettings {
/// Whether or not input collection systems should be running. /// Whether or not input collection systems should be running.
pub fn input_should_run(state: Res<Self>) -> bool { pub fn input_should_run(state: Res<Self>) -> bool {
state.is_input_enabled && state.is_enabled state.is_input_enabled && state.is_enabled
@ -335,7 +346,7 @@ impl PickingPlugin {
} }
} }
impl Default for PickingPlugin { impl Default for PickingSettings {
fn default() -> Self { fn default() -> Self {
Self { Self {
is_enabled: true, is_enabled: true,
@ -346,9 +357,18 @@ impl Default for PickingPlugin {
} }
} }
/// This plugin sets up the core picking infrastructure. It receives input events, and provides the shared
/// types used by other picking plugins.
///
/// Behavior of picking can be controlled by modifying [`PickingSettings`].
///
/// [`PickingSettings`] will be initialized with default values if it
/// is not present at the moment this is added to the app.
pub struct PickingPlugin;
impl Plugin for PickingPlugin { impl Plugin for PickingPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.insert_resource(*self) app.init_resource::<PickingSettings>()
.init_resource::<pointer::PointerMap>() .init_resource::<pointer::PointerMap>()
.init_resource::<backend::ray::RayMap>() .init_resource::<backend::ray::RayMap>()
.add_event::<pointer::PointerInput>() .add_event::<pointer::PointerInput>()
@ -369,7 +389,7 @@ impl Plugin for PickingPlugin {
.add_systems( .add_systems(
PreUpdate, PreUpdate,
window::update_window_hits window::update_window_hits
.run_if(Self::window_picking_should_run) .run_if(PickingSettings::window_picking_should_run)
.in_set(PickingSystems::Backend), .in_set(PickingSystems::Backend),
) )
.configure_sets( .configure_sets(
@ -382,15 +402,15 @@ impl Plugin for PickingPlugin {
.configure_sets( .configure_sets(
PreUpdate, PreUpdate,
( (
PickingSystems::ProcessInput.run_if(Self::input_should_run), PickingSystems::ProcessInput.run_if(PickingSettings::input_should_run),
PickingSystems::Backend, PickingSystems::Backend,
PickingSystems::Hover.run_if(Self::hover_should_run), PickingSystems::Hover.run_if(PickingSettings::hover_should_run),
PickingSystems::PostHover, PickingSystems::PostHover,
PickingSystems::Last, PickingSystems::Last,
) )
.chain(), .chain(),
) )
.register_type::<Self>() .register_type::<PickingSettings>()
.register_type::<Pickable>() .register_type::<Pickable>()
.register_type::<hover::PickingInteraction>() .register_type::<hover::PickingInteraction>()
.register_type::<hover::Hovered>() .register_type::<hover::Hovered>()

View File

@ -9,6 +9,7 @@ mod structs {
#[reflect_remote(external_crate::TheirStruct)] #[reflect_remote(external_crate::TheirStruct)]
//~^ ERROR: `?` operator has incompatible types //~^ ERROR: `?` operator has incompatible types
//~| ERROR: mismatched types
struct MyStruct { struct MyStruct {
// Reason: Should be `u32` // Reason: Should be `u32`
pub value: bool, pub value: bool,
@ -25,6 +26,7 @@ mod tuple_structs {
#[reflect_remote(external_crate::TheirStruct)] #[reflect_remote(external_crate::TheirStruct)]
//~^ ERROR: `?` operator has incompatible types //~^ ERROR: `?` operator has incompatible types
//~| ERROR: mismatched types
struct MyStruct( struct MyStruct(
// Reason: Should be `u32` // Reason: Should be `u32`
pub bool, pub bool,
@ -48,6 +50,7 @@ mod enums {
//~| ERROR: variant `enums::external_crate::TheirStruct::Unit` has no field named `0` //~| ERROR: variant `enums::external_crate::TheirStruct::Unit` has no field named `0`
//~| ERROR: `?` operator has incompatible types //~| ERROR: `?` operator has incompatible types
//~| ERROR: `?` operator has incompatible types //~| ERROR: `?` operator has incompatible types
//~| ERROR: mismatched types
enum MyStruct { enum MyStruct {
// Reason: Should be unit variant // Reason: Should be unit variant
Unit(i32), Unit(i32),
@ -57,6 +60,7 @@ mod enums {
// Reason: Should be `usize` // Reason: Should be `usize`
Struct { value: String }, Struct { value: String },
//~^ ERROR: mismatched types //~^ ERROR: mismatched types
//~| ERROR: mismatched types
} }
} }

View File

@ -26,8 +26,8 @@ mod incorrect_inner_type {
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
//~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner<T>` does not implement `PartialReflect` so cannot be introspected
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information //~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
//~| ERROR: `TheirInner<T>` does not implement `TypePath` so cannot provide dynamic type path information
//~| ERROR: `?` operator has incompatible types //~| ERROR: `?` operator has incompatible types
//~| ERROR: mismatched types
struct MyOuter<T: FromReflect + GetTypeRegistration> { struct MyOuter<T: FromReflect + GetTypeRegistration> {
// Reason: Should not use `MyInner<T>` directly // Reason: Should not use `MyInner<T>` directly
pub inner: MyInner<T>, pub inner: MyInner<T>,

View File

@ -77,6 +77,7 @@ mod enums {
#[reflect_remote(external_crate::TheirBar)] #[reflect_remote(external_crate::TheirBar)]
//~^ ERROR: `?` operator has incompatible types //~^ ERROR: `?` operator has incompatible types
//~| ERROR: mismatched types
enum MyBar { enum MyBar {
// Reason: Should use `i32` // Reason: Should use `i32`
Value(u32), Value(u32),

View File

@ -24,7 +24,6 @@ indexmap = "2.0"
proc-macro2 = "1.0" proc-macro2 = "1.0"
quote = "1.0" quote = "1.0"
syn = { version = "2.0", features = ["full", "extra-traits"] } syn = { version = "2.0", features = ["full", "extra-traits"] }
uuid = { version = "1.13.1", features = ["v4"] }
[target.'cfg(target_arch = "wasm32")'.dependencies] [target.'cfg(target_arch = "wasm32")'.dependencies]
# TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption. # TODO: Assuming all wasm builds are for the browser. Require `no_std` support to break assumption.

View File

@ -722,18 +722,7 @@ impl<'a> ReflectStruct<'a> {
} }
} else { } else {
quote! { quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#accessor)? <#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#accessor)?
.take()
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
),
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
)
),
})?
} }
}; };

View File

@ -316,9 +316,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
fn construct_field(&self, field: VariantField) -> TokenStream { fn construct_field(&self, field: VariantField) -> TokenStream {
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path(); let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
let field_ty = field.field.reflected_type(); let field_ty = field.field.reflected_type();
let alias = field.alias; let alias = field.alias;
let alias = match &field.field.attrs.remote { let alias = match &field.field.attrs.remote {
Some(wrapper_ty) => { Some(wrapper_ty) => {
@ -332,18 +330,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
match &field.field.attrs.clone { match &field.field.attrs.clone {
CloneBehavior::Default => { CloneBehavior::Default => {
quote! { quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#alias)? <#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#alias)?
.take()
.map_err(|value| #bevy_reflect_path::ReflectCloneError::FailedDowncast {
expected: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Borrowed(
<#field_ty as #bevy_reflect_path::TypePath>::type_path()
),
received: #bevy_reflect_path::__macro_exports::alloc_utils::Cow::Owned(
#bevy_reflect_path::__macro_exports::alloc_utils::ToString::to_string(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(&*value)
)
),
})?
} }
} }
CloneBehavior::Trait => { CloneBehavior::Trait => {

View File

@ -231,11 +231,11 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
/// // Generates a where clause like: /// // Generates a where clause like:
/// // impl bevy_reflect::Reflect for Foo /// // impl bevy_reflect::Reflect for Foo
/// // where /// // where
/// // Self: Any + Send + Sync, /// // Foo: Any + Send + Sync,
/// // Vec<Foo>: FromReflect + TypePath, /// // Vec<Foo>: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
/// ``` /// ```
/// ///
/// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + TypePath`, /// In this case, `Foo` is given the bounds `Vec<Foo>: FromReflect + ...`,
/// which requires that `Foo` implements `FromReflect`, /// which requires that `Foo` implements `FromReflect`,
/// which requires that `Vec<Foo>` implements `FromReflect`, /// which requires that `Vec<Foo>` implements `FromReflect`,
/// and so on, resulting in the error. /// and so on, resulting in the error.
@ -283,10 +283,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
/// // /// //
/// // impl<T: Trait> bevy_reflect::Reflect for Foo<T> /// // impl<T: Trait> bevy_reflect::Reflect for Foo<T>
/// // where /// // where
/// // Self: Any + Send + Sync, /// // Foo<T>: Any + Send + Sync,
/// // T::Assoc: Default, /// // T::Assoc: Default,
/// // T: TypePath, /// // T: TypePath,
/// // T::Assoc: FromReflect + TypePath, /// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
/// // T::Assoc: List, /// // T::Assoc: List,
/// // {/* ... */} /// // {/* ... */}
/// ``` /// ```

View File

@ -1,8 +1,8 @@
use crate::derive_data::ReflectMeta; use crate::derive_data::ReflectMeta;
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync}; use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
use proc_macro2::TokenStream; use proc_macro2::{TokenStream, TokenTree};
use quote::{quote, ToTokens}; use quote::{quote, ToTokens};
use syn::{punctuated::Punctuated, Token, Type, WhereClause}; use syn::{punctuated::Punctuated, Ident, Token, Type, WhereClause};
/// Options defining how to extend the `where` clause for reflection. /// Options defining how to extend the `where` clause for reflection.
pub(crate) struct WhereClauseOptions<'a, 'b> { pub(crate) struct WhereClauseOptions<'a, 'b> {
@ -29,15 +29,24 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
self.meta self.meta
} }
/// Extends the `where` clause for a type with additional bounds needed for the reflection impls. /// Extends the `where` clause for a type with additional bounds needed for the reflection
/// impls.
/// ///
/// The default bounds added are as follows: /// The default bounds added are as follows:
/// - `Self` has the bounds `Any + Send + Sync` /// - `Self` has:
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present /// - `Any + Send + Sync` bounds, if generic over types
/// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present /// - An `Any` bound, if generic over lifetimes but not types
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present) /// - No bounds, if generic over neither types nor lifetimes
/// - Any given bounds in a `where` clause on the type
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is
/// present
/// - Active fields with non-generic types have the bounds `TypePath`, either `PartialReflect`
/// if `#[reflect(from_reflect = false)]` is present or `FromReflect` otherwise,
/// `MaybeTyped`, and `RegisterForReflection` (or no bounds at all if
/// `#[reflect(no_field_bounds)]` is present)
/// ///
/// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are added as well. /// When the derive is used with `#[reflect(where)]`, the bounds specified in the attribute are
/// added as well.
/// ///
/// # Example /// # Example
/// ///
@ -55,57 +64,69 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
/// ```ignore (bevy_reflect is not accessible from this crate) /// ```ignore (bevy_reflect is not accessible from this crate)
/// where /// where
/// // `Self` bounds: /// // `Self` bounds:
/// Self: Any + Send + Sync, /// Foo<T, U>: Any + Send + Sync,
/// // Type parameter bounds: /// // Type parameter bounds:
/// T: TypePath, /// T: TypePath,
/// U: TypePath, /// U: TypePath,
/// // Field bounds /// // Active non-generic field bounds
/// T: FromReflect + TypePath, /// T: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
///
/// ``` /// ```
/// ///
/// If we had added `#[reflect(where T: MyTrait)]` to the type, it would instead generate: /// If we add various things to the type:
///
/// ```ignore (bevy_reflect is not accessible from this crate)
/// #[derive(Reflect)]
/// #[reflect(where T: MyTrait)]
/// #[reflect(no_field_bounds)]
/// struct Foo<T, U>
/// where T: Clone
/// {
/// a: T,
/// #[reflect(ignore)]
/// b: U
/// }
/// ```
///
/// It will instead generate the following where clause:
/// ///
/// ```ignore (bevy_reflect is not accessible from this crate) /// ```ignore (bevy_reflect is not accessible from this crate)
/// where /// where
/// // `Self` bounds: /// // `Self` bounds:
/// Self: Any + Send + Sync, /// Foo<T, U>: Any + Send + Sync,
/// // Type parameter bounds: /// // Given bounds:
/// T: TypePath, /// T: Clone,
/// U: TypePath,
/// // Field bounds
/// T: FromReflect + TypePath,
/// // Custom bounds
/// T: MyTrait,
/// ```
///
/// And if we also added `#[reflect(no_field_bounds)]` to the type, it would instead generate:
///
/// ```ignore (bevy_reflect is not accessible from this crate)
/// where
/// // `Self` bounds:
/// Self: Any + Send + Sync,
/// // Type parameter bounds: /// // Type parameter bounds:
/// T: TypePath, /// T: TypePath,
/// U: TypePath, /// U: TypePath,
/// // No active non-generic field bounds
/// // Custom bounds /// // Custom bounds
/// T: MyTrait, /// T: MyTrait,
/// ``` /// ```
pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream { pub fn extend_where_clause(&self, where_clause: Option<&WhereClause>) -> TokenStream {
// We would normally just use `Self`, but that won't work for generating things like assertion functions let mut generic_where_clause = quote! { where };
// and trait impls for a type's reference (e.g. `impl FromArg for &MyType`)
let this = self.meta.type_path().true_type();
let required_bounds = self.required_bounds(); // Bounds on `Self`. We would normally just use `Self`, but that won't work for generating
// things like assertion functions and trait impls for a type's reference (e.g. `impl
// FromArg for &MyType`).
let generics = self.meta.type_path().generics();
if generics.type_params().next().is_some() {
// Generic over types? We need `Any + Send + Sync`.
let this = self.meta.type_path().true_type();
generic_where_clause.extend(quote! { #this: #FQAny + #FQSend + #FQSync, });
} else if generics.lifetimes().next().is_some() {
// Generic only over lifetimes? We need `'static`.
let this = self.meta.type_path().true_type();
generic_where_clause.extend(quote! { #this: 'static, });
}
// Maintain existing where clause, if any. // Maintain existing where clause bounds, if any.
let mut generic_where_clause = if let Some(where_clause) = where_clause { if let Some(where_clause) = where_clause {
let predicates = where_clause.predicates.iter(); let predicates = where_clause.predicates.iter();
quote! {where #this: #required_bounds, #(#predicates,)*} generic_where_clause.extend(quote! { #(#predicates,)* });
} else { }
quote!(where #this: #required_bounds,)
};
// Add additional reflection trait bounds // Add additional reflection trait bounds.
let predicates = self.predicates(); let predicates = self.predicates();
generic_where_clause.extend(quote! { generic_where_clause.extend(quote! {
#predicates #predicates
@ -157,19 +178,57 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
let bevy_reflect_path = self.meta.bevy_reflect_path(); let bevy_reflect_path = self.meta.bevy_reflect_path();
let reflect_bound = self.reflect_bound(); let reflect_bound = self.reflect_bound();
// `TypePath` is always required for active fields since they are used to // Get the identifiers of all type parameters.
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl. let type_param_idents = self
// Likewise, `GetTypeRegistration` is always required for active fields since .meta
// they are used to register the type's dependencies. .type_path()
Some(self.active_fields.iter().map(move |ty| { .generics()
quote!( .type_params()
#ty : #reflect_bound .map(|type_param| type_param.ident.clone())
+ #bevy_reflect_path::TypePath .collect::<Vec<Ident>>();
// Needed for `Typed` impls
+ #bevy_reflect_path::MaybeTyped // Do any of the identifiers in `idents` appear in `token_stream`?
// Needed for `GetTypeRegistration` impls fn is_any_ident_in_token_stream(idents: &[Ident], token_stream: TokenStream) -> bool {
+ #bevy_reflect_path::__macro_exports::RegisterForReflection for token_tree in token_stream {
) match token_tree {
TokenTree::Ident(ident) => {
if idents.contains(&ident) {
return true;
}
}
TokenTree::Group(group) => {
if is_any_ident_in_token_stream(idents, group.stream()) {
return true;
}
}
TokenTree::Punct(_) | TokenTree::Literal(_) => {}
}
}
false
}
Some(self.active_fields.iter().filter_map(move |ty| {
// Field type bounds are only required if `ty` is generic. How to determine that?
// Search `ty`s token stream for identifiers that match the identifiers from the
// function's type params. E.g. if `T` and `U` are the type param identifiers and
// `ty` is `Vec<[T; 4]>` then the `T` identifiers match. This is a bit hacky, but
// it works.
let is_generic =
is_any_ident_in_token_stream(&type_param_idents, ty.to_token_stream());
is_generic.then(|| {
quote!(
#ty: #reflect_bound
// Needed to construct `NamedField` and `UnnamedField` instances for
// the `Typed` impl.
+ #bevy_reflect_path::TypePath
// Needed for `Typed` impls
+ #bevy_reflect_path::MaybeTyped
// Needed for registering type dependencies in the
// `GetTypeRegistration` impl.
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
)
})
})) }))
} }
} }
@ -194,9 +253,4 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
None None
} }
} }
/// The minimum required bounds for a type to be reflected.
fn required_bounds(&self) -> TokenStream {
quote!(#FQAny + #FQSend + #FQSync)
}
} }

View File

@ -9,7 +9,6 @@ use crate::{
type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration}, type_registry::{FromType, GetTypeRegistration, ReflectFromPtr, TypeRegistration},
utility::GenericTypeInfoCell, utility::GenericTypeInfoCell,
}; };
use alloc::borrow::Cow;
use alloc::vec::Vec; use alloc::vec::Vec;
use bevy_platform::prelude::*; use bevy_platform::prelude::*;
use bevy_reflect_derive::impl_type_path; use bevy_reflect_derive::impl_type_path;
@ -144,21 +143,8 @@ where
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
let mut map = Self::new(); let mut map = Self::new();
for (key, value) in self.iter() { for (key, value) in self.iter() {
let key = let key = key.reflect_clone_and_take()?;
key.reflect_clone()? let value = value.reflect_clone_and_take()?;
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
received: Cow::Owned(key.reflect_type_path().to_string()),
})?;
let value =
value
.reflect_clone()?
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
})?;
map.insert(key, value); map.insert(key, value);
} }

View File

@ -10,7 +10,6 @@ use crate::{
}; };
use bevy_platform::prelude::*; use bevy_platform::prelude::*;
use bevy_reflect_derive::impl_type_path; use bevy_reflect_derive::impl_type_path;
use core::any::Any;
use core::fmt; use core::fmt;
macro_rules! impl_reflect_for_atomic { macro_rules! impl_reflect_for_atomic {
@ -21,10 +20,7 @@ macro_rules! impl_reflect_for_atomic {
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
crate::func::macros::impl_function_traits!($ty); crate::func::macros::impl_function_traits!($ty);
impl GetTypeRegistration for $ty impl GetTypeRegistration for $ty {
where
$ty: Any + Send + Sync,
{
fn get_type_registration() -> TypeRegistration { fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>(); let mut registration = TypeRegistration::of::<Self>();
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type()); registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
@ -42,10 +38,7 @@ macro_rules! impl_reflect_for_atomic {
} }
} }
impl Typed for $ty impl Typed for $ty {
where
$ty: Any + Send + Sync,
{
fn type_info() -> &'static TypeInfo { fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new(); static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| { CELL.get_or_set(|| {
@ -55,10 +48,7 @@ macro_rules! impl_reflect_for_atomic {
} }
} }
impl PartialReflect for $ty impl PartialReflect for $ty {
where
$ty: Any + Send + Sync,
{
#[inline] #[inline]
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> { fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info()) Some(<Self as Typed>::type_info())
@ -128,10 +118,7 @@ macro_rules! impl_reflect_for_atomic {
} }
} }
impl FromReflect for $ty impl FromReflect for $ty {
where
$ty: Any + Send + Sync,
{
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> { fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
Some(<$ty>::new( Some(<$ty>::new(
reflect.try_downcast_ref::<$ty>()?.load($ordering), reflect.try_downcast_ref::<$ty>()?.load($ordering),
@ -140,7 +127,7 @@ macro_rules! impl_reflect_for_atomic {
} }
}; };
impl_full_reflect!(for $ty where $ty: Any + Send + Sync); impl_full_reflect!(for $ty);
}; };
} }

View File

@ -113,14 +113,7 @@ macro_rules! impl_reflect_for_veclike {
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> { fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
Ok(bevy_platform::prelude::Box::new( Ok(bevy_platform::prelude::Box::new(
self.iter() self.iter()
.map(|value| { .map(|value| value.reflect_clone_and_take())
value.reflect_clone()?.take().map_err(|_| {
$crate::error::ReflectCloneError::FailedDowncast {
expected: alloc::borrow::Cow::Borrowed(<T as $crate::type_path::TypePath>::type_path()),
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
}
})
})
.collect::<Result<Self, $crate::error::ReflectCloneError>>()?, .collect::<Result<Self, $crate::error::ReflectCloneError>>()?,
)) ))
} }

View File

@ -146,18 +146,8 @@ macro_rules! impl_reflect_for_hashmap {
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> { fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
let mut map = Self::with_capacity_and_hasher(self.len(), S::default()); let mut map = Self::with_capacity_and_hasher(self.len(), S::default());
for (key, value) in self.iter() { for (key, value) in self.iter() {
let key = key.reflect_clone()?.take().map_err(|_| { let key = key.reflect_clone_and_take()?;
$crate::error::ReflectCloneError::FailedDowncast { let value = value.reflect_clone_and_take()?;
expected: alloc::borrow::Cow::Borrowed(<K as $crate::type_path::TypePath>::type_path()),
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(key.reflect_type_path())),
}
})?;
let value = value.reflect_clone()?.take().map_err(|_| {
$crate::error::ReflectCloneError::FailedDowncast {
expected: alloc::borrow::Cow::Borrowed(<V as $crate::type_path::TypePath>::type_path()),
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
}
})?;
map.insert(key, value); map.insert(key, value);
} }

View File

@ -129,12 +129,7 @@ macro_rules! impl_reflect_for_hashset {
fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> { fn reflect_clone(&self) -> Result<bevy_platform::prelude::Box<dyn $crate::reflect::Reflect>, $crate::error::ReflectCloneError> {
let mut set = Self::with_capacity_and_hasher(self.len(), S::default()); let mut set = Self::with_capacity_and_hasher(self.len(), S::default());
for value in self.iter() { for value in self.iter() {
let value = value.reflect_clone()?.take().map_err(|_| { let value = value.reflect_clone_and_take()?;
$crate::error::ReflectCloneError::FailedDowncast {
expected: alloc::borrow::Cow::Borrowed(<V as $crate::type_path::TypePath>::type_path()),
received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())),
}
})?;
set.insert(value); set.insert(value);
} }

View File

@ -4,7 +4,7 @@ use crate::{
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, TypePath, TypeRegistration, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypeParamInfo, TypePath, TypeRegistration,
Typed, Typed,
}; };
use alloc::{borrow::Cow, boxed::Box, string::ToString, vec::Vec}; use alloc::{boxed::Box, vec::Vec};
use bevy_reflect::ReflectCloneError; use bevy_reflect::ReflectCloneError;
use bevy_reflect_derive::impl_type_path; use bevy_reflect_derive::impl_type_path;
use core::any::Any; use core::any::Any;
@ -137,16 +137,11 @@ where
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> { fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
Ok(Box::new( Ok(Box::new(
self.iter() // `(**self)` avoids getting `SmallVec<T> as List::iter`, which
.map(|value| { // would give us the wrong item type.
value (**self)
.reflect_clone()? .iter()
.take() .map(PartialReflect::reflect_clone_and_take)
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<T::Item as TypePath>::type_path()),
received: Cow::Owned(value.reflect_type_path().to_string()),
})
})
.collect::<Result<Self, ReflectCloneError>>()?, .collect::<Result<Self, ReflectCloneError>>()?,
)) ))
} }

View File

@ -215,7 +215,7 @@ pub enum ReflectRef<'a> {
/// [function-like]: Function /// [function-like]: Function
#[cfg(feature = "functions")] #[cfg(feature = "functions")]
Function(&'a dyn Function), Function(&'a dyn Function),
/// An immutable refeence to an [opaque] type. /// An immutable reference to an [opaque] type.
/// ///
/// [opaque]: ReflectKind::Opaque /// [opaque]: ReflectKind::Opaque
Opaque(&'a dyn PartialReflect), Opaque(&'a dyn PartialReflect),
@ -281,7 +281,7 @@ pub enum ReflectMut<'a> {
/// ///
/// [function-like]: Function /// [function-like]: Function
Function(&'a mut dyn Function), Function(&'a mut dyn Function),
/// A mutable refeence to an [opaque] type. /// A mutable reference to an [opaque] type.
/// ///
/// [opaque]: ReflectKind::Opaque /// [opaque]: ReflectKind::Opaque
Opaque(&'a mut dyn PartialReflect), Opaque(&'a mut dyn PartialReflect),

View File

@ -2615,9 +2615,11 @@ bevy_reflect::tests::Test {
#[reflect(where T: Default)] #[reflect(where T: Default)]
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>); struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
#[expect(dead_code, reason = "Bar is never constructed")]
#[derive(Default, TypePath)] #[derive(Default, TypePath)]
struct Bar; struct Bar;
#[expect(dead_code, reason = "Baz is never constructed")]
#[derive(TypePath)] #[derive(TypePath)]
struct Baz; struct Baz;
@ -2631,6 +2633,7 @@ bevy_reflect::tests::Test {
#[reflect(where)] #[reflect(where)]
struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>); struct Foo<T>(String, #[reflect(ignore)] PhantomData<T>);
#[expect(dead_code, reason = "Bar is never constructed")]
#[derive(TypePath)] #[derive(TypePath)]
struct Bar; struct Bar;
@ -2665,6 +2668,7 @@ bevy_reflect::tests::Test {
#[reflect(where T::Assoc: core::fmt::Display)] #[reflect(where T::Assoc: core::fmt::Display)]
struct Foo<T: Trait>(T::Assoc); struct Foo<T: Trait>(T::Assoc);
#[expect(dead_code, reason = "Bar is never constructed")]
#[derive(TypePath)] #[derive(TypePath)]
struct Bar; struct Bar;
@ -2672,6 +2676,7 @@ bevy_reflect::tests::Test {
type Assoc = usize; type Assoc = usize;
} }
#[expect(dead_code, reason = "Baz is never constructed")]
#[derive(TypePath)] #[derive(TypePath)]
struct Baz; struct Baz;

View File

@ -313,6 +313,24 @@ where
}) })
} }
/// For a type implementing [`PartialReflect`], combines `reflect_clone` and
/// `take` in a useful fashion, automatically constructing an appropriate
/// [`ReflectCloneError`] if the downcast fails.
///
/// This is an associated function, rather than a method, because methods
/// with generic types prevent dyn-compatibility.
fn reflect_clone_and_take<T: 'static>(&self) -> Result<T, ReflectCloneError>
where
Self: TypePath + Sized,
{
self.reflect_clone()?
.take()
.map_err(|_| ReflectCloneError::FailedDowncast {
expected: Cow::Borrowed(<Self as TypePath>::type_path()),
received: Cow::Owned(self.reflect_type_path().to_string()),
})
}
/// Returns a hash of the value (which includes the type). /// Returns a hash of the value (which includes the type).
/// ///
/// If the underlying type does not support hashing, returns `None`. /// If the underlying type does not support hashing, returns `None`.

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