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:
- uses: actions/checkout@v4
- name: Check for typos
uses: crate-ci/typos@v1.33.1
uses: crate-ci/typos@v1.34.0
- name: Typos info
if: failure()
run: |

View File

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

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}
struct CustomMaterial {

View File

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

View File

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

View File

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

View File

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

View File

@ -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_color = { path = "../bevy_color", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
bevy_log = { path = "../bevy_log", version = "0.17.0-dev" }
bevy_math = { path = "../bevy_math", version = "0.17.0-dev" }
bevy_mesh = { path = "../bevy_mesh", version = "0.17.0-dev" }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [

View File

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

View File

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

View File

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

View File

@ -167,7 +167,7 @@ impl AssetServer {
fn sender<A: Asset>(world: &mut World, id: UntypedAssetId) {
world
.resource_mut::<Events<AssetEvent<A>>>()
.send(AssetEvent::LoadedWithDependencies { id: id.typed() });
.write(AssetEvent::LoadedWithDependencies { id: id.typed() });
}
fn failed_sender<A: Asset>(
world: &mut World,
@ -177,7 +177,7 @@ impl AssetServer {
) {
world
.resource_mut::<Events<AssetLoadFailedEvent<A>>>()
.send(AssetLoadFailedEvent {
.write(AssetLoadFailedEvent {
id: id.typed(),
path,
error,
@ -1685,7 +1685,7 @@ pub fn handle_internal_asset_events(world: &mut World) {
}
if !untyped_failures.is_empty() {
world.send_event_batch(untyped_failures);
world.write_event_batch(untyped_failures);
}
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_reflect = { path = "../bevy_reflect", version = "0.17.0-dev" }
bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" }
bevy_derive = { path = "../bevy_derive", version = "0.17.0-dev" }
# other
# TODO: Remove `coreaudio-sys` dep below when updating `cpal`.

View File

@ -3,6 +3,7 @@ use bevy_asset::{Asset, Handle};
use bevy_ecs::prelude::*;
use bevy_math::Vec3;
use bevy_reflect::prelude::*;
use bevy_transform::components::Transform;
/// The way Bevy manages the sound playback.
#[derive(Debug, Clone, Copy, Reflect)]
@ -10,10 +11,10 @@ use bevy_reflect::prelude::*;
pub enum PlaybackMode {
/// Play the sound once. Do nothing when it ends.
///
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
/// 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.
Once,
/// Repeat the sound forever.
@ -27,7 +28,7 @@ pub enum PlaybackMode {
/// Initial settings to be used when audio starts playing.
///
/// 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.
#[derive(Component, Clone, Copy, Debug, Reflect)]
#[reflect(Clone, Default, Component, Debug)]
@ -78,10 +79,10 @@ impl Default for PlaybackSettings {
impl PlaybackSettings {
/// Will play the associated audio source once.
///
/// Note: It is not possible to reuse an `AudioPlayer` after it has finished playing and
/// the underlying `AudioSink` or `SpatialAudioSink` has been drained.
/// Note: It is not possible to reuse an [`AudioPlayer`] after it has finished playing and
/// 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.
pub const ONCE: PlaybackSettings = PlaybackSettings {
mode: PlaybackMode::Once,
@ -164,14 +165,15 @@ impl PlaybackSettings {
/// Settings for the listener for spatial audio sources.
///
/// This must be accompanied by `Transform` and `GlobalTransform`.
/// Only one entity with a `SpatialListener` should be present at any given time.
/// This is accompanied by [`Transform`] and [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
/// Only one entity with a [`SpatialListener`] should be present at any given time.
#[derive(Component, Clone, Debug, Reflect)]
#[require(Transform)]
#[reflect(Clone, Default, Component, Debug)]
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,
/// Right ear position relative to the `GlobalTransform`.
/// Right ear position relative to the [`GlobalTransform`](bevy_transform::prelude::GlobalTransform).
pub right_ear_offset: Vec3,
}
@ -182,7 +184,7 @@ impl Default for 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
/// positioned on the x axis.
@ -203,12 +205,12 @@ impl SpatialListener {
pub struct SpatialScale(pub Vec3);
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 {
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`.
pub const fn new_2d(scale: f32) -> Self {
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),
/// 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.
///
/// 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)]
#[reflect(Component, Clone)]
#[require(PlaybackSettings)]

View File

@ -103,7 +103,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
Entity,
&AudioPlayer<Source>,
&PlaybackSettings,
Option<&GlobalTransform>,
&GlobalTransform,
),
(Without<AudioSink>, Without<SpatialAudioSink>),
>,
@ -118,7 +118,7 @@ pub(crate) fn play_queued_audio_system<Source: Asset + Decodable>(
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 {
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 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 emitter_translation = (emitter_transform.translation() * scale).into();
let sink = match SpatialSink::try_new(
stream_handle,
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_color = { path = "../bevy_color", 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_image = { path = "../bevy_image", 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",
] }
serde = { version = "1", features = ["derive"] }
bitflags = "2.3"
radsort = "0.1"
nonmax = "0.5"
smallvec = { version = "1", default-features = false }
thiserror = { version = "2", default-features = false }
tracing = { version = "0.1", default-features = false, features = ["std"] }
bytemuck = { version = "1" }
[lints]
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_math = { path = "../bevy_math", 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_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
/// shorctuts. Defaults to 1.0.
/// shortcuts. Defaults to 1.0.
#[derive(Component, Debug, PartialEq, Clone)]
#[component(immutable)]
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_diagnostic = { path = "../bevy_diagnostic", 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_render = { path = "../bevy_render", 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_text = { path = "../bevy_text", 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_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);
match event {
CiTestingEvent::AppExit => {
world.send_event(AppExit::Success);
world.write_event(AppExit::Success);
info!("Exiting after {} frames. Test successful!", *current_frame);
}
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.
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_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_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", default-features = false }
bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"alloc",

View File

@ -630,7 +630,7 @@ impl Archetype {
#[inline]
pub fn len(&self) -> u32 {
// 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
}

View File

@ -1584,7 +1584,7 @@ impl<'w> BundleRemover<'w> {
// Handle sparse set removes
for component_id in self.bundle_info.as_ref().iter_explicit_components() {
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.
// 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.
pub fn deny<T: Bundle>(&mut self) -> &mut Self {
let bundle_id = self.world.register_bundle::<T>().id();
self.deny_by_bundle_id(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
self.deny_by_ids(bundle_id)
}
/// 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`
/// is denied as well. See [`Self::without_required_by_components`] to alter
/// this behavior.
pub fn deny_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
for id in ids {
self.filter.filter_deny(id, self.world);
}
self
}
/// 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);
pub fn deny_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
ids.filter_ids(&mut |ids| match ids {
FilterableId::Type(type_id) => {
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
}
}
@ -865,7 +852,7 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
/// to alter this behavior.
pub fn allow<T: Bundle>(&mut self) -> &mut Self {
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
@ -876,94 +863,55 @@ impl<'w> EntityClonerBuilder<'w, OptIn> {
/// to alter this behavior.
pub fn allow_if_new<T: Bundle>(&mut self) -> &mut Self {
let bundle_id = self.world.register_bundle::<T>().id();
self.allow_by_bundle_id_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
self.allow_by_ids_if_new(bundle_id)
}
/// 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`
/// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior.
pub fn allow_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
for id in ids {
self.filter
.filter_allow(id, self.world, InsertMode::Replace);
}
pub fn allow_by_ids<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
self.allow_by_ids_inner(ids, InsertMode::Replace);
self
}
/// 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`
/// is allowed as well. See [`Self::without_required_components`]
/// to alter this behavior.
pub fn allow_by_ids_if_new(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
for id in ids {
self.filter.filter_allow(id, self.world, InsertMode::Keep);
}
pub fn allow_by_ids_if_new<M: Marker>(&mut self, ids: impl FilterableIds<M>) -> &mut Self {
self.allow_by_ids_inner(ids, InsertMode::Keep);
self
}
/// Extends the list of components to clone using [`TypeId`]s.
///
/// 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(&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) {
fn allow_by_ids_inner<M: Marker>(
&mut self,
ids: impl FilterableIds<M>,
insert_mode: InsertMode,
) {
ids.filter_ids(&mut |id| match id {
FilterableId::Type(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
.filter_allow(id, self.world, InsertMode::Replace);
.filter_allow(component_id, self.world, insert_mode);
}
}
self
}
/// Extends the list of components to clone using [`TypeId`]s 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_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);
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_allow(id, self.world, insert_mode);
}
}
}
}
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)]
mod tests {
use super::*;

View File

@ -54,8 +54,8 @@ use {
/// // run this once per update/frame
/// events.update();
///
/// // somewhere else: send an event
/// events.send(MyEvent { value: 1 });
/// // somewhere else: write an event
/// events.write(MyEvent { value: 1 });
///
/// // somewhere else: read the events
/// for event in cursor.read(&events) {
@ -118,22 +118,22 @@ impl<E: BufferedEvent> Events<E> {
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.
/// This method returns the [ID](`EventId`) of the sent `event`.
/// This method returns the [ID](`EventId`) of the written `event`.
#[track_caller]
pub fn send(&mut self, event: E) -> EventId<E> {
self.send_with_caller(event, MaybeLocation::caller())
pub fn write(&mut self, event: E) -> EventId<E> {
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 {
id: self.event_count,
caller,
_marker: PhantomData,
};
#[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 };
@ -143,30 +143,59 @@ impl<E: BufferedEvent> Events<E> {
event_id
}
/// 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`.
/// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than writing each event individually.
/// This method returns the [IDs](`EventId`) of the written `events`.
#[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;
self.extend(events);
SendBatchIds {
WriteBatchIds {
last_count,
event_count: self.event_count,
_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.
/// This method returns the [ID](`EventId`) of the sent `event`.
#[deprecated(since = "0.17.0", note = "Use `Events<E>::write_default` instead.")]
#[track_caller]
pub fn send_default(&mut self) -> EventId<E>
where
E: Default,
{
self.send(Default::default())
self.write_default()
}
/// 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.
pub struct SendBatchIds<E> {
/// [`Iterator`] over written [`EventIds`](`EventId`) from a batch.
pub struct WriteBatchIds<E> {
last_count: usize,
event_count: usize,
_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>;
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 {
self.event_count.saturating_sub(self.last_count)
}
@ -400,22 +433,22 @@ mod tests {
assert_eq!(test_events.iter_current_update_events().count(), 0);
test_events.update();
// Sending one event
test_events.send(TestEvent);
// Writing one event
test_events.write(TestEvent);
assert_eq!(test_events.len(), 1);
assert_eq!(test_events.iter_current_update_events().count(), 1);
test_events.update();
// Sending two events on the next frame
test_events.send(TestEvent);
test_events.send(TestEvent);
// Writing two events on the next frame
test_events.write(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.iter_current_update_events().count(), 2);
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.iter_current_update_events().count(), 0);
}

View File

@ -41,7 +41,7 @@ use core::marker::PhantomData;
/// }
///
/// 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 use base::{BufferedEvent, EntityEvent, Event, EventId, EventKey};
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;
#[cfg(feature = "multi_threaded")]
pub use iterators::EventParIter;
@ -68,7 +69,7 @@ mod tests {
let mut reader_a: EventCursor<TestEvent> = events.get_cursor();
events.send(event_0);
events.write(event_0);
assert_eq!(
get_events(&events, &mut reader_a),
@ -94,7 +95,7 @@ mod tests {
"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();
@ -119,7 +120,7 @@ mod tests {
let mut reader_d = events.get_cursor();
events.send(event_2);
events.write(event_2);
assert_eq!(
get_events(&events, &mut reader_a),
@ -153,17 +154,17 @@ mod tests {
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(), None);
events.send(TestEvent { i: 1 });
events.write(TestEvent { i: 1 });
clear_func(&mut events);
assert!(reader.read(&events).next().is_none());
events.send(TestEvent { i: 2 });
events.write(TestEvent { i: 2 });
events.update();
events.send(TestEvent { i: 3 });
events.write(TestEvent { i: 3 });
assert!(reader
.read(&events)
@ -185,22 +186,22 @@ mod tests {
}
#[test]
fn test_events_send_default() {
fn test_events_write_default() {
let mut events = Events::<EmptyTestEvent>::default();
events.send_default();
events.write_default();
let mut reader = events.get_cursor();
assert_eq!(get_events(&events, &mut reader), vec![EmptyTestEvent]);
}
#[test]
fn test_send_events_ids() {
fn test_write_events_ids() {
let mut events = Events::<TestEvent>::default();
let event_0 = TestEvent { i: 0 };
let event_1 = TestEvent { i: 1 };
let event_2 = TestEvent { i: 2 };
let event_0_id = events.send(event_0);
let event_0_id = events.write(event_0);
assert_eq!(
events.get_event(event_0_id.id),
@ -208,7 +209,7 @@ mod tests {
"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");
@ -253,14 +254,14 @@ mod tests {
let mut events = Events::<TestEvent>::default();
let mut reader = events.get_cursor();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 1 });
events.write(TestEvent { i: 0 });
events.write(TestEvent { i: 1 });
assert_eq!(reader.read(&events).count(), 2);
let mut old_events = Vec::from_iter(events.update_drain());
assert!(old_events.is_empty());
events.send(TestEvent { i: 2 });
events.write(TestEvent { i: 2 });
assert_eq!(reader.read(&events).count(), 1);
old_events.extend(events.update_drain());
@ -278,7 +279,7 @@ mod tests {
let mut events = Events::<TestEvent>::default();
assert!(events.is_empty());
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
assert!(!events.is_empty());
events.update();
@ -308,12 +309,12 @@ mod tests {
let mut cursor = events.get_cursor();
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();
assert_eq!(sent_event, &TestEvent { i: 0 });
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();
assert_eq!(sent_event, &TestEvent { i: 2 });
assert!(cursor.read(&events).next().is_none());
@ -330,7 +331,7 @@ mod tests {
assert!(write_cursor.read_mut(&mut 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();
assert_eq!(sent_event, &mut TestEvent { i: 0 });
*sent_event = TestEvent { i: 1 }; // Mutate whole event
@ -340,7 +341,7 @@ mod tests {
);
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();
assert_eq!(sent_event, &mut TestEvent { i: 2 });
sent_event.i = 3; // Mutate sub value
@ -360,7 +361,7 @@ mod tests {
let mut events = Events::<TestEvent>::default();
let mut reader = events.get_cursor();
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 1);
reader.clear(&events);
assert_eq!(reader.len(&events), 0);
@ -369,12 +370,12 @@ mod tests {
#[test]
fn test_event_cursor_len_update() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
let reader = events.get_cursor();
assert_eq!(reader.len(&events), 2);
events.update();
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 3);
events.update();
assert_eq!(reader.len(&events), 1);
@ -385,10 +386,10 @@ mod tests {
#[test]
fn test_event_cursor_len_current() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
let reader = events.get_cursor_current();
assert!(reader.is_empty(&events));
events.send(TestEvent { i: 0 });
events.write(TestEvent { i: 0 });
assert_eq!(reader.len(&events), 1);
assert!(!reader.is_empty(&events));
}
@ -396,9 +397,9 @@ mod tests {
#[test]
fn test_event_cursor_iter_len_updated() {
let mut events = Events::<TestEvent>::default();
events.send(TestEvent { i: 0 });
events.send(TestEvent { i: 1 });
events.send(TestEvent { i: 2 });
events.write(TestEvent { i: 0 });
events.write(TestEvent { i: 1 });
events.write(TestEvent { i: 2 });
let mut reader = events.get_cursor();
let mut iter = reader.read(&events);
assert_eq!(iter.len(), 3);
@ -420,7 +421,7 @@ mod tests {
#[test]
fn test_event_cursor_len_filled() {
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!(!events.get_cursor().is_empty(&events));
}
@ -437,7 +438,7 @@ mod tests {
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
for _ in 0..100 {
world.send_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 1 });
}
let mut schedule = Schedule::default();
@ -479,7 +480,7 @@ mod tests {
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
for _ in 0..100 {
world.send_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 1 });
}
let mut schedule = Schedule::default();
schedule.add_systems(
@ -531,13 +532,13 @@ mod tests {
let last = reader.run((), &mut world).unwrap();
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();
assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.write_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 2 });
world.write_event(TestEvent { i: 3 });
let last = reader.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 3 }));
@ -561,13 +562,13 @@ mod tests {
let last = mutator.run((), &mut world).unwrap();
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();
assert_eq!(last, Some(TestEvent { i: 0 }));
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.write_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 2 });
world.write_event(TestEvent { i: 3 });
let last = mutator.run((), &mut world).unwrap();
assert_eq!(last, Some(TestEvent { i: 3 }));
@ -582,11 +583,11 @@ mod tests {
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 });
world.write_event(TestEvent { i: 0 });
world.write_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 2 });
world.write_event(TestEvent { i: 3 });
world.write_event(TestEvent { i: 4 });
let mut schedule = Schedule::default();
schedule.add_systems(|mut events: EventReader<TestEvent>| {
@ -608,11 +609,11 @@ mod tests {
let mut world = World::new();
world.init_resource::<Events<TestEvent>>();
world.send_event(TestEvent { i: 0 });
world.send_event(TestEvent { i: 1 });
world.send_event(TestEvent { i: 2 });
world.send_event(TestEvent { i: 3 });
world.send_event(TestEvent { i: 4 });
world.write_event(TestEvent { i: 0 });
world.write_event(TestEvent { i: 1 });
world.write_event(TestEvent { i: 2 });
world.write_event(TestEvent { i: 3 });
world.write_event(TestEvent { i: 4 });
let mut schedule = Schedule::default();
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 {
/// world.send_event(MyEvent { value });
/// world.write_event(MyEvent { value });
/// }
/// schedule.run(&mut world);
/// 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 {
/// world.send_event(MyEvent { value });
/// world.write_event(MyEvent { value });
/// }
/// schedule.run(&mut world);
/// let Counter(counter) = world.remove_resource::<Counter>().unwrap();

View File

@ -1,9 +1,9 @@
use bevy_ecs::{
event::{BufferedEvent, EventId, Events, SendBatchIds},
event::{BufferedEvent, EventId, Events, WriteBatchIds},
system::{ResMut, SystemParam},
};
/// Sends [`BufferedEvent`]s of type `T`.
/// Writes [`BufferedEvent`]s of type `T`.
///
/// # Usage
///
@ -34,14 +34,14 @@ use bevy_ecs::{
///
/// `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
/// 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};
/// # #[derive(Event, BufferedEvent)]
/// # pub struct MyEvent;
/// fn send_untyped(mut commands: Commands) {
/// // Send an event of a specific type without having to declare that
/// fn write_untyped(mut commands: Commands) {
/// // Write an event of a specific type without having to declare that
/// // type as a SystemParam.
/// //
/// // 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
/// // apply_deferred.
/// 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")]
#[track_caller]
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.
/// This is more efficient than sending each event individually.
/// Writes a list of `events` all at once, which can later be read by [`EventReader`](super::EventReader)s.
/// This is more efficient than writing each event individually.
/// This method returns the [IDs](`EventId`) of the written `events`.
///
/// See [`Events`] for details.
#[doc(alias = "send_batch")]
#[track_caller]
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> SendBatchIds<E> {
self.events.send_batch(events)
pub fn write_batch(&mut self, events: impl IntoIterator<Item = E>) -> WriteBatchIds<E> {
self.events.write_batch(events)
}
/// 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
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 {
return;
};
if !world
.get_entity(child_of.parent())
.is_ok_and(|e| e.contains::<C>())
{
let parent = child_of.parent();
if !world.get_entity(parent).is_ok_and(|e| e.contains::<C>()) {
// TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = None;
let debug_name = DebugName::type_name::<C>();
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",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = debug_name.shortname(),

View File

@ -465,10 +465,16 @@ impl RemovedComponentEvents {
}
/// 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) {
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
.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| {
relationship_target.collection_mut_risky().add(entity);
})
.or_insert_with(|| {
.or_insert_with(move || {
let mut target = Self::RelationshipTarget::with_capacity(1);
target.collection_mut_risky().add(entity);
target

View File

@ -875,7 +875,7 @@ pub mod common_conditions {
/// app.run(&mut world);
/// 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
/// app.run(&mut world);

View File

@ -597,7 +597,7 @@ impl Table {
#[inline]
pub fn entity_count(&self) -> u32 {
// 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
}

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]
pub fn send_event<E: BufferedEvent>(event: E) -> impl Command {
pub fn write_event<E: BufferedEvent>(event: E) -> impl Command {
let caller = MaybeLocation::caller();
move |world: &mut World| {
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
/// 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]
pub fn insert_from_world<T: Component + FromWorld>(mode: InsertMode) -> impl EntityCommand {
let caller = MaybeLocation::caller();
move |mut entity: EntityWorldMut| {
let value = entity.world_scope(|world| T::from_world(world));
entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run);
if !(mode == InsertMode::Keep && entity.contains::<T>()) {
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))
}
/// 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).
///
/// # Performance
@ -1137,11 +1137,29 @@ impl<'w, 's> Commands<'w, 's> {
/// If these events are performance-critical or very frequently sent,
/// consider using a typed [`EventWriter`](crate::event::EventWriter) instead.
#[track_caller]
pub fn send_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
self.queue(command::send_event(event));
pub fn write_event<E: BufferedEvent>(&mut self, event: E) -> &mut Self {
self.queue(command::write_event(event));
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`].
///
/// 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,
/// if `T` is not already present.
///
/// `default` will only be invoked if the component will actually be inserted.
#[track_caller]
pub fn or_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
self.or_insert(default())
pub fn or_insert_with<F>(&mut self, default: F) -> &mut Self
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,
/// if `T` is not already present.
///
/// `default` will only be invoked if the component will actually be inserted.
///
/// # Note
///
/// If the entity does not exist when this command is executed,
/// the resulting error will be ignored.
#[track_caller]
pub fn or_try_insert_with(&mut self, default: impl Fn() -> T) -> &mut Self {
self.or_try_insert(default())
pub fn or_try_insert_with<F>(&mut self, default: F) -> &mut Self
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,
/// if `T` is not already present.
///
/// `T::default` will only be invoked if the component will actually be inserted.
#[track_caller]
pub fn or_default(&mut self) -> &mut Self
where
T: Default,
{
self.or_insert(T::default())
self.or_insert_with(T::default)
}
/// [Insert](EntityCommands::insert) `T::from_world` into this entity,
/// if `T` is not already present.
///
/// `T::from_world` will only be invoked if the component will actually be inserted.
#[track_caller]
pub fn or_from_world(&mut self) -> &mut Self
where
@ -2396,6 +2432,12 @@ mod tests {
}
}
impl Default for W<u8> {
fn default() -> Self {
unreachable!()
}
}
#[test]
fn entity_commands_entry() {
let mut world = World::default();
@ -2435,6 +2477,17 @@ mod tests {
let id = commands.entity(entity).entry::<W<u64>>().entity().id();
queue.apply(&mut world);
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]

View File

@ -7,7 +7,7 @@ use crate::{
change_detection::{MaybeLocation, MutUntyped},
component::{ComponentId, Mutable},
entity::Entity,
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, SendBatchIds},
event::{BufferedEvent, EntityEvent, Event, EventId, EventKey, Events, WriteBatchIds},
lifecycle::{HookContext, INSERT, REPLACE},
observer::{Observers, TriggerTargets},
prelude::{Component, QueryState},
@ -507,30 +507,51 @@ impl<'w> DeferredWorld<'w> {
unsafe { self.world.get_non_send_resource_mut() }
}
/// Sends a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
/// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[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>> {
self.send_event_batch(core::iter::once(event))?.next()
self.write_event(event)
}
/// Sends the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
/// 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]
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>> {
self.send_event(E::default())
self.write_event_default::<E>()
}
/// Sends a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the sent `events`,
/// or [`None`] if the `event` could not be sent.
/// 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]
pub fn send_event_batch<E: BufferedEvent>(
pub fn write_event_batch<E: BufferedEvent>(
&mut self,
events: impl IntoIterator<Item = E>,
) -> Option<SendBatchIds<E>> {
) -> Option<WriteBatchIds<E>> {
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
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 ",
@ -538,7 +559,22 @@ impl<'w> DeferredWorld<'w> {
);
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.

View File

@ -2418,7 +2418,7 @@ impl<'w> EntityWorldMut<'w> {
}
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

View File

@ -50,7 +50,7 @@ use crate::{
},
entity::{Entities, Entity, EntityDoesNotExistError},
entity_disabling::DefaultQueryFilters,
event::{Event, EventId, Events, SendBatchIds},
event::{Event, EventId, Events, WriteBatchIds},
lifecycle::RemovedComponentEvents,
observer::Observers,
query::{DebugCheckedUnwrap, QueryData, QueryFilter, QueryState},
@ -2626,30 +2626,48 @@ impl World {
Some(result)
}
/// Sends a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
/// Writes a [`BufferedEvent`].
/// This method returns the [ID](`EventId`) of the written `event`,
/// or [`None`] if the `event` could not be written.
#[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>> {
self.send_event_batch(core::iter::once(event))?.next()
self.write_event(event)
}
/// Sends the default value of the [`BufferedEvent`] of type `E`.
/// This method returns the [ID](`EventId`) of the sent `event`,
/// or [`None`] if the `event` could not be sent.
/// 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]
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>> {
self.send_event(E::default())
self.write_event_default::<E>()
}
/// Sends a batch of [`BufferedEvent`]s from an iterator.
/// This method returns the [IDs](`EventId`) of the sent `events`,
/// or [`None`] if the `event` could not be sent.
/// 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]
pub fn send_event_batch<E: BufferedEvent>(
pub fn write_event_batch<E: BufferedEvent>(
&mut self,
events: impl IntoIterator<Item = E>,
) -> Option<SendBatchIds<E>> {
) -> Option<WriteBatchIds<E>> {
let Some(mut events_resource) = self.get_resource_mut::<Events<E>>() else {
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 ",
@ -2657,7 +2675,19 @@ impl World {
);
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.

View File

@ -13,7 +13,6 @@ keywords = ["bevy"]
bevy_app = { path = "../bevy_app", 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_utils = { path = "../bevy_utils", 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 = [
"std",

View File

@ -16,7 +16,6 @@ proc-macro = true
bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" }
syn = "2.0"
proc-macro2 = "1.0"
quote = "1.0"
[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_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 = [
"std",
"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_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_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false }
bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", features = [
"glam",
], default-features = false, optional = true }

View File

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

View File

@ -546,7 +546,7 @@ mod tests {
assert!(!app.world().is_focus_visible(child_of_b));
// entity_a should receive this event
app.world_mut().send_event(key_a_event());
app.world_mut().write_event(key_a_event());
app.update();
assert_eq!(get_gathered(&app, entity_a), "A");
@ -559,7 +559,7 @@ mod tests {
assert!(!app.world().is_focus_visible(entity_a));
// This event should be lost
app.world_mut().send_event(key_a_event());
app.world_mut().write_event(key_a_event());
app.update();
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
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();
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_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
dynamic_linking = ["bevy_diagnostic/dynamic_linking"]
@ -218,7 +222,7 @@ bevy_render = [
"dep:bevy_render",
"bevy_scene?/bevy_render",
"bevy_gizmos?/bevy_render",
"bevy_image",
"bevy_camera",
"bevy_color/wgpu-types",
"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_feathers = { path = "../bevy_feathers", 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_reflect",
] }

View File

@ -25,6 +25,8 @@ pub use bevy_app as app;
pub use bevy_asset as asset;
#[cfg(feature = "bevy_audio")]
pub use bevy_audio as audio;
#[cfg(feature = "bevy_camera")]
pub use bevy_camera as camera;
#[cfg(feature = "bevy_color")]
pub use bevy_color as color;
#[cfg(feature = "bevy_core_pipeline")]
@ -48,9 +50,13 @@ pub use bevy_image as image;
pub use bevy_input as input;
#[cfg(feature = "bevy_input_focus")]
pub use bevy_input_focus as input_focus;
#[cfg(feature = "bevy_light")]
pub use bevy_light as light;
#[cfg(feature = "bevy_log")]
pub use bevy_log as log;
pub use bevy_math as math;
#[cfg(feature = "bevy_mesh")]
pub use bevy_mesh as mesh;
#[cfg(feature = "bevy_pbr")]
pub use bevy_pbr as pbr;
#[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_ecs::prelude::*;
use bevy_reflect::prelude::*;
use bevy_render::{extract_component::ExtractComponent, extract_resource::ExtractResource};
/// 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_pbr::AmbientLight;
/// # use bevy_light::AmbientLight;
/// fn setup_ambient_light(mut ambient_light: ResMut<AmbientLight>) {
/// ambient_light.brightness = 100.0;
/// }
/// ```
///
/// [`LightPlugin`]: crate::LightPlugin
#[derive(Resource, Component, Clone, Debug, ExtractResource, ExtractComponent, Reflect)]
#[derive(Resource, Component, Clone, Debug, Reflect)]
#[reflect(Resource, Component, Debug, Default, Clone)]
#[require(Camera)]
pub struct AmbientLight {

View File

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

View File

@ -22,10 +22,7 @@ use super::{
ClusterConfig, ClusterFarZMode, ClusteredDecal, Clusters, GlobalClusterSettings,
GlobalVisibleClusterableObjects, VisibleClusterableObjects,
};
use crate::{
prelude::EnvironmentMapLight, ExtractedPointLight, LightProbe, PointLight, SpotLight,
VolumetricLight,
};
use crate::{EnvironmentMapLight, LightProbe, PointLight, SpotLight, VolumetricLight};
const NDC_MIN: Vec2 = Vec2::NEG_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
/// clusterable object.
#[derive(Clone, Copy, Debug)]
pub(crate) enum ClusterableObjectType {
pub enum ClusterableObjectType {
/// Data needed to assign point lights to clusters.
PointLight {
/// 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
/// are enabled (enabled before disabled), and then whether volumetrics are
/// enabled (enabled before disabled).
pub(crate) fn ordering(&self) -> (u8, bool, bool) {
pub fn ordering(&self) -> (u8, bool, bool) {
match *self {
ClusterableObjectType::PointLight {
shadows_enabled,
@ -121,23 +118,6 @@ impl ClusterableObjectType {
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!

View File

@ -17,15 +17,10 @@ use bevy_image::Image;
use bevy_math::{AspectRatio, UVec2, UVec3, Vec3Swizzles as _};
use bevy_platform::collections::HashSet;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::extract_component::ExtractComponent;
use bevy_transform::components::Transform;
use tracing::warn;
pub(crate) use crate::cluster::assign::assign_objects_to_clusters;
pub(crate) mod assign;
mod extract_and_prepare;
pub use extract_and_prepare::*;
pub mod assign;
#[cfg(test)]
mod test;
@ -106,14 +101,14 @@ pub enum ClusterConfig {
#[derive(Component, Debug, Default)]
pub struct Clusters {
/// Tile size
pub(crate) tile_size: UVec2,
pub tile_size: UVec2,
/// 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
/// and explicitly-configured to avoid having unnecessarily many slices close to the camera.
pub(crate) near: f32,
pub(crate) far: f32,
pub(crate) clusterable_objects: Vec<VisibleClusterableObjects>,
pub near: f32,
pub far: f32,
pub clusterable_objects: Vec<VisibleClusterableObjects>,
}
/// 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)]
pub struct VisibleClusterableObjects {
pub(crate) entities: Vec<Entity>,
counts: ClusterableObjectCounts,
pub entities: Vec<Entity>,
pub counts: ClusterableObjectCounts,
}
#[derive(Resource, Default)]
@ -137,17 +132,17 @@ pub struct GlobalVisibleClusterableObjects {
/// Note that `reflection_probes` and `irradiance_volumes` won't be clustered if
/// fewer than 3 SSBOs are available, which usually means on WebGL 2.
#[derive(Clone, Copy, Default, Debug)]
struct ClusterableObjectCounts {
pub struct ClusterableObjectCounts {
/// The number of point lights in the cluster.
point_lights: u32,
pub point_lights: u32,
/// The number of spot lights in the cluster.
spot_lights: u32,
pub spot_lights: u32,
/// The number of reflection probes in the cluster.
reflection_probes: u32,
pub reflection_probes: u32,
/// The number of irradiance volumes in the cluster.
irradiance_volumes: u32,
pub irradiance_volumes: u32,
/// The number of decals in the cluster.
decals: u32,
pub decals: u32,
}
/// 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
/// 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.
#[derive(Component, Debug, Clone, Reflect, ExtractComponent)]
#[derive(Component, Debug, Clone, Reflect)]
#[reflect(Component, Debug, Clone)]
#[require(Transform, Visibility, VisibilityClass)]
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]

View File

@ -1,6 +1,6 @@
use bevy_math::UVec2;
use crate::{ClusterConfig, Clusters};
use super::{ClusterConfig, Clusters};
fn test_cluster_tiling(config: ClusterConfig, screen_size: UVec2) -> Clusters {
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_transform::components::Transform;
use super::{cascade::CascadeShadowConfig, light_consts, Cascades};
use crate::cluster::ClusterVisibilityClass;
use super::{
cascade::CascadeShadowConfig, cluster::ClusterVisibilityClass, light_consts, Cascades,
};
/// A Directional light.
///
@ -172,7 +173,7 @@ pub struct DirectionalLightTexture {
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_pbr::DirectionalLightShadowMap;
/// # use bevy_light::DirectionalLightShadowMap;
/// App::new()
/// .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_camera::{
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere},
@ -10,21 +12,27 @@ use bevy_camera::{
};
use bevy_ecs::{entity::EntityHashSet, prelude::*};
use bevy_math::Vec3A;
use bevy_mesh::Mesh3d;
use bevy_reflect::prelude::*;
use bevy_render::{extract_component::ExtractComponent, mesh::Mesh3d};
use bevy_transform::{components::GlobalTransform, TransformSystems};
use bevy_utils::Parallel;
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;
pub use ambient_light::AmbientLight;
mod probe;
pub use probe::{EnvironmentMapLight, IrradianceVolume, LightProbe};
mod volumetric;
pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight};
pub mod cascade;
use cascade::{
build_directional_light_cascades, clear_directional_light_cascades, CascadeShadowConfig,
Cascades,
};
use cascade::{build_directional_light_cascades, clear_directional_light_cascades};
pub use cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
mod point_light;
pub use point_light::{
update_point_light_frusta, PointLight, PointLightShadowMap, PointLightTexture,
@ -111,9 +119,16 @@ impl Plugin for LightPlugin {
.register_type::<NotShadowCaster>()
.register_type::<NotShadowReceiver>()
.register_type::<PointLight>()
.register_type::<LightProbe>()
.register_type::<EnvironmentMapLight>()
.register_type::<IrradianceVolume>()
.register_type::<VolumetricFog>()
.register_type::<VolumetricLight>()
.register_type::<PointLightShadowMap>()
.register_type::<SpotLight>()
.register_type::<ShadowFilteringMethod>()
.register_type::<ClusterConfig>()
.init_resource::<GlobalVisibleClusterableObjects>()
.init_resource::<AmbientLight>()
.init_resource::<DirectionalLightShadowMap>()
.init_resource::<PointLightShadowMap>()
@ -182,7 +197,7 @@ impl Plugin for LightPlugin {
}
/// 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>)>;
/// Add this component to make a [`Mesh3d`] not cast shadows.
@ -197,10 +212,10 @@ pub struct NotShadowCaster;
#[derive(Debug, Component, Reflect, Default)]
#[reflect(Component, Default, Debug)]
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”)
///
/// 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.
///
/// **Note:** Using [`NotShadowReceiver`] overrides this component.
@ -208,12 +223,12 @@ pub struct NotShadowReceiver;
#[reflect(Component, Default, Debug)]
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.
///
/// 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).
#[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)]
pub enum ShadowFilteringMethod {
/// Hardware 2x2.

View File

@ -166,7 +166,7 @@ pub struct PointLightTexture {
///
/// ```
/// # use bevy_app::prelude::*;
/// # use bevy_pbr::PointLightShadowMap;
/// # use bevy_light::PointLightShadowMap;
/// App::new()
/// .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_camera::{
primitives::Frustum,
visibility::{self, Visibility, VisibilityClass},
visibility::{self, Visibility, VisibilityClass, VisibleMeshEntities},
};
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::{Mat4, Vec4};
use bevy_reflect::prelude::*;
use bevy_render::view::VisibleMeshEntities;
use bevy_transform::components::{GlobalTransform, Transform};
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::fmt;
use derive_more::derive::Into;
#[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))]
impl approx::AbsDiffEq for Dir2 {
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")]
impl approx::AbsDiffEq for Dir3 {
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")]
impl approx::AbsDiffEq for Dir3A {
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")]
impl approx::AbsDiffEq for Dir4 {
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_mikktspace = { path = "../bevy_mikktspace", 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 = [
"std",
"serialize",

View File

@ -9,19 +9,19 @@ license = "MIT OR Apache-2.0"
keywords = ["bevy"]
[features]
webgl = []
webgpu = []
webgl = ["bevy_light/webgl"]
webgpu = ["bevy_light/webgpu"]
pbr_transmission_textures = []
pbr_multi_layer_material_textures = []
pbr_anisotropy_texture = []
experimental_pbr_pcss = []
experimental_pbr_pcss = ["bevy_light/experimental_pbr_pcss"]
pbr_specular_textures = []
pbr_clustered_decals = []
pbr_light_textures = []
shader_format_glsl = ["bevy_render/shader_format_glsl"]
trace = ["bevy_render/trace"]
# 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
meshlet_processor = [
"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_diagnostic = { path = "../bevy_diagnostic", 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_math = { path = "../bevy_math", 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_tasks = { path = "../bevy_tasks", version = "0.17.0-dev", optional = true }
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_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"std",
] }
@ -63,14 +65,12 @@ lz4_flex = { version = "0.11", default-features = false, features = [
"frame",
], optional = true }
range-alloc = { version = "0.1.3", optional = true }
half = { version = "2", features = ["bytemuck"], optional = true }
meshopt = { version = "0.4.1", optional = true }
metis = { version = "0.2", optional = true }
itertools = { version = "0.14", optional = true }
bitvec = { version = "1", optional = true }
# direct dependency required for derive macro
bytemuck = { version = "1", features = ["derive", "must_cast"] }
radsort = "0.1"
smallvec = { version = "1", default-features = false }
nonmax = "0.5"
static_assertions = "1"

View File

@ -2,6 +2,7 @@ use core::num::NonZero;
use bevy_camera::Camera;
use bevy_ecs::{entity::EntityHashMap, prelude::*};
use bevy_light::cluster::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
use bevy_math::{uvec4, UVec3, UVec4, Vec4};
use bevy_render::{
render_resource::{
@ -13,7 +14,6 @@ use bevy_render::{
};
use tracing::warn;
use super::{ClusterableObjectCounts, Clusters, GlobalClusterSettings};
use crate::MeshPipeline;
// 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},
};
use bevy_image::Image;
pub use bevy_light::cluster::ClusteredDecal;
use bevy_light::{DirectionalLightTexture, PointLightTexture, SpotLightTexture};
use bevy_math::Mat4;
use bevy_platform::collections::HashMap;
pub use bevy_render::primitives::CubemapLayout;
@ -47,9 +49,7 @@ use bevy_render::{
use bevy_transform::components::GlobalTransform;
use bytemuck::{Pod, Zeroable};
pub use crate::ClusteredDecal;
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.
///

View File

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

View File

@ -31,7 +31,6 @@ pub mod decal;
pub mod deferred;
mod extended_material;
mod fog;
mod light;
mod light_probe;
mod lightmap;
mod material;
@ -48,12 +47,19 @@ mod volumetric_fog;
use bevy_color::{Color, LinearRgba};
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 components::*;
pub use decal::clustered::ClusteredDecalPlugin;
pub use extended_material::*;
pub use fog::*;
pub use light::*;
pub use light_probe::*;
pub use lightmap::*;
pub use material::*;
@ -65,7 +71,7 @@ pub use prepass::*;
pub use render::*;
pub use ssao::*;
pub use ssr::*;
pub use volumetric_fog::{FogVolume, VolumetricFog, VolumetricFogPlugin, VolumetricLight};
pub use volumetric_fog::VolumetricFogPlugin;
/// The PBR prelude.
///
@ -74,14 +80,17 @@ pub mod prelude {
#[doc(hidden)]
pub use crate::{
fog::{DistanceFog, FogFalloff},
light::{light_consts, AmbientLight, DirectionalLight, PointLight, SpotLight},
light_probe::{environment_map::EnvironmentMapLight, LightProbe},
material::{Material, MaterialPlugin},
mesh_material::MeshMaterial3d,
parallax::ParallaxMappingMethod,
pbr_material::StandardMaterial,
ssao::ScreenSpaceAmbientOcclusionPlugin,
};
#[doc(hidden)]
pub use bevy_light::{
light_consts, AmbientLight, DirectionalLight, EnvironmentMapLight, LightProbe, PointLight,
SpotLight,
};
}
pub mod graph {
@ -122,7 +131,6 @@ pub mod graph {
}
}
pub use crate::cascade::{CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades};
use crate::{deferred::DeferredPbrLightingPlugin, graph::NodePbr};
use bevy_app::prelude::*;
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");
app.register_asset_reflect::<StandardMaterial>()
.register_type::<ClusterConfig>()
.init_resource::<GlobalVisibleClusterableObjects>()
.register_type::<DefaultOpaqueRendererMethod>()
.init_resource::<DefaultOpaqueRendererMethod>()
.add_plugins((

View File

@ -44,13 +44,10 @@
//!
//! [several pre-filtered environment maps]: https://github.com/KhronosGroup/glTF-Sample-Environments
use bevy_asset::{AssetId, Handle};
use bevy_ecs::{
component::Component, query::QueryItem, reflect::ReflectComponent, system::lifetimeless::Read,
};
use bevy_asset::AssetId;
use bevy_ecs::{query::QueryItem, system::lifetimeless::Read};
use bevy_image::Image;
use bevy_math::Quat;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_light::EnvironmentMapLight;
use bevy_render::{
extract_instances::ExtractInstance,
render_asset::RenderAssets,
@ -72,56 +69,6 @@ use crate::{
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.
///
/// This is for use in the render app.

View File

@ -133,8 +133,8 @@
//!
//! [Why ambient cubes?]: #why-ambient-cubes
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_image::Image;
pub use bevy_light::IrradianceVolume;
use bevy_render::{
render_asset::RenderAssets,
render_resource::{
@ -144,71 +144,22 @@ use bevy_render::{
renderer::{RenderAdapter, RenderDevice},
texture::{FallbackImage, GpuImage},
};
use bevy_utils::default;
use core::{num::NonZero, ops::Deref};
use bevy_asset::{AssetId, Handle};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_asset::AssetId;
use crate::{
add_cubemap_texture_view, binding_arrays_are_usable, RenderViewLightProbes,
MAX_VIEW_LIGHT_PROBES,
};
use super::{LightProbe, LightProbeComponent};
use super::LightProbeComponent;
/// 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
/// (see issue #11885).
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
/// irradiance volumes exposed to a view.
pub(crate) enum RenderViewIrradianceVolumeBindGroupEntries<'a> {

View File

@ -8,15 +8,14 @@ use bevy_ecs::{
component::Component,
entity::Entity,
query::With,
reflect::ReflectComponent,
resource::Resource,
schedule::IntoScheduleConfigs,
system::{Commands, Local, Query, Res, ResMut},
};
use bevy_image::Image;
use bevy_light::{EnvironmentMapLight, LightProbe};
use bevy_math::{Affine3A, FloatOrd, Mat4, Vec3A, Vec4};
use bevy_platform::collections::HashMap;
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
extract_instances::ExtractInstancesPlugin,
load_shader_library,
@ -27,7 +26,7 @@ use bevy_render::{
settings::WgpuFeatures,
sync_world::RenderEntity,
texture::{FallbackImage, GpuImage},
view::{ExtractedView, Visibility},
view::ExtractedView,
Extract, ExtractSchedule, Render, RenderApp, RenderSystems,
};
use bevy_transform::{components::Transform, prelude::GlobalTransform};
@ -35,7 +34,7 @@ use tracing::error;
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;
@ -59,50 +58,6 @@ const STANDARD_MATERIAL_FRAGMENT_SHADER_MIN_TEXTURE_BINDINGS: usize = 16;
/// cubemaps applied to all objects that a view renders.
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.
#[derive(Clone, Copy, ShaderType, Default)]
struct RenderLightProbe {
@ -302,14 +257,6 @@ pub trait LightProbeComponent: Send + Sync + Component + Sized {
) -> RenderViewLightProbes<Self>;
}
impl LightProbe {
/// Creates a new light probe component.
#[inline]
pub fn new() -> Self {
Self
}
}
/// The uniform struct extracted from [`EnvironmentMapLight`].
/// Will be available for use in the Environment Map shader.
#[derive(Component, ShaderType, Clone)]
@ -341,10 +288,7 @@ impl Plugin for LightProbePlugin {
load_shader_library!(app, "environment_map.wgsl");
load_shader_library!(app, "irradiance_volume.wgsl");
app.register_type::<LightProbe>()
.register_type::<EnvironmentMapLight>()
.register_type::<IrradianceVolume>()
.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
app.add_plugins(ExtractInstancesPlugin::<EnvironmentMapIds>::new());
}
fn finish(&self, app: &mut App) {

View File

@ -2,13 +2,14 @@ use super::{
instance_manager::InstanceManager, pipelines::MeshletPipelines,
resource_manager::ResourceManager,
};
use crate::{environment_map::EnvironmentMapLight, irradiance_volume::IrradianceVolume, *};
use crate::{irradiance_volume::IrradianceVolume, *};
use bevy_core_pipeline::{
core_3d::Camera3d,
prepass::{DeferredPrepass, DepthPrepass, MotionVectorPrepass, NormalPrepass},
tonemapping::{DebandDither, Tonemapping},
};
use bevy_derive::{Deref, DerefMut};
use bevy_light::EnvironmentMapLight;
use bevy_platform::collections::{HashMap, HashSet};
use bevy_render::erased_render_asset::ErasedRenderAssets;
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);
}
#ifdef PREPASS_FRAGMENT
@fragment
fn fragment(@builtin(position) frag_coord: vec4<f32>) -> @location(0) vec4<f32> {
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));
return vec4(color, 1.0);
}
#endif
#ifdef PREPASS_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 bevy_asset::UntypedAssetId;
pub use bevy_camera::primitives::{face_index_to_name, CubeMapFace, CUBE_MAP_FACES};
@ -14,6 +11,13 @@ use bevy_ecs::{
prelude::*,
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_platform::collections::{HashMap, HashSet};
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.
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,
)
});
@ -2265,3 +2269,18 @@ impl ShadowPassNode {
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},
};
use bevy_image::{BevyDefault, ImageSampler, TextureFormatPixelInfo};
use bevy_light::{
EnvironmentMapLight, NotShadowCaster, NotShadowReceiver, TransmittedShadowReceiver,
};
use bevy_math::{Affine3, Rect, UVec2, Vec3, Vec4};
use bevy_platform::collections::{hash_map::Entry, HashMap};
use bevy_render::{
@ -52,7 +55,6 @@ use material_bind_groups::MaterialBindingId;
use tracing::{error, warn};
use self::irradiance_volume::IRRADIANCE_VOLUMES_ARE_USABLE;
use crate::environment_map::EnvironmentMapLight;
use crate::irradiance_volume::IrradianceVolume;
use crate::{
render::{

View File

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

View File

@ -6,14 +6,14 @@
//! for light beams from directional lights to shine through, creating what is
//! known as *light shafts* or *god rays*.
//!
//! To add volumetric fog to a scene, add [`VolumetricFog`] to the
//! camera, and add [`VolumetricLight`] to directional lights that you wish to
//! be volumetric. [`VolumetricFog`] feature numerous settings that
//! To add volumetric fog to a scene, add [`crate::VolumetricFog`] to the
//! camera, and add [`crate::VolumetricLight`] to directional lights that you wish to
//! be volumetric. [`crate::VolumetricFog`] feature numerous settings that
//! 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
//! shadow maps is supported. Note that the overhead of the effect scales
//! 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
//! 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
use bevy_app::{App, Plugin};
use bevy_asset::{embedded_asset, Assets, Handle};
use bevy_color::Color;
use bevy_asset::{embedded_asset, Assets};
use bevy_core_pipeline::core_3d::{
graph::{Core3d, Node3d},
prepare_core_3d_depth_textures,
};
use bevy_ecs::{
component::Component, reflect::ReflectComponent, schedule::IntoScheduleConfigs as _,
};
use bevy_image::Image;
use bevy_ecs::schedule::IntoScheduleConfigs as _;
use bevy_light::FogVolume;
use bevy_math::{
primitives::{Cuboid, Plane3d},
Vec2, Vec3,
};
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
use bevy_render::{
mesh::{Mesh, Meshable},
render_graph::{RenderGraphExt, ViewNodeRunner},
render_resource::SpecializedRenderPipelines,
sync_component::SyncComponentPlugin,
view::Visibility,
ExtractSchedule, Render, RenderApp, RenderSystems,
};
use bevy_transform::components::Transform;
use render::{
VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH,
};
@ -65,127 +59,6 @@ pub mod render;
/// A plugin that implements volumetric fog.
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 {
fn build(&self, app: &mut App) {
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(&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());
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_time = { path = "../bevy_time", 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_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [
"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
// 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
// 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.

View File

@ -39,24 +39,30 @@ pub mod prelude {
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)]
#[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?
pub is_touch_enabled: bool,
/// Should mouse inputs be updated?
pub is_mouse_enabled: bool,
}
impl PointerInputPlugin {
impl PointerInputSettings {
fn is_mouse_enabled(state: Res<Self>) -> bool {
state.is_mouse_enabled
}
@ -66,7 +72,7 @@ impl PointerInputPlugin {
}
}
impl Default for PointerInputPlugin {
impl Default for PointerInputSettings {
fn default() -> Self {
Self {
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 {
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(
First,
(
mouse_pick_events.run_if(PointerInputPlugin::is_mouse_enabled),
touch_pick_events.run_if(PointerInputPlugin::is_touch_enabled),
mouse_pick_events.run_if(PointerInputSettings::is_mouse_enabled),
touch_pick_events.run_if(PointerInputSettings::is_touch_enabled),
)
.chain()
.in_set(PickingSystems::Input),
)
.add_systems(
Last,
deactivate_touch_pointers.run_if(PointerInputPlugin::is_touch_enabled),
)
.register_type::<Self>()
.register_type::<PointerInputPlugin>();
deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled),
);
}
}

View File

@ -293,20 +293,31 @@ pub struct DefaultPickingPlugins;
impl PluginGroup for DefaultPickingPlugins {
fn build(self) -> PluginGroupBuilder {
PluginGroupBuilder::start::<Self>()
.add(input::PointerInputPlugin::default())
.add(PickingPlugin::default())
.add(input::PointerInputPlugin)
.add(PickingPlugin)
.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)]
#[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.
pub is_enabled: bool,
/// Enables and disables input collection.
@ -317,7 +328,7 @@ pub struct PickingPlugin {
pub is_window_picking_enabled: bool,
}
impl PickingPlugin {
impl PickingSettings {
/// Whether or not input collection systems should be running.
pub fn input_should_run(state: Res<Self>) -> bool {
state.is_input_enabled && state.is_enabled
@ -335,7 +346,7 @@ impl PickingPlugin {
}
}
impl Default for PickingPlugin {
impl Default for PickingSettings {
fn default() -> Self {
Self {
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 {
fn build(&self, app: &mut App) {
app.insert_resource(*self)
app.init_resource::<PickingSettings>()
.init_resource::<pointer::PointerMap>()
.init_resource::<backend::ray::RayMap>()
.add_event::<pointer::PointerInput>()
@ -369,7 +389,7 @@ impl Plugin for PickingPlugin {
.add_systems(
PreUpdate,
window::update_window_hits
.run_if(Self::window_picking_should_run)
.run_if(PickingSettings::window_picking_should_run)
.in_set(PickingSystems::Backend),
)
.configure_sets(
@ -382,15 +402,15 @@ impl Plugin for PickingPlugin {
.configure_sets(
PreUpdate,
(
PickingSystems::ProcessInput.run_if(Self::input_should_run),
PickingSystems::ProcessInput.run_if(PickingSettings::input_should_run),
PickingSystems::Backend,
PickingSystems::Hover.run_if(Self::hover_should_run),
PickingSystems::Hover.run_if(PickingSettings::hover_should_run),
PickingSystems::PostHover,
PickingSystems::Last,
)
.chain(),
)
.register_type::<Self>()
.register_type::<PickingSettings>()
.register_type::<Pickable>()
.register_type::<hover::PickingInteraction>()
.register_type::<hover::Hovered>()

View File

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

View File

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

View File

@ -24,7 +24,6 @@ indexmap = "2.0"
proc-macro2 = "1.0"
quote = "1.0"
syn = { version = "2.0", features = ["full", "extra-traits"] }
uuid = { version = "1.13.1", features = ["v4"] }
[target.'cfg(target_arch = "wasm32")'.dependencies]
# 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 {
quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#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)
)
),
})?
<#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#accessor)?
}
};

View File

@ -316,9 +316,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
fn construct_field(&self, field: VariantField) -> TokenStream {
let bevy_reflect_path = self.reflect_enum.meta().bevy_reflect_path();
let field_ty = field.field.reflected_type();
let alias = field.alias;
let alias = match &field.field.attrs.remote {
Some(wrapper_ty) => {
@ -332,18 +330,7 @@ impl<'a> VariantBuilder for ReflectCloneVariantBuilder<'a> {
match &field.field.attrs.clone {
CloneBehavior::Default => {
quote! {
#bevy_reflect_path::PartialReflect::reflect_clone(#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)
)
),
})?
<#field_ty as #bevy_reflect_path::PartialReflect>::reflect_clone_and_take(#alias)?
}
}
CloneBehavior::Trait => {

View File

@ -231,11 +231,11 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre
/// // Generates a where clause like:
/// // impl bevy_reflect::Reflect for Foo
/// // where
/// // Self: Any + Send + Sync,
/// // Vec<Foo>: FromReflect + TypePath,
/// // Foo: Any + Send + Sync,
/// // 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 `Vec<Foo>` implements `FromReflect`,
/// 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>
/// // where
/// // Self: Any + Send + Sync,
/// // Foo<T>: Any + Send + Sync,
/// // T::Assoc: Default,
/// // T: TypePath,
/// // T::Assoc: FromReflect + TypePath,
/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection,
/// // T::Assoc: List,
/// // {/* ... */}
/// ```

View File

@ -1,8 +1,8 @@
use crate::derive_data::ReflectMeta;
use bevy_macro_utils::fq_std::{FQAny, FQSend, FQSync};
use proc_macro2::TokenStream;
use proc_macro2::{TokenStream, TokenTree};
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.
pub(crate) struct WhereClauseOptions<'a, 'b> {
@ -29,15 +29,24 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
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:
/// - `Self` has the bounds `Any + Send + Sync`
/// - Type parameters have the bound `TypePath` unless `#[reflect(type_path = false)]` is present
/// - Active fields have the bounds `TypePath` and either `PartialReflect` if `#[reflect(from_reflect = false)]` is present
/// or `FromReflect` otherwise (or no bounds at all if `#[reflect(no_field_bounds)]` is present)
/// - `Self` has:
/// - `Any + Send + Sync` bounds, if generic over types
/// - An `Any` bound, if generic over lifetimes but not types
/// - 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
///
@ -55,57 +64,69 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
/// ```ignore (bevy_reflect is not accessible from this crate)
/// where
/// // `Self` bounds:
/// Self: Any + Send + Sync,
/// Foo<T, U>: Any + Send + Sync,
/// // Type parameter bounds:
/// T: TypePath,
/// U: TypePath,
/// // Field bounds
/// T: FromReflect + TypePath,
/// // Active non-generic field bounds
/// 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)
/// where
/// // `Self` bounds:
/// Self: Any + Send + Sync,
/// // Type parameter bounds:
/// T: TypePath,
/// 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,
/// Foo<T, U>: Any + Send + Sync,
/// // Given bounds:
/// T: Clone,
/// // Type parameter bounds:
/// T: TypePath,
/// U: TypePath,
/// // No active non-generic field bounds
/// // Custom bounds
/// T: MyTrait,
/// ```
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
// and trait impls for a type's reference (e.g. `impl FromArg for &MyType`)
let this = self.meta.type_path().true_type();
let mut generic_where_clause = quote! { where };
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.
let mut generic_where_clause = if let Some(where_clause) = where_clause {
// Maintain existing where clause bounds, if any.
if let Some(where_clause) = where_clause {
let predicates = where_clause.predicates.iter();
quote! {where #this: #required_bounds, #(#predicates,)*}
} else {
quote!(where #this: #required_bounds,)
};
generic_where_clause.extend(quote! { #(#predicates,)* });
}
// Add additional reflection trait bounds
// Add additional reflection trait bounds.
let predicates = self.predicates();
generic_where_clause.extend(quote! {
#predicates
@ -157,19 +178,57 @@ impl<'a, 'b> WhereClauseOptions<'a, 'b> {
let bevy_reflect_path = self.meta.bevy_reflect_path();
let reflect_bound = self.reflect_bound();
// `TypePath` is always required for active fields since they are used to
// construct `NamedField` and `UnnamedField` instances for the `Typed` impl.
// Likewise, `GetTypeRegistration` is always required for active fields since
// they are used to register the type's dependencies.
Some(self.active_fields.iter().map(move |ty| {
quote!(
#ty : #reflect_bound
+ #bevy_reflect_path::TypePath
// Needed for `Typed` impls
+ #bevy_reflect_path::MaybeTyped
// Needed for `GetTypeRegistration` impls
+ #bevy_reflect_path::__macro_exports::RegisterForReflection
)
// Get the identifiers of all type parameters.
let type_param_idents = self
.meta
.type_path()
.generics()
.type_params()
.map(|type_param| type_param.ident.clone())
.collect::<Vec<Ident>>();
// Do any of the identifiers in `idents` appear in `token_stream`?
fn is_any_ident_in_token_stream(idents: &[Ident], token_stream: TokenStream) -> bool {
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
}
}
/// 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},
utility::GenericTypeInfoCell,
};
use alloc::borrow::Cow;
use alloc::vec::Vec;
use bevy_platform::prelude::*;
use bevy_reflect_derive::impl_type_path;
@ -144,21 +143,8 @@ where
fn reflect_clone(&self) -> Result<Box<dyn Reflect>, ReflectCloneError> {
let mut map = Self::new();
for (key, value) in self.iter() {
let key =
key.reflect_clone()?
.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()),
})?;
let key = key.reflect_clone_and_take()?;
let value = value.reflect_clone_and_take()?;
map.insert(key, value);
}

View File

@ -10,7 +10,6 @@ use crate::{
};
use bevy_platform::prelude::*;
use bevy_reflect_derive::impl_type_path;
use core::any::Any;
use core::fmt;
macro_rules! impl_reflect_for_atomic {
@ -21,10 +20,7 @@ macro_rules! impl_reflect_for_atomic {
#[cfg(feature = "functions")]
crate::func::macros::impl_function_traits!($ty);
impl GetTypeRegistration for $ty
where
$ty: Any + Send + Sync,
{
impl GetTypeRegistration for $ty {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Self>();
registration.insert::<ReflectFromPtr>(FromType::<Self>::from_type());
@ -42,10 +38,7 @@ macro_rules! impl_reflect_for_atomic {
}
}
impl Typed for $ty
where
$ty: Any + Send + Sync,
{
impl Typed for $ty {
fn type_info() -> &'static TypeInfo {
static CELL: NonGenericTypeInfoCell = NonGenericTypeInfoCell::new();
CELL.get_or_set(|| {
@ -55,10 +48,7 @@ macro_rules! impl_reflect_for_atomic {
}
}
impl PartialReflect for $ty
where
$ty: Any + Send + Sync,
{
impl PartialReflect for $ty {
#[inline]
fn get_represented_type_info(&self) -> Option<&'static TypeInfo> {
Some(<Self as Typed>::type_info())
@ -128,10 +118,7 @@ macro_rules! impl_reflect_for_atomic {
}
}
impl FromReflect for $ty
where
$ty: Any + Send + Sync,
{
impl FromReflect for $ty {
fn from_reflect(reflect: &dyn PartialReflect) -> Option<Self> {
Some(<$ty>::new(
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> {
Ok(bevy_platform::prelude::Box::new(
self.iter()
.map(|value| {
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())),
}
})
})
.map(|value| value.reflect_clone_and_take())
.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> {
let mut map = Self::with_capacity_and_hasher(self.len(), S::default());
for (key, value) in self.iter() {
let key = key.reflect_clone()?.take().map_err(|_| {
$crate::error::ReflectCloneError::FailedDowncast {
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())),
}
})?;
let key = key.reflect_clone_and_take()?;
let value = value.reflect_clone_and_take()?;
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> {
let mut set = Self::with_capacity_and_hasher(self.len(), S::default());
for value in self.iter() {
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())),
}
})?;
let value = value.reflect_clone_and_take()?;
set.insert(value);
}

View File

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

View File

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

View File

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