diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08d34fec7a..8ae7f6597e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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: | diff --git a/Cargo.toml b/Cargo.toml index 6470642766..6390ceeed7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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 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" diff --git a/assets/shaders/custom_vertex_attribute.wgsl b/assets/shaders/custom_vertex_attribute.wgsl index 6b7b93e4c7..cb650c8a47 100644 --- a/assets/shaders/custom_vertex_attribute.wgsl +++ b/assets/shaders/custom_vertex_attribute.wgsl @@ -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 { diff --git a/assets/shaders/extended_material.wgsl b/assets/shaders/extended_material.wgsl index 7bad24a331..ae89f7dfea 100644 --- a/assets/shaders/extended_material.wgsl +++ b/assets/shaders/extended_material.wgsl @@ -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) diff --git a/benches/benches/bevy_ecs/events/iter.rs b/benches/benches/bevy_ecs/events/iter.rs index 9ad17ed8c8..5f85633312 100644 --- a/benches/benches/bevy_ecs/events/iter.rs +++ b/benches/benches/bevy_ecs/events/iter.rs @@ -10,7 +10,7 @@ impl Benchmark { let mut events = Events::default(); for _ in 0..count { - events.send(BenchEvent([0u8; SIZE])); + events.write(BenchEvent([0u8; SIZE])); } Self(events) diff --git a/benches/benches/bevy_ecs/events/mod.rs b/benches/benches/bevy_ecs/events/mod.rs index c2c7f3ee28..fcc807c968 100644 --- a/benches/benches/bevy_ecs/events/mod.rs +++ b/benches/benches/bevy_ecs/events/mod.rs @@ -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()); }); } diff --git a/benches/benches/bevy_ecs/events/send.rs b/benches/benches/bevy_ecs/events/write.rs similarity index 86% rename from benches/benches/bevy_ecs/events/send.rs rename to benches/benches/bevy_ecs/events/write.rs index be8934e789..8095aee738 100644 --- a/benches/benches/bevy_ecs/events/send.rs +++ b/benches/benches/bevy_ecs/events/write.rs @@ -21,7 +21,7 @@ impl Benchmark { // 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 Benchmark { 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(); } diff --git a/crates/bevy_animation/Cargo.toml b/crates/bevy_animation/Cargo.toml index a5de22f1a4..731a6c7c4f 100644 --- a/crates/bevy_animation/Cargo.toml +++ b/crates/bevy_animation/Cargo.toml @@ -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 = [ diff --git a/crates/bevy_anti_aliasing/src/smaa/smaa.wgsl b/crates/bevy_anti_aliasing/src/smaa/smaa.wgsl index 0872325448..24dc6baa25 100644 --- a/crates/bevy_anti_aliasing/src/smaa/smaa.wgsl +++ b/crates/bevy_anti_aliasing/src/smaa/smaa.wgsl @@ -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) diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 05f3de27b1..a9057c787d 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -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::>(); 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::>(); assert_eq!(test_events.len(), 3); // Events are double-buffered, so we see 1 + 2 = 3 diff --git a/crates/bevy_app/src/sub_app.rs b/crates/bevy_app/src/sub_app.rs index 56d6b43d38..56a496f2b5 100644 --- a/crates/bevy_app/src/sub_app.rs +++ b/crates/bevy_app/src/sub_app.rs @@ -12,7 +12,7 @@ use core::fmt::Debug; #[cfg(feature = "trace")] use tracing::info_span; -type ExtractFn = Box; +type ExtractFn = Box; /// 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(&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); /// } /// diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 0b64fe74e0..69dc8428da 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -167,7 +167,7 @@ impl AssetServer { fn sender(world: &mut World, id: UntypedAssetId) { world .resource_mut::>>() - .send(AssetEvent::LoadedWithDependencies { id: id.typed() }); + .write(AssetEvent::LoadedWithDependencies { id: id.typed() }); } fn failed_sender( world: &mut World, @@ -177,7 +177,7 @@ impl AssetServer { ) { world .resource_mut::>>() - .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( diff --git a/crates/bevy_audio/Cargo.toml b/crates/bevy_audio/Cargo.toml index 2ffa62db9d..8beba77c0d 100644 --- a/crates/bevy_audio/Cargo.toml +++ b/crates/bevy_audio/Cargo.toml @@ -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`. diff --git a/crates/bevy_audio/src/audio.rs b/crates/bevy_audio/src/audio.rs index 8a4a406acc..0407997565 100644 --- a/crates/bevy_audio/src/audio.rs +++ b/crates/bevy_audio/src/audio.rs @@ -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)] diff --git a/crates/bevy_audio/src/audio_output.rs b/crates/bevy_audio/src/audio_output.rs index d02d326501..749b08a3b9 100644 --- a/crates/bevy_audio/src/audio_output.rs +++ b/crates/bevy_audio/src/audio_output.rs @@ -103,7 +103,7 @@ pub(crate) fn play_queued_audio_system( Entity, &AudioPlayer, &PlaybackSettings, - Option<&GlobalTransform>, + &GlobalTransform, ), (Without, Without), >, @@ -118,7 +118,7 @@ pub(crate) fn play_queued_audio_system( 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( } 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, diff --git a/crates/bevy_core_pipeline/Cargo.toml b/crates/bevy_core_pipeline/Cargo.toml index 837869bc07..63513787f8 100644 --- a/crates/bevy_core_pipeline/Cargo.toml +++ b/crates/bevy_core_pipeline/Cargo.toml @@ -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 diff --git a/crates/bevy_core_widgets/Cargo.toml b/crates/bevy_core_widgets/Cargo.toml index 57e2968e22..186b2ec820 100644 --- a/crates/bevy_core_widgets/Cargo.toml +++ b/crates/bevy_core_widgets/Cargo.toml @@ -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", ] } diff --git a/crates/bevy_core_widgets/src/core_slider.rs b/crates/bevy_core_widgets/src/core_slider.rs index 8a5e27f885..521e6fc1d3 100644 --- a/crates/bevy_core_widgets/src/core_slider.rs +++ b/crates/bevy_core_widgets/src/core_slider.rs @@ -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); diff --git a/crates/bevy_dev_tools/Cargo.toml b/crates/bevy_dev_tools/Cargo.toml index ef31767c0e..3f0efb1c21 100644 --- a/crates/bevy_dev_tools/Cargo.toml +++ b/crates/bevy_dev_tools/Cargo.toml @@ -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" } diff --git a/crates/bevy_dev_tools/src/ci_testing/systems.rs b/crates/bevy_dev_tools/src/ci_testing/systems.rs index ae7a2a774e..8e0b502aa0 100644 --- a/crates/bevy_dev_tools/src/ci_testing/systems.rs +++ b/crates/bevy_dev_tools/src/ci_testing/systems.rs @@ -18,7 +18,7 @@ pub(crate) fn send_events(world: &mut World, mut current_frame: Local) { 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) { } // Custom events are forwarded to the world. CiTestingEvent::Custom(event_string) => { - world.send_event(CiTestingCustomEvent(event_string)); + world.write_event(CiTestingCustomEvent(event_string)); } } } diff --git a/crates/bevy_diagnostic/Cargo.toml b/crates/bevy_diagnostic/Cargo.toml index e930da149a..424ca67437 100644 --- a/crates/bevy_diagnostic/Cargo.toml +++ b/crates/bevy_diagnostic/Cargo.toml @@ -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", diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 1ecbad16a1..f682554ce9 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -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 } diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 78948f81b1..75c2c89ff8 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -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`. diff --git a/crates/bevy_ecs/src/entity/clone_entities.rs b/crates/bevy_ecs/src/entity/clone_entities.rs index 08da93c261..4f0f082c50 100644 --- a/crates/bevy_ecs/src/entity/clone_entities.rs +++ b/crates/bevy_ecs/src/entity/clone_entities.rs @@ -795,47 +795,34 @@ impl<'w> EntityClonerBuilder<'w, OptOut> { /// this behavior. pub fn deny(&mut self) -> &mut Self { let bundle_id = self.world.register_bundle::().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) -> &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) -> &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(&mut self, ids: impl FilterableIds) -> &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(&mut self) -> &mut Self { let bundle_id = self.world.register_bundle::().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(&mut self) -> &mut Self { let bundle_id = self.world.register_bundle::().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) -> &mut Self { - for id in ids { - self.filter - .filter_allow(id, self.world, InsertMode::Replace); - } + pub fn allow_by_ids(&mut self, ids: impl FilterableIds) -> &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) -> &mut Self { - for id in ids { - self.filter.filter_allow(id, self.world, InsertMode::Keep); - } + pub fn allow_by_ids_if_new(&mut self, ids: impl FilterableIds) -> &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) -> &mut Self { - for type_id in ids { - if let Some(id) = self.world.components().get_valid_id(type_id) { + fn allow_by_ids_inner( + &mut self, + ids: impl FilterableIds, + 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) -> &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 + 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` is a different trait from `FilterableId`, 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 { + /// Takes in a function that processes all types of [`FilterableId`] one-by-one. + fn filter_ids(self, ids: &mut impl FnMut(FilterableId)); + } + + impl FilterableIds for I + where + I: IntoIterator, + T: Into, + { + #[inline] + fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) { + for id in self.into_iter() { + ids(id.into()); + } + } + } + + impl FilterableIds for T + where + T: Into, + { + #[inline] + fn filter_ids(self, ids: &mut impl FnMut(FilterableId)) { + ids(self.into()); + } + } +} + +use private::{FilterableId, FilterableIds, Marker}; + #[cfg(test)] mod tests { use super::*; diff --git a/crates/bevy_ecs/src/event/collections.rs b/crates/bevy_ecs/src/event/collections.rs index 7d1854149e..5175efb03a 100644 --- a/crates/bevy_ecs/src/event/collections.rs +++ b/crates/bevy_ecs/src/event/collections.rs @@ -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 Events { 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 { - self.send_with_caller(event, MaybeLocation::caller()) + pub fn write(&mut self, event: E) -> EventId { + self.write_with_caller(event, MaybeLocation::caller()) } - pub(crate) fn send_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId { + pub(crate) fn write_with_caller(&mut self, event: E, caller: MaybeLocation) -> EventId { 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 Events { 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) -> SendBatchIds { + pub fn write_batch(&mut self, events: impl IntoIterator) -> WriteBatchIds { 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 + 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::write` instead.")] + #[track_caller] + pub fn send(&mut self, event: E) -> EventId { + 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::write_batch` instead.")] + #[track_caller] + pub fn send_batch(&mut self, events: impl IntoIterator) -> WriteBatchIds { + 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::write_default` instead.")] #[track_caller] pub fn send_default(&mut self) -> EventId 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 DerefMut for EventSequence { } } -/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch. -pub struct SendBatchIds { +/// [`Iterator`] over written [`EventIds`](`EventId`) from a batch. +pub struct WriteBatchIds { last_count: usize, event_count: usize, _marker: PhantomData, } -impl Iterator for SendBatchIds { +/// [`Iterator`] over sent [`EventIds`](`EventId`) from a batch. +#[deprecated(since = "0.17.0", note = "Use `WriteBatchIds` instead.")] +pub type SendBatchIds = WriteBatchIds; + +impl Iterator for WriteBatchIds { type Item = EventId; fn next(&mut self) -> Option { @@ -378,7 +411,7 @@ impl Iterator for SendBatchIds { } } -impl ExactSizeIterator for SendBatchIds { +impl ExactSizeIterator for WriteBatchIds { 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); } diff --git a/crates/bevy_ecs/src/event/event_cursor.rs b/crates/bevy_ecs/src/event/event_cursor.rs index 70e19a732c..f0460a9424 100644 --- a/crates/bevy_ecs/src/event/event_cursor.rs +++ b/crates/bevy_ecs/src/event/event_cursor.rs @@ -41,7 +41,7 @@ use core::marker::PhantomData; /// } /// /// for event in events_to_resend { -/// events.send(MyEvent); +/// events.write(MyEvent); /// } /// } /// diff --git a/crates/bevy_ecs/src/event/mod.rs b/crates/bevy_ecs/src/event/mod.rs index ec930cd269..77741a47ab 100644 --- a/crates/bevy_ecs/src/event/mod.rs +++ b/crates/bevy_ecs/src/event/mod.rs @@ -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 = 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::::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::::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::::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::::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::::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::::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::::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::::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::::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::>(); 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::>(); 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::>(); - 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| { @@ -608,11 +609,11 @@ mod tests { let mut world = World::new(); world.init_resource::>(); - 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| { diff --git a/crates/bevy_ecs/src/event/mutator.rs b/crates/bevy_ecs/src/event/mutator.rs index a9c9459119..847cbf3e16 100644 --- a/crates/bevy_ecs/src/event/mutator.rs +++ b/crates/bevy_ecs/src/event/mutator.rs @@ -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::().unwrap(); diff --git a/crates/bevy_ecs/src/event/reader.rs b/crates/bevy_ecs/src/event/reader.rs index e15b3ea9e7..2e135eab2f 100644 --- a/crates/bevy_ecs/src/event/reader.rs +++ b/crates/bevy_ecs/src/event/reader.rs @@ -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::().unwrap(); diff --git a/crates/bevy_ecs/src/event/writer.rs b/crates/bevy_ecs/src/event/writer.rs index 4c38401eb4..015e59891c 100644 --- a/crates/bevy_ecs/src/event/writer.rs +++ b/crates/bevy_ecs/src/event/writer.rs @@ -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 { - 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) -> SendBatchIds { - self.events.send_batch(events) + pub fn write_batch(&mut self, events: impl IntoIterator) -> WriteBatchIds { + 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() } } diff --git a/crates/bevy_ecs/src/hierarchy.rs b/crates/bevy_ecs/src/hierarchy.rs index fe9bf571c9..d325d5756c 100644 --- a/crates/bevy_ecs/src/hierarchy.rs +++ b/crates/bevy_ecs/src/hierarchy.rs @@ -455,15 +455,13 @@ pub fn validate_parent_has_component( let Some(child_of) = entity_ref.get::() else { return; }; - if !world - .get_entity(child_of.parent()) - .is_ok_and(|e| e.contains::()) - { + let parent = child_of.parent(); + if !world.get_entity(parent).is_ok_and(|e| e.contains::()) { // TODO: print name here once Name lives in bevy_ecs let name: Option = None; let debug_name = DebugName::type_name::(); 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(), diff --git a/crates/bevy_ecs/src/lifecycle.rs b/crates/bevy_ecs/src/lifecycle.rs index 08f178eda1..7c3f1ffa4a 100644 --- a/crates/bevy_ecs/src/lifecycle.rs +++ b/crates/bevy_ecs/src/lifecycle.rs @@ -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, entity: Entity) { + self.write(component_id, entity); + } + + /// Writes a removal event for the specified component. + pub fn write(&mut self, component_id: impl Into, entity: Entity) { self.event_sets .get_or_insert_with(component_id.into(), Default::default) - .send(RemovedComponentEntity(entity)); + .write(RemovedComponentEntity(entity)); } } diff --git a/crates/bevy_ecs/src/relationship/mod.rs b/crates/bevy_ecs/src/relationship/mod.rs index f95214262b..82b39e04e5 100644 --- a/crates/bevy_ecs/src/relationship/mod.rs +++ b/crates/bevy_ecs/src/relationship/mod.rs @@ -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 diff --git a/crates/bevy_ecs/src/schedule/condition.rs b/crates/bevy_ecs/src/schedule/condition.rs index 1a4a7a8101..2cd71db9b4 100644 --- a/crates/bevy_ecs/src/schedule/condition.rs +++ b/crates/bevy_ecs/src/schedule/condition.rs @@ -875,7 +875,7 @@ pub mod common_conditions { /// app.run(&mut world); /// assert_eq!(world.resource::().0, 0); /// - /// world.resource_mut::>().send(MyEvent); + /// world.resource_mut::>().write(MyEvent); /// /// // A `MyEvent` event has been pushed so `my_system` will run /// app.run(&mut world); diff --git a/crates/bevy_ecs/src/storage/table/mod.rs b/crates/bevy_ecs/src/storage/table/mod.rs index be75c58f03..548ba82103 100644 --- a/crates/bevy_ecs/src/storage/table/mod.rs +++ b/crates/bevy_ecs/src/storage/table/mod.rs @@ -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 } diff --git a/crates/bevy_ecs/src/system/commands/command.rs b/crates/bevy_ecs/src/system/commands/command.rs index 164758cca8..83dad342a8 100644 --- a/crates/bevy_ecs/src/system/commands/command.rs +++ b/crates/bevy_ecs/src/system/commands/command.rs @@ -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(event: E) -> impl Command { +pub fn write_event(event: E) -> impl Command { let caller = MaybeLocation::caller(); move |world: &mut World| { let mut events = world.resource_mut::>(); - 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(event: E) -> impl Command { + write_event(event) +} diff --git a/crates/bevy_ecs/src/system/commands/entity_command.rs b/crates/bevy_ecs/src/system/commands/entity_command.rs index 098493a148..6d977e808d 100644 --- a/crates/bevy_ecs/src/system/commands/entity_command.rs +++ b/crates/bevy_ecs/src/system/commands/entity_command.rs @@ -143,12 +143,38 @@ pub unsafe fn insert_by_id( /// 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(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::()) { + 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(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::()) { + let value = component_fn(); + entity.insert_with_caller(value, mode, caller, RelationshipHookMode::Run); + } } } diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index d345368ab2..a43dea5627 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -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(&mut self, event: E) -> &mut Self { - self.queue(command::send_event(event)); + pub fn write_event(&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(&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(&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(&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 { + 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::>().entity().id(); queue.apply(&mut world); assert_eq!(id, entity); + let mut commands = Commands::new(&mut queue, &world); + commands + .entity(entity) + .entry::>() + .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::>(entity).unwrap().0); } #[test] diff --git a/crates/bevy_ecs/src/world/deferred_world.rs b/crates/bevy_ecs/src/world/deferred_world.rs index 0734aa9d8c..64f1fa409f 100644 --- a/crates/bevy_ecs/src/world/deferred_world.rs +++ b/crates/bevy_ecs/src/world/deferred_world.rs @@ -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(&mut self, event: E) -> Option> { + 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(&mut self, event: E) -> Option> { - 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(&mut self) -> Option> { + 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(&mut self) -> Option> { - self.send_event(E::default()) + self.write_event_default::() } - /// 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( + pub fn write_event_batch( &mut self, events: impl IntoIterator, - ) -> Option> { + ) -> Option> { let Some(mut events_resource) = self.get_resource_mut::>() 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( + &mut self, + events: impl IntoIterator, + ) -> Option> { + self.write_event_batch(events) } /// Gets a pointer to the resource with the id [`ComponentId`] if it exists. diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 9b7f8eb551..90438b8d6e 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -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 diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index fbdf0c04af..e77b348c96 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -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(&mut self, event: E) -> Option> { + 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(&mut self, event: E) -> Option> { - 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(&mut self) -> Option> { + 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(&mut self) -> Option> { - self.send_event(E::default()) + self.write_event_default::() } - /// 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( + pub fn write_event_batch( &mut self, events: impl IntoIterator, - ) -> Option> { + ) -> Option> { let Some(mut events_resource) = self.get_resource_mut::>() 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( + &mut self, + events: impl IntoIterator, + ) -> Option> { + self.write_event_batch(events) } /// Inserts a new resource with the given `value`. Will replace the value if it already existed. diff --git a/crates/bevy_gilrs/Cargo.toml b/crates/bevy_gilrs/Cargo.toml index afb20318bc..7effc016c4 100644 --- a/crates/bevy_gilrs/Cargo.toml +++ b/crates/bevy_gilrs/Cargo.toml @@ -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", diff --git a/crates/bevy_gizmos/macros/Cargo.toml b/crates/bevy_gizmos/macros/Cargo.toml index b7effe24b0..f4273f05ed 100644 --- a/crates/bevy_gizmos/macros/Cargo.toml +++ b/crates/bevy_gizmos/macros/Cargo.toml @@ -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] diff --git a/crates/bevy_gltf/Cargo.toml b/crates/bevy_gltf/Cargo.toml index e35d7771f5..7c4216d889 100644 --- a/crates/bevy_gltf/Cargo.toml +++ b/crates/bevy_gltf/Cargo.toml @@ -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", diff --git a/crates/bevy_input/Cargo.toml b/crates/bevy_input/Cargo.toml index 7c69aad54a..c32a87a52d 100644 --- a/crates/bevy_input/Cargo.toml +++ b/crates/bevy_input/Cargo.toml @@ -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 } diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index a448ec3d48..aaef46b3e8 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -2223,7 +2223,7 @@ mod tests { self.app .world_mut() .resource_mut::>() - .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::>() - .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::>() - .send(event); + .write(event); } pub fn send_raw_gamepad_event_batch( @@ -2255,7 +2255,7 @@ mod tests { self.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); } } @@ -2449,7 +2449,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch([ + .write_batch([ RawGamepadEvent::Axis(RawGamepadAxisChangedEvent::new( entity, GamepadAxis::LeftStickY, @@ -2513,7 +2513,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); assert_eq!( ctx.app @@ -2550,7 +2550,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); assert_eq!( ctx.app @@ -2598,7 +2598,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); let events = ctx @@ -2654,7 +2654,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); assert_eq!( ctx.app @@ -2692,7 +2692,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); let events = ctx @@ -2728,7 +2728,7 @@ mod tests { ctx.app .world_mut() .resource_mut::>() - .send_batch(events); + .write_batch(events); ctx.update(); assert_eq!( diff --git a/crates/bevy_input_focus/src/lib.rs b/crates/bevy_input_focus/src/lib.rs index df7690ef26..b9aa9ffcbb 100644 --- a/crates/bevy_input_focus/src/lib.rs +++ b/crates/bevy_input_focus/src/lib.rs @@ -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"); diff --git a/crates/bevy_internal/Cargo.toml b/crates/bevy_internal/Cargo.toml index a28cf15d99..ae4ae76b77 100644 --- a/crates/bevy_internal/Cargo.toml +++ b/crates/bevy_internal/Cargo.toml @@ -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", ] } diff --git a/crates/bevy_internal/src/lib.rs b/crates/bevy_internal/src/lib.rs index 5bc3d5b349..4f965e603a 100644 --- a/crates/bevy_internal/src/lib.rs +++ b/crates/bevy_internal/src/lib.rs @@ -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")] diff --git a/crates/bevy_light/Cargo.toml b/crates/bevy_light/Cargo.toml new file mode 100644 index 0000000000..6a3807f9bb --- /dev/null +++ b/crates/bevy_light/Cargo.toml @@ -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 diff --git a/crates/bevy_light/LICENSE-APACHE b/crates/bevy_light/LICENSE-APACHE new file mode 100644 index 0000000000..d9a10c0d8e --- /dev/null +++ b/crates/bevy_light/LICENSE-APACHE @@ -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 diff --git a/crates/bevy_light/LICENSE-MIT b/crates/bevy_light/LICENSE-MIT new file mode 100644 index 0000000000..9cf106272a --- /dev/null +++ b/crates/bevy_light/LICENSE-MIT @@ -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. diff --git a/crates/bevy_pbr/src/light/ambient_light.rs b/crates/bevy_light/src/ambient_light.rs similarity index 89% rename from crates/bevy_pbr/src/light/ambient_light.rs rename to crates/bevy_light/src/ambient_light.rs index dfb9cdfecc..92935e7e06 100644 --- a/crates/bevy_pbr/src/light/ambient_light.rs +++ b/crates/bevy_light/src/ambient_light.rs @@ -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) { /// 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 { diff --git a/crates/bevy_pbr/src/light/cascade.rs b/crates/bevy_light/src/cascade.rs similarity index 99% rename from crates/bevy_pbr/src/light/cascade.rs rename to crates/bevy_light/src/cascade.rs index a6ebe5a89b..0cb713a9e6 100644 --- a/crates/bevy_pbr/src/light/cascade.rs +++ b/crates/bevy_light/src/cascade.rs @@ -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 { diff --git a/crates/bevy_pbr/src/cluster/assign.rs b/crates/bevy_light/src/cluster/assign.rs similarity index 98% rename from crates/bevy_pbr/src/cluster/assign.rs rename to crates/bevy_light/src/cluster/assign.rs index e204644204..20a40104ed 100644 --- a/crates/bevy_pbr/src/cluster/assign.rs +++ b/crates/bevy_light/src/cluster/assign.rs @@ -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! diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_light/src/cluster/mod.rs similarity index 94% rename from crates/bevy_pbr/src/cluster/mod.rs rename to crates/bevy_light/src/cluster/mod.rs index 5132326fb1..92f1c5723e 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_light/src/cluster/mod.rs @@ -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, + pub near: f32, + pub far: f32, + pub clusterable_objects: Vec, } /// 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, - counts: ClusterableObjectCounts, + pub entities: Vec, + 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::)] diff --git a/crates/bevy_pbr/src/cluster/test.rs b/crates/bevy_light/src/cluster/test.rs similarity index 98% rename from crates/bevy_pbr/src/cluster/test.rs rename to crates/bevy_light/src/cluster/test.rs index 23809da7f6..4939d9cb74 100644 --- a/crates/bevy_pbr/src/cluster/test.rs +++ b/crates/bevy_light/src/cluster/test.rs @@ -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); diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_light/src/directional_light.rs similarity index 98% rename from crates/bevy_pbr/src/light/directional_light.rs rename to crates/bevy_light/src/directional_light.rs index 0b54b63cfe..9a13999ccc 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_light/src/directional_light.rs @@ -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 }); /// ``` diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_light/src/lib.rs similarity index 95% rename from crates/bevy_pbr/src/light/mod.rs rename to crates/bevy_light/src/lib.rs index a220b65439..655171da63 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_light/src/lib.rs @@ -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::() .register_type::() .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() + .register_type::() + .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -182,7 +197,7 @@ impl Plugin for LightPlugin { } /// A convenient alias for `Or<(With, With, -/// With)>`, for use with [`bevy_render::view::VisibleEntities`]. +/// With)>`, for use with [`bevy_camera::visibility::VisibleEntities`]. pub type WithLight = Or<(With, With, With)>; /// 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. diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_light/src/point_light.rs similarity index 99% rename from crates/bevy_pbr/src/light/point_light.rs rename to crates/bevy_light/src/point_light.rs index 84c024b7a2..f37a386200 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_light/src/point_light.rs @@ -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 }); /// ``` diff --git a/crates/bevy_light/src/probe.rs b/crates/bevy_light/src/probe.rs new file mode 100644 index 0000000000..5683daa562 --- /dev/null +++ b/crates/bevy_light/src/probe.rs @@ -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, + + /// The typically-sharper, mipmapped image that represents specular radiance + /// surrounding a region. + pub specular_map: Handle, + + /// 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 . + 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, + + /// 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 . + 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, + } + } +} diff --git a/crates/bevy_pbr/src/light/spot_light.rs b/crates/bevy_light/src/spot_light.rs similarity index 99% rename from crates/bevy_pbr/src/light/spot_light.rs rename to crates/bevy_light/src/spot_light.rs index c75a3124d3..6bb8bc0d47 100644 --- a/crates/bevy_pbr/src/light/spot_light.rs +++ b/crates/bevy_light/src/spot_light.rs @@ -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}; diff --git a/crates/bevy_light/src/volumetric.rs b/crates/bevy_light/src/volumetric.rs new file mode 100644 index 0000000000..347731985d --- /dev/null +++ b/crates/bevy_light/src/volumetric.rs @@ -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>, + + /// 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, + } + } +} diff --git a/crates/bevy_math/src/direction.rs b/crates/bevy_math/src/direction.rs index f5ecf75c08..03cb9f969f 100644 --- a/crates/bevy_math/src/direction.rs +++ b/crates/bevy_math/src/direction.rs @@ -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 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 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 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 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; diff --git a/crates/bevy_mesh/Cargo.toml b/crates/bevy_mesh/Cargo.toml index a34b0e7436..0f37ac1141 100644 --- a/crates/bevy_mesh/Cargo.toml +++ b/crates/bevy_mesh/Cargo.toml @@ -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", diff --git a/crates/bevy_pbr/Cargo.toml b/crates/bevy_pbr/Cargo.toml index f183e4e044..299c0f54c3 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -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" diff --git a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs b/crates/bevy_pbr/src/cluster.rs similarity index 99% rename from crates/bevy_pbr/src/cluster/extract_and_prepare.rs rename to crates/bevy_pbr/src/cluster.rs index 53187a2b02..9c47859deb 100644 --- a/crates/bevy_pbr/src/cluster/extract_and_prepare.rs +++ b/crates/bevy_pbr/src/cluster.rs @@ -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 diff --git a/crates/bevy_pbr/src/decal/clustered.rs b/crates/bevy_pbr/src/decal/clustered.rs index d4ac27a1f5..7580f1475e 100644 --- a/crates/bevy_pbr/src/decal/clustered.rs +++ b/crates/bevy_pbr/src/decal/clustered.rs @@ -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. /// diff --git a/crates/bevy_pbr/src/deferred/mod.rs b/crates/bevy_pbr/src/deferred/mod.rs index f4718a175e..96569b2861 100644 --- a/crates/bevy_pbr/src/deferred/mod.rs +++ b/crates/bevy_pbr/src/deferred/mod.rs @@ -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, diff --git a/crates/bevy_pbr/src/lib.rs b/crates/bevy_pbr/src/lib.rs index cad7b8979c..45aa6297d2 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -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::() - .register_type::() - .init_resource::() .register_type::() .init_resource::() .add_plugins(( diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index fa55b4d94a..e6dfebd903 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -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, - - /// The typically-sharper, mipmapped image that represents specular radiance - /// surrounding a region. - pub specular_map: Handle, - - /// 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 . - 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. diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index e2dea463f2..95ed8dcbd9 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -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, - - /// 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 . - 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> { diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 82035be2f6..bce844bb21 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -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; } -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::() - .register_type::() - .register_type::() - .add_plugins(ExtractInstancesPlugin::::new()); + app.add_plugins(ExtractInstancesPlugin::::new()); } fn finish(&self, app: &mut App) { diff --git a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs index 95c8859520..1ec316dcd8 100644 --- a/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs +++ b/crates/bevy_pbr/src/meshlet/material_pipeline_prepare.rs @@ -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::{ diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl b/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl index 4b114cbfc8..1309c7884c 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_material.wgsl @@ -15,7 +15,6 @@ fn vertex(@builtin(vertex_index) vertex_input: u32) -> @builtin(position) vec4) -> @location(0) vec4 { let vertex_output = resolve_vertex_output(frag_coord); @@ -23,7 +22,6 @@ fn fragment(@builtin(position) frag_coord: vec4) -> @location(0) vec4 let color = vec3(rand_f(&rng), rand_f(&rng), rand_f(&rng)); return vec4(color, 1.0); } -#endif #ifdef PREPASS_FRAGMENT @fragment diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 883147acc3..74dc0ff15d 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -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, + }, + } +} diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index dff8edb32b..2e9562c5f8 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -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::{ diff --git a/crates/bevy_pbr/src/render/mesh_view_bindings.rs b/crates/bevy_pbr/src/render/mesh_view_bindings.rs index 03165f1bc5..0f40327dc7 100644 --- a/crates/bevy_pbr/src/render/mesh_view_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_view_bindings.rs @@ -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::{ diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index c9f1f230c6..a4099aeb62 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -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>, - - /// 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::() - .register_type::(); - app.add_plugins(SyncComponentPlugin::::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, - } - } -} diff --git a/crates/bevy_picking/Cargo.toml b/crates/bevy_picking/Cargo.toml index ac18b1dd89..9053f48188 100644 --- a/crates/bevy_picking/Cargo.toml +++ b/crates/bevy_picking/Cargo.toml @@ -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", diff --git a/crates/bevy_picking/src/hover.rs b/crates/bevy_picking/src/hover.rs index dbb6ee942e..f675568394 100644 --- a/crates/bevy_picking/src/hover.rs +++ b/crates/bevy_picking/src/hover.rs @@ -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. diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index 2995174d6d..3050522ab5 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -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) -> 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::() + .register_type::() .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::() - .register_type::(); + deactivate_touch_pointers.run_if(PointerInputSettings::is_touch_enabled), + ); } } diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 74a765fbcd..026d2a1953 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -293,20 +293,31 @@ pub struct DefaultPickingPlugins; impl PluginGroup for DefaultPickingPlugins { fn build(self) -> PluginGroupBuilder { PluginGroupBuilder::start::() - .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) -> 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::() .init_resource::() .init_resource::() .add_event::() @@ -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::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs index d691c824cc..67135669f5 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/invalid_definition_fail.rs @@ -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 } } diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs index 457f1f75e5..391258ccc6 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs @@ -26,8 +26,8 @@ mod incorrect_inner_type { //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `TypePath` so cannot provide dynamic type path information - //~| ERROR: `TheirInner` does not implement `TypePath` so cannot provide dynamic type path information //~| ERROR: `?` operator has incompatible types + //~| ERROR: mismatched types struct MyOuter { // Reason: Should not use `MyInner` directly pub inner: MyInner, diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs index 1983442ab7..e3c894e6d5 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/type_mismatch_fail.rs @@ -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), diff --git a/crates/bevy_reflect/derive/Cargo.toml b/crates/bevy_reflect/derive/Cargo.toml index b9eaa369cd..19875633ed 100644 --- a/crates/bevy_reflect/derive/Cargo.toml +++ b/crates/bevy_reflect/derive/Cargo.toml @@ -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. diff --git a/crates/bevy_reflect/derive/src/derive_data.rs b/crates/bevy_reflect/derive/src/derive_data.rs index 9af7acda76..3f6532a408 100644 --- a/crates/bevy_reflect/derive/src/derive_data.rs +++ b/crates/bevy_reflect/derive/src/derive_data.rs @@ -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)? } }; diff --git a/crates/bevy_reflect/derive/src/enum_utility.rs b/crates/bevy_reflect/derive/src/enum_utility.rs index ad41a1a8df..c717b7723e 100644 --- a/crates/bevy_reflect/derive/src/enum_utility.rs +++ b/crates/bevy_reflect/derive/src/enum_utility.rs @@ -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 => { diff --git a/crates/bevy_reflect/derive/src/lib.rs b/crates/bevy_reflect/derive/src/lib.rs index 2d9dfca681..7ee7ad83e7 100644 --- a/crates/bevy_reflect/derive/src/lib.rs +++ b/crates/bevy_reflect/derive/src/lib.rs @@ -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: FromReflect + TypePath, +/// // Foo: Any + Send + Sync, +/// // Vec: FromReflect + TypePath + MaybeTyped + RegisterForReflection, /// ``` /// -/// In this case, `Foo` is given the bounds `Vec: FromReflect + TypePath`, +/// In this case, `Foo` is given the bounds `Vec: FromReflect + ...`, /// which requires that `Foo` implements `FromReflect`, /// which requires that `Vec` implements `FromReflect`, /// and so on, resulting in the error. @@ -283,10 +283,10 @@ fn match_reflect_impls(ast: DeriveInput, source: ReflectImplSource) -> TokenStre /// // /// // impl bevy_reflect::Reflect for Foo /// // where -/// // Self: Any + Send + Sync, +/// // Foo: Any + Send + Sync, /// // T::Assoc: Default, /// // T: TypePath, -/// // T::Assoc: FromReflect + TypePath, +/// // T::Assoc: FromReflect + TypePath + MaybeTyped + RegisterForReflection, /// // T::Assoc: List, /// // {/* ... */} /// ``` diff --git a/crates/bevy_reflect/derive/src/where_clause_options.rs b/crates/bevy_reflect/derive/src/where_clause_options.rs index e63dbe599a..d2d3b15a44 100644 --- a/crates/bevy_reflect/derive/src/where_clause_options.rs +++ b/crates/bevy_reflect/derive/src/where_clause_options.rs @@ -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: 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 + /// 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: 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::>(); + + // 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) - } } diff --git a/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs index df68b425f6..d5559a2985 100644 --- a/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs +++ b/crates/bevy_reflect/src/impls/alloc/collections/btree/map.rs @@ -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, 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(::type_path()), - received: Cow::Owned(key.reflect_type_path().to_string()), - })?; - let value = - value - .reflect_clone()? - .take() - .map_err(|_| ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::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); } diff --git a/crates/bevy_reflect/src/impls/core/sync.rs b/crates/bevy_reflect/src/impls/core/sync.rs index b4fe8977d5..06b930e8a9 100644 --- a/crates/bevy_reflect/src/impls/core/sync.rs +++ b/crates/bevy_reflect/src/impls/core/sync.rs @@ -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::(); registration.insert::(FromType::::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(::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 { 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); }; } diff --git a/crates/bevy_reflect/src/impls/macros/list.rs b/crates/bevy_reflect/src/impls/macros/list.rs index 72e05ba10f..81a27047cb 100644 --- a/crates/bevy_reflect/src/impls/macros/list.rs +++ b/crates/bevy_reflect/src/impls/macros/list.rs @@ -113,14 +113,7 @@ macro_rules! impl_reflect_for_veclike { fn reflect_clone(&self) -> Result, $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(::type_path()), - received: alloc::borrow::Cow::Owned(alloc::string::ToString::to_string(value.reflect_type_path())), - } - }) - }) + .map(|value| value.reflect_clone_and_take()) .collect::>()?, )) } diff --git a/crates/bevy_reflect/src/impls/macros/map.rs b/crates/bevy_reflect/src/impls/macros/map.rs index d356ddba58..e87bb314b5 100644 --- a/crates/bevy_reflect/src/impls/macros/map.rs +++ b/crates/bevy_reflect/src/impls/macros/map.rs @@ -146,18 +146,8 @@ macro_rules! impl_reflect_for_hashmap { fn reflect_clone(&self) -> Result, $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(::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(::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); } diff --git a/crates/bevy_reflect/src/impls/macros/set.rs b/crates/bevy_reflect/src/impls/macros/set.rs index 599ec1c0c8..844b904cde 100644 --- a/crates/bevy_reflect/src/impls/macros/set.rs +++ b/crates/bevy_reflect/src/impls/macros/set.rs @@ -129,12 +129,7 @@ macro_rules! impl_reflect_for_hashset { fn reflect_clone(&self) -> Result, $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(::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); } diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 561111a901..86b7284381 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -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, ReflectCloneError> { Ok(Box::new( - self.iter() - .map(|value| { - value - .reflect_clone()? - .take() - .map_err(|_| ReflectCloneError::FailedDowncast { - expected: Cow::Borrowed(::type_path()), - received: Cow::Owned(value.reflect_type_path().to_string()), - }) - }) + // `(**self)` avoids getting `SmallVec as List::iter`, which + // would give us the wrong item type. + (**self) + .iter() + .map(PartialReflect::reflect_clone_and_take) .collect::>()?, )) } diff --git a/crates/bevy_reflect/src/kind.rs b/crates/bevy_reflect/src/kind.rs index 8988b30aa5..e8a2310b0f 100644 --- a/crates/bevy_reflect/src/kind.rs +++ b/crates/bevy_reflect/src/kind.rs @@ -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), diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index eaf601ef0d..99a0a1f7b5 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -2615,9 +2615,11 @@ bevy_reflect::tests::Test { #[reflect(where T: Default)] struct Foo(String, #[reflect(ignore)] PhantomData); + #[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(String, #[reflect(ignore)] PhantomData); + #[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::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; diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 1bd1795066..ffe9be54fe 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -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(&self) -> Result + where + Self: TypePath + Sized, + { + self.reflect_clone()? + .take() + .map_err(|_| ReflectCloneError::FailedDowncast { + expected: Cow::Borrowed(::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`. diff --git a/crates/bevy_render/Cargo.toml b/crates/bevy_render/Cargo.toml index 595abe76d0..31ac5113a4 100644 --- a/crates/bevy_render/Cargo.toml +++ b/crates/bevy_render/Cargo.toml @@ -78,6 +78,7 @@ bevy_tasks = { path = "../bevy_tasks", 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_camera = { path = "../bevy_camera", version = "0.17.0-dev" } +bevy_light = { path = "../bevy_light", optional = true, version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ "std", "serialize", diff --git a/crates/bevy_render/src/extract_impls.rs b/crates/bevy_render/src/extract_impls.rs new file mode 100644 index 0000000000..87b854363a --- /dev/null +++ b/crates/bevy_render/src/extract_impls.rs @@ -0,0 +1,41 @@ +//! This module exists because of the orphan rule + +use bevy_ecs::query::QueryItem; +use bevy_light::{cluster::ClusteredDecal, AmbientLight, ShadowFilteringMethod}; + +use crate::{extract_component::ExtractComponent, extract_resource::ExtractResource}; + +impl ExtractComponent for ClusteredDecal { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) + } +} +impl ExtractResource for AmbientLight { + type Source = Self; + + fn extract_resource(source: &Self::Source) -> Self { + source.clone() + } +} +impl ExtractComponent for AmbientLight { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component(item: QueryItem) -> Option { + Some(item.clone()) + } +} +impl ExtractComponent for ShadowFilteringMethod { + type QueryData = &'static Self; + type QueryFilter = (); + type Out = Self; + + fn extract_component(item: QueryItem) -> Option { + Some(*item) + } +} diff --git a/crates/bevy_render/src/lib.rs b/crates/bevy_render/src/lib.rs index 2141a12479..29bb44a298 100644 --- a/crates/bevy_render/src/lib.rs +++ b/crates/bevy_render/src/lib.rs @@ -50,6 +50,8 @@ pub mod sync_world; pub mod texture; pub mod view; pub use bevy_camera::primitives; +#[cfg(feature = "bevy_light")] +mod extract_impls; /// The render prelude. /// @@ -218,22 +220,6 @@ pub enum RenderSystems { PostCleanup, } -/// The schedule that contains the app logic that is evaluated each tick -/// -/// This is highly inspired by [`bevy_app::Main`] -#[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] -pub struct MainRender; -impl MainRender { - pub fn run(world: &mut World, mut run_at_least_once: Local) { - if !*run_at_least_once { - let _ = world.try_run_schedule(RenderStartup); - *run_at_least_once = true; - } - - let _ = world.try_run_schedule(Render); - } -} - /// Deprecated alias for [`RenderSystems`]. #[deprecated(since = "0.17.0", note = "Renamed to `RenderSystems`.")] pub type RenderSet = RenderSystems; @@ -573,7 +559,7 @@ unsafe fn initialize_render_app(app: &mut App) { app.init_resource::(); let mut render_app = SubApp::new(); - render_app.update_schedule = Some(MainRender.intern()); + render_app.update_schedule = Some(Render.intern()); let mut extract_schedule = Schedule::new(ExtractSchedule); // We skip applying any commands during the ExtractSchedule @@ -588,7 +574,6 @@ unsafe fn initialize_render_app(app: &mut App) { .add_schedule(extract_schedule) .add_schedule(Render::base_schedule()) .init_resource::() - .add_systems(MainRender, MainRender::run) .insert_resource(app.world().resource::().clone()) .add_systems(ExtractSchedule, PipelineCache::extract_shaders) .add_systems( @@ -604,7 +589,19 @@ unsafe fn initialize_render_app(app: &mut App) { ), ); - render_app.set_extract(|main_world, render_world| { + // We want the closure to have a flag to only run the RenderStartup schedule once, but the only + // way to have the closure store this flag is by capturing it. This variable is otherwise + // unused. + let mut should_run_startup = true; + render_app.set_extract(move |main_world, render_world| { + if should_run_startup { + // Run the `RenderStartup` if it hasn't run yet. This does mean `RenderStartup` blocks + // the rest of the app extraction, but this is necessary since extraction itself can + // depend on resources initialized in `RenderStartup`. + render_world.run_schedule(RenderStartup); + should_run_startup = false; + } + { #[cfg(feature = "trace")] let _stage_span = tracing::info_span!("entity_sync").entered(); diff --git a/crates/bevy_render/src/maths.wgsl b/crates/bevy_render/src/maths.wgsl index b492dd6bb2..d1e35523dc 100644 --- a/crates/bevy_render/src/maths.wgsl +++ b/crates/bevy_render/src/maths.wgsl @@ -104,17 +104,11 @@ fn project_onto(lhs: vec3, rhs: vec3) -> vec3 { // are likely most useful when raymarching, for example, where complete numeric // accuracy can be sacrificed for greater sample count. -fn fast_sqrt(x: f32) -> f32 { - let n = bitcast(0x1fbd1df5 + (bitcast(x) >> 1u)); - // One Newton's method iteration for better precision - return 0.5 * (n + x / n); -} - // Slightly less accurate than fast_acos_4, but much simpler. fn fast_acos(in_x: f32) -> f32 { let x = abs(in_x); var res = -0.156583 * x + HALF_PI; - res *= fast_sqrt(1.0 - x); + res *= sqrt(1.0 - x); return select(PI - res, res, in_x >= 0.0); } @@ -131,7 +125,7 @@ fn fast_acos_4(x: f32) -> f32 { s = -0.2121144 * x1 + 1.5707288; s = 0.0742610 * x2 + s; s = -0.0187293 * x3 + s; - s = fast_sqrt(1.0 - x1) * s; + s = sqrt(1.0 - x1) * s; // acos function mirroring return select(PI - s, s, x >= 0.0); diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index c171cf3957..bbdb543116 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -452,13 +452,17 @@ impl MeshAllocator { // Allocate. for (mesh_id, mesh) in &extracted_meshes.extracted { + let vertex_buffer_size = mesh.get_vertex_buffer_size() as u64; + if vertex_buffer_size == 0 { + continue; + } // Allocate vertex data. Note that we can only pack mesh vertex data // together if the platform supports it. let vertex_element_layout = ElementLayout::vertex(mesh_vertex_buffer_layouts, mesh); if self.general_vertex_slabs_supported { self.allocate( mesh_id, - mesh.get_vertex_buffer_size() as u64, + vertex_buffer_size, vertex_element_layout, &mut slabs_to_grow, mesh_allocator_settings, diff --git a/crates/bevy_render/src/pipelined_rendering.rs b/crates/bevy_render/src/pipelined_rendering.rs index efe728e7c7..00dfc4ba0e 100644 --- a/crates/bevy_render/src/pipelined_rendering.rs +++ b/crates/bevy_render/src/pipelined_rendering.rs @@ -197,7 +197,7 @@ fn renderer_extract(app_world: &mut World, _world: &mut World) { render_channels.send_blocking(render_app); } else { // Renderer thread panicked - world.send_event(AppExit::error()); + world.write_event(AppExit::error()); } }); }); diff --git a/crates/bevy_state/macros/Cargo.toml b/crates/bevy_state/macros/Cargo.toml index 2ab531a9b2..a4ff416632 100644 --- a/crates/bevy_state/macros/Cargo.toml +++ b/crates/bevy_state/macros/Cargo.toml @@ -13,7 +13,6 @@ bevy_macro_utils = { path = "../../bevy_macro_utils", version = "0.17.0-dev" } syn = { version = "2.0", features = ["full"] } quote = "1.0" -proc-macro2 = "1.0" [lints] workspace = true diff --git a/crates/bevy_state/src/app.rs b/crates/bevy_state/src/app.rs index c13abceeb9..27bfd826e1 100644 --- a/crates/bevy_state/src/app.rs +++ b/crates/bevy_state/src/app.rs @@ -107,7 +107,7 @@ impl AppExtStates for SubApp { ); S::register_state(schedule); let state = self.world().resource::>().get().clone(); - self.world_mut().send_event(StateTransitionEvent { + self.world_mut().write_event(StateTransitionEvent { exited: None, entered: Some(state), }); @@ -132,7 +132,7 @@ impl AppExtStates for SubApp { "The `StateTransition` schedule is missing. Did you forget to add StatesPlugin or DefaultPlugins before calling insert_state?" ); S::register_state(schedule); - self.world_mut().send_event(StateTransitionEvent { + self.world_mut().write_event(StateTransitionEvent { exited: None, entered: Some(state), }); @@ -145,7 +145,7 @@ impl AppExtStates for SubApp { self.world_mut() .resource_mut::>>() .clear(); - self.world_mut().send_event(StateTransitionEvent { + self.world_mut().write_event(StateTransitionEvent { exited: None, entered: Some(state), }); @@ -169,7 +169,7 @@ impl AppExtStates for SubApp { .world() .get_resource::>() .map(|s| s.get().clone()); - self.world_mut().send_event(StateTransitionEvent { + self.world_mut().write_event(StateTransitionEvent { exited: None, entered: state, }); @@ -200,7 +200,7 @@ impl AppExtStates for SubApp { .world() .get_resource::>() .map(|s| s.get().clone()); - self.world_mut().send_event(StateTransitionEvent { + self.world_mut().write_event(StateTransitionEvent { exited: None, entered: state, }); diff --git a/crates/bevy_state/src/state/transitions.rs b/crates/bevy_state/src/state/transitions.rs index 1ee21826c3..90b033b7fd 100644 --- a/crates/bevy_state/src/state/transitions.rs +++ b/crates/bevy_state/src/state/transitions.rs @@ -50,6 +50,8 @@ pub struct OnTransition { /// } /// ``` /// +/// This schedule is split up into four phases, as described in [`StateTransitionSteps`]. +/// /// [`PreStartup`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreStartup.html /// [`PreUpdate`]: https://docs.rs/bevy/latest/bevy/prelude/struct.PreUpdate.html #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash, Default)] diff --git a/crates/bevy_state/src/state_scoped_events.rs b/crates/bevy_state/src/state_scoped_events.rs index adba1ca6b6..a84490f64c 100644 --- a/crates/bevy_state/src/state_scoped_events.rs +++ b/crates/bevy_state/src/state_scoped_events.rs @@ -213,8 +213,8 @@ mod tests { app.add_event::() .clear_events_on_exit_state::(TestState::A); - app.world_mut().send_event(StandardEvent).unwrap(); - app.world_mut().send_event(StateScopedEvent).unwrap(); + app.world_mut().write_event(StandardEvent).unwrap(); + app.world_mut().write_event(StateScopedEvent).unwrap(); assert!(!app.world().resource::>().is_empty()); assert!(!app .world() @@ -243,8 +243,8 @@ mod tests { app.add_event::() .clear_events_on_enter_state::(TestState::B); - app.world_mut().send_event(StandardEvent).unwrap(); - app.world_mut().send_event(StateScopedEvent).unwrap(); + app.world_mut().write_event(StandardEvent).unwrap(); + app.world_mut().write_event(StateScopedEvent).unwrap(); assert!(!app.world().resource::>().is_empty()); assert!(!app .world() diff --git a/crates/bevy_time/src/lib.rs b/crates/bevy_time/src/lib.rs index c4118b876c..ff2e70b1db 100644 --- a/crates/bevy_time/src/lib.rs +++ b/crates/bevy_time/src/lib.rs @@ -343,15 +343,15 @@ mod tests { let fixed_update_timestep = Time::::default().timestep(); let time_step = fixed_update_timestep / 2 + Duration::from_millis(1); - fn send_event(mut events: ResMut>) { - events.send(DummyEvent); + fn write_event(mut events: ResMut>) { + events.write(DummyEvent); } let mut app = App::new(); app.add_plugins(TimePlugin) .add_event::() .init_resource::() - .add_systems(Startup, send_event) + .add_systems(Startup, write_event) .add_systems(FixedUpdate, count_fixed_updates) .insert_resource(TimeUpdateStrategy::ManualDuration(time_step)); diff --git a/crates/bevy_ui/Cargo.toml b/crates/bevy_ui/Cargo.toml index 86297f6df6..a26821a20c 100644 --- a/crates/bevy_ui/Cargo.toml +++ b/crates/bevy_ui/Cargo.toml @@ -36,10 +36,8 @@ bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-fea taffy = { version = "0.7" } serde = { version = "1", features = ["derive"], optional = true } uuid = { version = "1.1", features = ["v4"], optional = true } -bytemuck = { version = "1.5", features = ["derive"] } thiserror = { version = "2", default-features = false } derive_more = { version = "2", default-features = false, features = ["from"] } -nonmax = "0.5" smallvec = { version = "1", default-features = false } accesskit = "0.19" tracing = { version = "0.1", default-features = false, features = ["std"] } diff --git a/crates/bevy_ui/src/gradients.rs b/crates/bevy_ui/src/gradients.rs index eb1d255cc7..7086b37416 100644 --- a/crates/bevy_ui/src/gradients.rs +++ b/crates/bevy_ui/src/gradients.rs @@ -631,6 +631,14 @@ pub enum InterpolationColorSpace { Srgb, /// Interpolates in linear sRGB space. LinearRgb, + /// Interpolates in HSL space, taking the shortest hue path. + Hsl, + /// Interpolates in HSL space, taking the longest hue path. + HslLong, + /// Interpolates in HSV space, taking the shortest hue path. + Hsv, + /// Interpolates in HSV space, taking the longest hue path. + HsvLong, } /// Set the color space used for interpolation. diff --git a/crates/bevy_ui/src/widget/viewport.rs b/crates/bevy_ui/src/widget/viewport.rs index 120c3335c2..5002813cd4 100644 --- a/crates/bevy_ui/src/widget/viewport.rs +++ b/crates/bevy_ui/src/widget/viewport.rs @@ -147,7 +147,7 @@ pub fn viewport_picking( }; viewport_pointer_location.location = Some(location.clone()); - commands.send_event(PointerInput { + commands.write_event(PointerInput { location, pointer_id: viewport_pointer_id, action: input.action, diff --git a/crates/bevy_ui_render/Cargo.toml b/crates/bevy_ui_render/Cargo.toml index 249372c7f0..309e33ce40 100644 --- a/crates/bevy_ui_render/Cargo.toml +++ b/crates/bevy_ui_render/Cargo.toml @@ -23,7 +23,6 @@ bevy_render = { path = "../bevy_render", version = "0.17.0-dev" } bevy_sprite = { path = "../bevy_sprite", version = "0.17.0-dev" } bevy_picking = { path = "../bevy_picking", version = "0.17.0-dev", optional = true } bevy_transform = { path = "../bevy_transform", version = "0.17.0-dev" } -bevy_window = { path = "../bevy_window", 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", @@ -32,23 +31,13 @@ bevy_ui = { path = "../bevy_ui", version = "0.17.0-dev" } bevy_text = { path = "../bevy_text", version = "0.17.0-dev", default-features = false } # other -serde = { version = "1", features = ["derive"], optional = true } bytemuck = { version = "1.5", features = ["derive"] } -thiserror = { version = "2", default-features = false } derive_more = { version = "1", default-features = false, features = ["from"] } -nonmax = "0.5" -smallvec = { version = "1", default-features = false } -accesskit = "0.18" tracing = { version = "0.1", default-features = false, features = ["std"] } [features] default = [] -serialize = [ - "serde", - "smallvec/serde", - "bevy_math/serialize", - "bevy_platform/serialize", -] +serialize = ["bevy_math/serialize", "bevy_platform/serialize"] bevy_ui_picking_backend = ["bevy_picking"] bevy_ui_debug = [] diff --git a/crates/bevy_ui_render/src/gradient.rs b/crates/bevy_ui_render/src/gradient.rs index 9bef5340cb..12cfcbeb68 100644 --- a/crates/bevy_ui_render/src/gradient.rs +++ b/crates/bevy_ui_render/src/gradient.rs @@ -186,6 +186,10 @@ impl SpecializedRenderPipeline for GradientPipeline { InterpolationColorSpace::OkLchLong => "IN_OKLCH_LONG", InterpolationColorSpace::Srgb => "IN_SRGB", InterpolationColorSpace::LinearRgb => "IN_LINEAR_RGB", + InterpolationColorSpace::Hsl => "IN_HSL", + InterpolationColorSpace::HslLong => "IN_HSL_LONG", + InterpolationColorSpace::Hsv => "IN_HSV", + InterpolationColorSpace::HsvLong => "IN_HSV_LONG", }; let shader_defs = if key.anti_alias { diff --git a/crates/bevy_ui_render/src/gradient.wgsl b/crates/bevy_ui_render/src/gradient.wgsl index 074cf35a35..54bc35eb14 100644 --- a/crates/bevy_ui_render/src/gradient.wgsl +++ b/crates/bevy_ui_render/src/gradient.wgsl @@ -31,7 +31,7 @@ struct GradientVertexOutput { @location(0) uv: vec2, @location(1) @interpolate(flat) size: vec2, @location(2) @interpolate(flat) flags: u32, - @location(3) @interpolate(flat) radius: vec4, + @location(3) @interpolate(flat) radius: vec4, @location(4) @interpolate(flat) border: vec4, // Position relative to the center of the rectangle. @@ -114,27 +114,27 @@ fn fragment(in: GradientVertexOutput) -> @location(0) vec4 { } } -// This function converts two linear rgb colors to srgb space, mixes them, and then converts the result back to linear rgb space. -fn mix_linear_rgb_in_srgb_space(a: vec4, b: vec4, t: f32) -> vec4 { +// This function converts two linear rgba colors to srgba space, mixes them, and then converts the result back to linear rgb space. +fn mix_linear_rgba_in_srgba_space(a: vec4, b: vec4, t: f32) -> vec4 { let a_srgb = pow(a.rgb, vec3(1. / 2.2)); let b_srgb = pow(b.rgb, vec3(1. / 2.2)); let mixed_srgb = mix(a_srgb, b_srgb, t); return vec4(pow(mixed_srgb, vec3(2.2)), mix(a.a, b.a, t)); } -fn linear_rgb_to_oklab(c: vec4) -> vec4 { +fn linear_rgba_to_oklaba(c: vec4) -> vec4 { let l = pow(0.41222146 * c.x + 0.53633255 * c.y + 0.051445995 * c.z, 1. / 3.); let m = pow(0.2119035 * c.x + 0.6806995 * c.y + 0.10739696 * c.z, 1. / 3.); let s = pow(0.08830246 * c.x + 0.28171885 * c.y + 0.6299787 * c.z, 1. / 3.); return vec4( 0.21045426 * l + 0.7936178 * m - 0.004072047 * s, 1.9779985 * l - 2.4285922 * m + 0.4505937 * s, - 0.025904037 * l + 0.78277177 * m - 0.80867577 * s, - c.w + 0.025904037 * l + 0.78277177 * m - 0.80867577 * s, + c.a ); } -fn oklab_to_linear_rgba(c: vec4) -> vec4 { +fn oklaba_to_linear_rgba(c: vec4) -> vec4 { let l_ = c.x + 0.39633778 * c.y + 0.21580376 * c.z; let m_ = c.x - 0.105561346 * c.y - 0.06385417 * c.z; let s_ = c.x - 0.08948418 * c.y - 1.2914855 * c.z; @@ -145,26 +145,126 @@ fn oklab_to_linear_rgba(c: vec4) -> vec4 { 4.0767417 * l - 3.3077116 * m + 0.23096994 * s, -1.268438 * l + 2.6097574 * m - 0.34131938 * s, -0.0041960863 * l - 0.7034186 * m + 1.7076147 * s, - c.w + c.a ); } -fn mix_linear_rgb_in_oklab_space(a: vec4, b: vec4, t: f32) -> vec4 { - return oklab_to_linear_rgba(mix(linear_rgb_to_oklab(a), linear_rgb_to_oklab(b), t)); +fn mix_linear_rgba_in_oklaba_space(a: vec4, b: vec4, t: f32) -> vec4 { + return oklaba_to_linear_rgba(mix(linear_rgba_to_oklaba(a), linear_rgba_to_oklaba(b), t)); +} + +fn linear_rgba_to_hsla(c: vec4) -> vec4 { + let max = max(max(c.r, c.g), c.b); + let min = min(min(c.r, c.g), c.b); + let l = (max + min) * 0.5; + if max == min { + return vec4(0., 0., l, c.a); + } else { + let delta = max - min; + let s = delta / (1. - abs(2. * l - 1.)); + var h = 0.; + if max == c.r { + h = ((c.g - c.b) / delta) % 6.; + } else if max == c.g { + h = ((c.b - c.r) / delta) + 2.; + } else { + h = ((c.r - c.g) / delta) + 4.; + } + h = h / 6.; + return vec4(h, s, l, c.a); + } +} + + +fn hsla_to_linear_rgba(hsl: vec4) -> vec4 { + let h = hsl.x; + let s = hsl.y; + let l = hsl.z; + let c = (1.0 - abs(2.0 * l - 1.0)) * s; + let hp = h * 6.0; + let x = c * (1.0 - abs(hp % 2.0 - 1.0)); + var r: f32 = 0.0; + var g: f32 = 0.0; + var b: f32 = 0.0; + if 0.0 <= hp && hp < 1.0 { + r = c; g = x; b = 0.0; + } else if 1.0 <= hp && hp < 2.0 { + r = x; g = c; b = 0.0; + } else if 2.0 <= hp && hp < 3.0 { + r = 0.0; g = c; b = x; + } else if 3.0 <= hp && hp < 4.0 { + r = 0.0; g = x; b = c; + } else if 4.0 <= hp && hp < 5.0 { + r = x; g = 0.0; b = c; + } else if 5.0 <= hp && hp < 6.0 { + r = c; g = 0.0; b = x; + } + let m = l - 0.5 * c; + return vec4(r + m, g + m, b + m, hsl.a); +} + +fn linear_rgba_to_hsva(c: vec4) -> vec4 { + let maxc = max(max(c.r, c.g), c.b); + let minc = min(min(c.r, c.g), c.b); + let delta = maxc - minc; + var h: f32 = 0.0; + var s: f32 = 0.0; + let v: f32 = maxc; + if delta != 0.0 { + s = delta / maxc; + if maxc == c.r { + h = ((c.g - c.b) / delta) % 6.0; + } else if maxc == c.g { + h = ((c.b - c.r) / delta) + 2.0; + } else { + h = ((c.r - c.g) / delta) + 4.0; + } + h = h / 6.0; + if h < 0.0 { + h = h + 1.0; + } + } + return vec4(h, s, v, c.a); +} + +fn hsva_to_linear_rgba(hsva: vec4) -> vec4 { + let h = hsva.x * 6.0; + let s = hsva.y; + let v = hsva.z; + let c = v * s; + let x = c * (1.0 - abs(h % 2.0 - 1.0)); + let m = v - c; + var r: f32 = 0.0; + var g: f32 = 0.0; + var b: f32 = 0.0; + if 0.0 <= h && h < 1.0 { + r = c; g = x; b = 0.0; + } else if 1.0 <= h && h < 2.0 { + r = x; g = c; b = 0.0; + } else if 2.0 <= h && h < 3.0 { + r = 0.0; g = c; b = x; + } else if 3.0 <= h && h < 4.0 { + r = 0.0; g = x; b = c; + } else if 4.0 <= h && h < 5.0 { + r = x; g = 0.0; b = c; + } else if 5.0 <= h && h < 6.0 { + r = c; g = 0.0; b = x; + } + return vec4(r + m, g + m, b + m, hsva.a); } /// hue is left in radians and not converted to degrees -fn linear_rgb_to_oklch(c: vec4) -> vec4 { - let o = linear_rgb_to_oklab(c); +fn linear_rgba_to_oklcha(c: vec4) -> vec4 { + let o = linear_rgba_to_oklaba(c); let chroma = sqrt(o.y * o.y + o.z * o.z); let hue = atan2(o.z, o.y); - return vec4(o.x, chroma, select(hue + TAU, hue, hue < 0.0), o.w); + return vec4(o.x, chroma, rem_euclid(hue, TAU), o.a); } -fn oklch_to_linear_rgb(c: vec4) -> vec4 { +fn oklcha_to_linear_rgba(c: vec4) -> vec4 { let a = c.y * cos(c.z); let b = c.y * sin(c.z); - return oklab_to_linear_rgba(vec4(c.x, a, b, c.w)); + return oklaba_to_linear_rgba(vec4(c.x, a, b, c.a)); } fn rem_euclid(a: f32, b: f32) -> f32 { @@ -178,31 +278,82 @@ fn lerp_hue(a: f32, b: f32, t: f32) -> f32 { fn lerp_hue_long(a: f32, b: f32, t: f32) -> f32 { let diff = rem_euclid(b - a + PI, TAU) - PI; - return rem_euclid(a + select(diff - TAU, diff + TAU, 0. < diff) * t, TAU); + return rem_euclid(a + (diff + select(TAU, -TAU, 0. < diff)) * t, TAU); } -fn mix_oklch(a: vec4, b: vec4, t: f32) -> vec4 { +fn mix_oklcha(a: vec4, b: vec4, t: f32) -> vec4 { + let ah = select(a.z, b.z, a.y == 0.); + let bh = select(b.z, a.z, b.y == 0.); return vec4( mix(a.xy, b.xy, t), - lerp_hue(a.z, b.z, t), + lerp_hue(ah, bh, t), + mix(a.a, b.a, t) + ); +} + +fn mix_oklcha_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ah = select(a.z, b.z, a.y == 0.); + let bh = select(b.z, a.z, b.y == 0.); + return vec4( + mix(a.xy, b.xy, t), + lerp_hue_long(ah, bh, t), mix(a.w, b.w, t) ); } -fn mix_oklch_long(a: vec4, b: vec4, t: f32) -> vec4 { - return vec4( - mix(a.xy, b.xy, t), - lerp_hue_long(a.z, b.z, t), - mix(a.w, b.w, t) - ); +fn mix_linear_rgba_in_oklcha_space(a: vec4, b: vec4, t: f32) -> vec4 { + return oklcha_to_linear_rgba(mix_oklcha(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t)); } -fn mix_linear_rgb_in_oklch_space(a: vec4, b: vec4, t: f32) -> vec4 { - return oklch_to_linear_rgb(mix_oklch(linear_rgb_to_oklch(a), linear_rgb_to_oklch(b), t)); +fn mix_linear_rgba_in_oklcha_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + return oklcha_to_linear_rgba(mix_oklcha_long(linear_rgba_to_oklcha(a), linear_rgba_to_oklcha(b), t)); } -fn mix_linear_rgb_in_oklch_space_long(a: vec4, b: vec4, t: f32) -> vec4 { - return oklch_to_linear_rgb(mix_oklch_long(linear_rgb_to_oklch(a), linear_rgb_to_oklch(b), t)); +fn mix_linear_rgba_in_hsva_space(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsva(a); + let hb = linear_rgba_to_hsva(b); + var h: f32; + if ha.y == 0. { + h = hb.x; + } else if hb.y == 0. { + h = ha.x; + } else { + h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + } + let s = mix(ha.y, hb.y, t); + let v = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.a, hb.a, t); + return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); +} + +fn mix_linear_rgba_in_hsva_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsva(a); + let hb = linear_rgba_to_hsva(b); + let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let v = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.a, hb.a, t); + return hsva_to_linear_rgba(vec4(h, s, v, a_alpha)); +} + +fn mix_linear_rgba_in_hsla_space(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsla(a); + let hb = linear_rgba_to_hsla(b); + let h = lerp_hue(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let l = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.a, hb.a, t); + return hsla_to_linear_rgba(vec4(h, s, l, a_alpha)); +} + +fn mix_linear_rgba_in_hsla_space_long(a: vec4, b: vec4, t: f32) -> vec4 { + let ha = linear_rgba_to_hsla(a); + let hb = linear_rgba_to_hsla(b); + let h = lerp_hue_long(ha.x * TAU, hb.x * TAU, t) / TAU; + let s = mix(ha.y, hb.y, t); + let l = mix(ha.z, hb.z, t); + let a_alpha = mix(ha.a, hb.a, t); + return hsla_to_linear_rgba(vec4(h, s, l, a_alpha)); } // These functions are used to calculate the distance in gradient space from the start of the gradient to the point. @@ -277,13 +428,21 @@ fn interpolate_gradient( } #ifdef IN_SRGB - return mix_linear_rgb_in_srgb_space(start_color, end_color, t); + return mix_linear_rgba_in_srgba_space(start_color, end_color, t); #else ifdef IN_OKLAB - return mix_linear_rgb_in_oklab_space(start_color, end_color, t); + return mix_linear_rgba_in_oklaba_space(start_color, end_color, t); #else ifdef IN_OKLCH - return mix_linear_rgb_in_oklch_space(start_color, end_color, t); + return mix_linear_rgba_in_oklcha_space(start_color, end_color, t); #else ifdef IN_OKLCH_LONG - return mix_linear_rgb_in_oklch_space_long(start_color, end_color, t); + return mix_linear_rgba_in_oklcha_space_long(start_color, end_color, t); +#else ifdef IN_HSV + return mix_linear_rgba_in_hsva_space(start_color, end_color, t); +#else ifdef IN_HSV_LONG + return mix_linear_rgba_in_hsva_space_long(start_color, end_color, t); +#else ifdef IN_HSL + return mix_linear_rgba_in_hsla_space(start_color, end_color, t); +#else ifdef IN_HSL_LONG + return mix_linear_rgba_in_hsla_space_long(start_color, end_color, t); #else return mix(start_color, end_color, t); #endif diff --git a/crates/bevy_utils/src/debug_info.rs b/crates/bevy_utils/src/debug_info.rs index 292b77d352..71ce96ea00 100644 --- a/crates/bevy_utils/src/debug_info.rs +++ b/crates/bevy_utils/src/debug_info.rs @@ -79,7 +79,7 @@ impl DebugName { } } - /// Get the [`ShortName`] corresping to this debug name + /// Get the [`ShortName`] corresponding to this debug name /// /// The value will be a static string if the `debug` feature is not enabled pub fn shortname(&self) -> ShortName { diff --git a/crates/bevy_utils/src/parallel_queue.rs b/crates/bevy_utils/src/parallel_queue.rs index e97a48378d..8f35b4554d 100644 --- a/crates/bevy_utils/src/parallel_queue.rs +++ b/crates/bevy_utils/src/parallel_queue.rs @@ -5,7 +5,6 @@ use thread_local::ThreadLocal; /// A cohesive set of thread-local values of a given type. /// /// Mutable references can be fetched if `T: Default` via [`Parallel::scope`]. -#[derive(Default)] pub struct Parallel { locals: ThreadLocal>, } @@ -20,6 +19,25 @@ impl Parallel { pub fn clear(&mut self) { self.locals.clear(); } + + /// Retrieves the thread-local value for the current thread and runs `f` on it. + /// + /// If there is no thread-local value, it will be initialized to the result + /// of `create`. + pub fn scope_or(&self, create: impl FnOnce() -> T, f: impl FnOnce(&mut T) -> R) -> R { + f(&mut self.borrow_local_mut_or(create)) + } + + /// Mutably borrows the thread-local value. + /// + /// If there is no thread-local value, it will be initialized to the result + /// of `create`. + pub fn borrow_local_mut_or( + &self, + create: impl FnOnce() -> T, + ) -> impl DerefMut + '_ { + self.locals.get_or(|| RefCell::new(create())).borrow_mut() + } } impl Parallel { @@ -27,15 +45,14 @@ impl Parallel { /// /// If there is no thread-local value, it will be initialized to its default. pub fn scope(&self, f: impl FnOnce(&mut T) -> R) -> R { - let mut cell = self.locals.get_or_default().borrow_mut(); - f(cell.deref_mut()) + self.scope_or(Default::default, f) } /// Mutably borrows the thread-local value. /// /// If there is no thread-local value, it will be initialized to its default. pub fn borrow_local_mut(&self) -> impl DerefMut + '_ { - self.locals.get_or_default().borrow_mut() + self.borrow_local_mut_or(Default::default) } } @@ -72,3 +89,12 @@ impl Parallel> { } } } + +// `Default` is manually implemented to avoid the `T: Default` bound. +impl Default for Parallel { + fn default() -> Self { + Self { + locals: ThreadLocal::default(), + } + } +} diff --git a/crates/bevy_window/Cargo.toml b/crates/bevy_window/Cargo.toml index 1a5bca6916..2cd96053b4 100644 --- a/crates/bevy_window/Cargo.toml +++ b/crates/bevy_window/Cargo.toml @@ -22,12 +22,7 @@ bevy_reflect = [ ] ## Adds serialization support through `serde`. -serialize = [ - "serde", - "smol_str/serde", - "bevy_ecs/serialize", - "bevy_input/serialize", -] +serialize = ["serde", "bevy_ecs/serialize", "bevy_input/serialize"] # Platform Compatibility @@ -56,9 +51,7 @@ bevy_input = { path = "../bevy_input", version = "0.17.0-dev", default-features bevy_math = { path = "../bevy_math", version = "0.17.0-dev", default-features = false } bevy_reflect = { path = "../bevy_reflect", version = "0.17.0-dev", default-features = false, features = [ "glam", - "smol_str", ], optional = true } -bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev", default-features = false } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false } # other @@ -69,7 +62,6 @@ serde = { version = "1.0", features = [ raw-window-handle = { version = "0.6", features = [ "alloc", ], default-features = false } -smol_str = { version = "0.2", default-features = false } log = { version = "0.4", default-features = false } [target.'cfg(target_os = "android")'.dependencies] diff --git a/crates/bevy_winit/Cargo.toml b/crates/bevy_winit/Cargo.toml index 43dcc0506b..3ad2e4379e 100644 --- a/crates/bevy_winit/Cargo.toml +++ b/crates/bevy_winit/Cargo.toml @@ -16,7 +16,6 @@ x11 = ["winit/x11"] accesskit_unix = ["accesskit_winit/accesskit_unix", "accesskit_winit/async-io"] serialize = [ - "serde", "bevy_input/serialize", "bevy_window/serialize", "bevy_platform/serialize", @@ -38,7 +37,6 @@ bevy_log = { path = "../bevy_log", 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_window = { path = "../bevy_window", version = "0.17.0-dev" } -bevy_utils = { path = "../bevy_utils", version = "0.17.0-dev" } bevy_tasks = { path = "../bevy_tasks", version = "0.17.0-dev" } bevy_platform = { path = "../bevy_platform", version = "0.17.0-dev", default-features = false, features = [ "std", @@ -57,7 +55,6 @@ accesskit_winit = { version = "0.27", default-features = false, features = [ approx = { version = "0.5", default-features = false } cfg-if = "1.0" raw-window-handle = "0.6" -serde = { version = "1.0", features = ["derive"], optional = true } bytemuck = { version = "1.5", optional = true } wgpu-types = { version = "25", optional = true } accesskit = "0.19" diff --git a/crates/bevy_winit/src/state.rs b/crates/bevy_winit/src/state.rs index 5b873d4620..97c26db7d8 100644 --- a/crates/bevy_winit/src/state.rs +++ b/crates/bevy_winit/src/state.rs @@ -240,7 +240,7 @@ impl ApplicationHandler for WinitAppRunnerState { fn user_event(&mut self, _event_loop: &ActiveEventLoop, event: T) { self.user_event_received = true; - self.world_mut().send_event(event); + self.world_mut().write_event(event); self.redraw_requested = true; } @@ -790,91 +790,91 @@ impl WinitAppRunnerState { if !raw_winit_events.is_empty() { world .resource_mut::>() - .send_batch(raw_winit_events); + .write_batch(raw_winit_events); } for winit_event in buffered_events.iter() { match winit_event.clone() { BevyWindowEvent::AppLifecycle(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::CursorEntered(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::CursorLeft(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::CursorMoved(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::FileDragAndDrop(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::Ime(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::RequestRedraw(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowBackendScaleFactorChanged(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowCloseRequested(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowCreated(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowDestroyed(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowFocused(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowMoved(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowOccluded(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowResized(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowScaleFactorChanged(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::WindowThemeChanged(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::MouseButtonInput(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::MouseMotion(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::MouseWheel(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::PinchGesture(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::RotationGesture(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::DoubleTapGesture(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::PanGesture(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::TouchInput(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::KeyboardInput(e) => { - world.send_event(e); + world.write_event(e); } BevyWindowEvent::KeyboardFocusLost(e) => { - world.send_event(e); + world.write_event(e); } } } @@ -882,7 +882,7 @@ impl WinitAppRunnerState { if !buffered_events.is_empty() { world .resource_mut::>() - .send_batch(buffered_events); + .write_batch(buffered_events); } } diff --git a/examples/3d/3d_shapes.rs b/examples/3d/3d_shapes.rs index 2ef3a65dc3..3fab989a1b 100644 --- a/examples/3d/3d_shapes.rs +++ b/examples/3d/3d_shapes.rs @@ -1,5 +1,14 @@ -//! This example demonstrates the built-in 3d shapes in Bevy. -//! The scene includes a patterned texture and a rotation for visualizing the normals and UVs. +//! Here we use shape primitives to generate meshes for 3d objects as well as attaching a runtime-generated patterned texture to each 3d object. +//! +//! "Shape primitives" here are just the mathematical definition of certain shapes, they're not meshes on their own! A sphere with radius `1.0` can be defined with [`Sphere::new(1.0)`][Sphere::new] but all this does is store the radius. So we need to turn these descriptions of shapes into meshes. +//! +//! While a shape is not a mesh, turning it into one in Bevy is easy. In this example we call [`meshes.add(/* Shape here! */)`][Assets::add] on the shape, which works because the [`Assets::add`] method takes anything that can be turned into the asset type it stores. There's an implementation for [`From`] on shape primitives into [`Mesh`], so that will get called internally by [`Assets::add`]. +//! +//! [`Extrusion`] lets us turn 2D shape primitives into versions of those shapes that have volume by extruding them. A 1x1 square that gets wrapped in this with an extrusion depth of 2 will give us a rectangular prism of size 1x1x2, but here we're just extruding these 2d shapes by depth 1. +//! +//! The material applied to these shapes is a texture that we generate at run time by looping through a "palette" of RGBA values (stored adjacent to each other in the array) and writing values to positions in another array that represents the buffer for an 8x8 texture. This texture is then registered with the assets system just one time, with that [`Handle`] then applied to all the shapes in this example. +//! +//! The mesh and material are [`Handle`] and [`Handle`] at the moment, neither of which implement `Component` on their own. Handles are put behind "newtypes" to prevent ambiguity, as some entities might want to have handles to meshes (or images, or materials etc.) for different purposes! All we need to do to make them rendering-relevant components is wrap the mesh handle and the material handle in [`Mesh3d`] and [`MeshMaterial3d`] respectively. //! //! You can toggle wireframes with the space bar except on wasm. Wasm does not support //! `POLYGON_MODE_LINE` on the gpu. diff --git a/examples/3d/clustered_decals.rs b/examples/3d/clustered_decals.rs index 60a445b483..a0593af0e0 100644 --- a/examples/3d/clustered_decals.rs +++ b/examples/3d/clustered_decals.rs @@ -165,7 +165,7 @@ fn setup( // Error out if clustered decals aren't supported on the current platform. if !decal::clustered::clustered_decals_are_usable(&render_device, &render_adapter) { error!("Clustered decals aren't usable on this platform."); - commands.send_event(AppExit::error()); + commands.write_event(AppExit::error()); } spawn_cube(&mut commands, &mut meshes, &mut materials); diff --git a/examples/3d/light_textures.rs b/examples/3d/light_textures.rs index 94ddd159f2..c7cfb86f88 100644 --- a/examples/3d/light_textures.rs +++ b/examples/3d/light_textures.rs @@ -6,13 +6,7 @@ use std::fmt::{self, Formatter}; use bevy::{ color::palettes::css::{SILVER, YELLOW}, input::mouse::AccumulatedMouseMotion, - pbr::{ - decal::{ - self, - clustered::{DirectionalLightTexture, PointLightTexture, SpotLightTexture}, - }, - NotShadowCaster, - }, + pbr::{decal, DirectionalLightTexture, NotShadowCaster, PointLightTexture, SpotLightTexture}, prelude::*, render::renderer::{RenderAdapter, RenderDevice}, window::SystemCursorIcon, @@ -157,7 +151,7 @@ fn setup( // Error out if clustered decals (and so light textures) aren't supported on the current platform. if !decal::clustered::clustered_decals_are_usable(&render_device, &render_adapter) { error!("Light textures aren't usable on this platform."); - commands.send_event(AppExit::error()); + commands.write_event(AppExit::error()); } spawn_cubes(&mut commands, &mut meshes, &mut materials); diff --git a/examples/ecs/component_hooks.rs b/examples/ecs/component_hooks.rs index 7a97bf454c..a50452a11e 100644 --- a/examples/ecs/component_hooks.rs +++ b/examples/ecs/component_hooks.rs @@ -95,7 +95,7 @@ fn setup(world: &mut World) { .resource_mut::() .insert(value, entity); // Or send events - world.send_event(MyEvent); + world.write_event(MyEvent); }, ) // `on_insert` will trigger when a component is inserted onto an entity, diff --git a/examples/ecs/send_and_receive_events.rs b/examples/ecs/send_and_receive_events.rs index 78dd5b240b..7a376529f5 100644 --- a/examples/ecs/send_and_receive_events.rs +++ b/examples/ecs/send_and_receive_events.rs @@ -160,6 +160,6 @@ fn send_and_receive_manual_event_reader( for mut event in events_to_resend { event.times_sent += 1; - events.send(event); + events.write(event); } } diff --git a/examples/shader/extended_material.rs b/examples/shader/extended_material.rs index 3c5102db8a..7253d22949 100644 --- a/examples/shader/extended_material.rs +++ b/examples/shader/extended_material.rs @@ -41,7 +41,7 @@ fn setup( // change the above to `OpaqueRendererMethod::Deferred` or add the `DefaultOpaqueRendererMethod` resource. ..Default::default() }, - extension: MyExtension { quantize_steps: 3 }, + extension: MyExtension::new(1), })), Transform::from_xyz(0.0, 0.5, 0.0), )); @@ -69,12 +69,30 @@ fn rotate_things(mut q: Query<&mut Transform, With>, time: Res