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 9d6e921638..f047040bdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -868,6 +868,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]] @@ -945,6 +946,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 @@ -1012,6 +1014,7 @@ doc-scrape-examples = true name = "Anti-aliasing" description = "Compares different anti-aliasing methods" category = "3D Rendering" +# TAA not supported by WebGL wasm = false [[example]] @@ -1056,6 +1059,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]] @@ -1123,6 +1127,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]] @@ -1222,6 +1227,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]] @@ -1321,7 +1327,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" @@ -1432,6 +1438,7 @@ doc-scrape-examples = true name = "Wireframe" description = "Showcases wireframe rendering" category = "3D Rendering" +# Not supported on WebGL wasm = false [[example]] @@ -1443,6 +1450,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]] @@ -1455,6 +1464,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 = [ [ @@ -1490,7 +1500,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" @@ -1643,6 +1653,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]] @@ -1654,6 +1665,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]] @@ -1665,6 +1677,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]] @@ -1688,6 +1701,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]] @@ -1710,6 +1724,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]] @@ -1721,6 +1737,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]] @@ -4325,6 +4342,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/mod.rs b/crates/bevy_anti_aliasing/src/smaa/mod.rs index d18e17da09..33a916e489 100644 --- a/crates/bevy_anti_aliasing/src/smaa/mod.rs +++ b/crates/bevy_anti_aliasing/src/smaa/mod.rs @@ -30,9 +30,7 @@ //! //! [SMAA]: https://www.iryoku.com/smaa/ use bevy_app::{App, Plugin}; -#[cfg(feature = "smaa_luts")] -use bevy_asset::load_internal_binary_asset; -use bevy_asset::{embedded_asset, load_embedded_asset, uuid_handle, AssetServer, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle}; #[cfg(not(feature = "smaa_luts"))] use bevy_core_pipeline::tonemapping::lut_placeholder; use bevy_core_pipeline::{ @@ -79,13 +77,6 @@ use bevy_render::{ }; use bevy_utils::prelude::default; -/// The handle of the area LUT, a KTX2 format texture that SMAA uses internally. -const SMAA_AREA_LUT_TEXTURE_HANDLE: Handle = - uuid_handle!("569c4d67-c7fa-4958-b1af-0836023603c0"); -/// The handle of the search LUT, a KTX2 format texture that SMAA uses internally. -const SMAA_SEARCH_LUT_TEXTURE_HANDLE: Handle = - uuid_handle!("43b97515-252e-4c8a-b9af-f2fc528a1c27"); - /// Adds support for subpixel morphological antialiasing, or SMAA. pub struct SmaaPlugin; @@ -125,6 +116,14 @@ pub enum SmaaPreset { Ultra, } +#[derive(Resource)] +struct SmaaLuts { + /// The handle of the area LUT, a KTX2 format texture that SMAA uses internally. + area_lut: Handle, + /// The handle of the search LUT, a KTX2 format texture that SMAA uses internally. + search_lut: Handle, +} + /// A render world resource that holds all render pipeline data needed for SMAA. /// /// There are three separate passes, so we need three separate pipelines. @@ -292,49 +291,26 @@ impl Plugin for SmaaPlugin { // Load the shader. embedded_asset!(app, "smaa.wgsl"); - // Load the two lookup textures. These are compressed textures in KTX2 - // format. #[cfg(feature = "smaa_luts")] - load_internal_binary_asset!( - app, - SMAA_AREA_LUT_TEXTURE_HANDLE, - "SMAAAreaLUT.ktx2", - |bytes, _: String| Image::from_buffer( - bytes, - bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2), - bevy_image::CompressedImageFormats::NONE, - false, - bevy_image::ImageSampler::Default, - bevy_asset::RenderAssetUsages::RENDER_WORLD, - ) - .expect("Failed to load SMAA area LUT") - ); - - #[cfg(feature = "smaa_luts")] - load_internal_binary_asset!( - app, - SMAA_SEARCH_LUT_TEXTURE_HANDLE, - "SMAASearchLUT.ktx2", - |bytes, _: String| Image::from_buffer( - bytes, - bevy_image::ImageType::Format(bevy_image::ImageFormat::Ktx2), - bevy_image::CompressedImageFormats::NONE, - false, - bevy_image::ImageSampler::Default, - bevy_asset::RenderAssetUsages::RENDER_WORLD, - ) - .expect("Failed to load SMAA search LUT") - ); + let smaa_luts = { + // Load the two lookup textures. These are compressed textures in KTX2 format. + embedded_asset!(app, "SMAAAreaLUT.ktx2"); + embedded_asset!(app, "SMAASearchLUT.ktx2"); + SmaaLuts { + area_lut: load_embedded_asset!(app, "SMAAAreaLUT.ktx2"), + search_lut: load_embedded_asset!(app, "SMAASearchLUT.ktx2"), + } + }; #[cfg(not(feature = "smaa_luts"))] - app.world_mut() - .resource_mut::>() - .insert(SMAA_AREA_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); - - #[cfg(not(feature = "smaa_luts"))] - app.world_mut() - .resource_mut::>() - .insert(SMAA_SEARCH_LUT_TEXTURE_HANDLE.id(), lut_placeholder()); + let smaa_luts = { + let mut images = app.world_mut().resource_mut::>(); + let handle = images.add(lut_placeholder()); + SmaaLuts { + area_lut: handle.clone(), + search_lut: handle.clone(), + } + }; app.add_plugins(ExtractComponentPlugin::::default()) .register_type::(); @@ -344,6 +320,7 @@ impl Plugin for SmaaPlugin { }; render_app + .insert_resource(smaa_luts) .init_resource::() .init_resource::() .add_systems(RenderStartup, init_smaa_pipelines) @@ -747,13 +724,14 @@ fn prepare_smaa_bind_groups( mut commands: Commands, render_device: Res, smaa_pipelines: Res, + smaa_luts: Res, images: Res>, view_targets: Query<(Entity, &SmaaTextures), (With, With)>, ) { // Fetch the two lookup textures. These are bundled in this library. let (Some(search_texture), Some(area_texture)) = ( - images.get(&SMAA_SEARCH_LUT_TEXTURE_HANDLE), - images.get(&SMAA_AREA_LUT_TEXTURE_HANDLE), + images.get(&smaa_luts.search_lut), + images.get(&smaa_luts.area_lut), ) else { return; }; 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_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index b9a20a7af8..838c618d8e 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -492,8 +492,8 @@ impl TryFrom for Handle { /// /// ``` /// # use bevy_asset::{Handle, uuid_handle}; -/// # type Shader = (); -/// const SHADER: Handle = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); +/// # type Image = (); +/// const IMAGE: Handle = uuid_handle!("1347c9b7-c46a-48e7-b7b8-023a354b7cac"); /// ``` #[macro_export] macro_rules! uuid_handle { 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_pipeline/src/experimental/mip_generation/mod.rs b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs index 1733743310..2080efdabb 100644 --- a/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs +++ b/crates/bevy_core_pipeline/src/experimental/mip_generation/mod.rs @@ -12,7 +12,7 @@ use crate::core_3d::{ prepare_core_3d_depth_textures, }; use bevy_app::{App, Plugin}; -use bevy_asset::{load_internal_asset, uuid_handle, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -51,8 +51,8 @@ use bitflags::bitflags; use tracing::debug; /// Identifies the `downsample_depth.wgsl` shader. -pub const DOWNSAMPLE_DEPTH_SHADER_HANDLE: Handle = - uuid_handle!("a09a149e-5922-4fa4-9170-3c1a13065364"); +#[derive(Resource, Deref)] +pub struct DownsampleDepthShader(Handle); /// The maximum number of mip levels that we can produce. /// @@ -69,18 +69,16 @@ pub struct MipGenerationPlugin; impl Plugin for MipGenerationPlugin { fn build(&self, app: &mut App) { - load_internal_asset!( - app, - DOWNSAMPLE_DEPTH_SHADER_HANDLE, - "downsample_depth.wgsl", - Shader::from_wgsl - ); + embedded_asset!(app, "downsample_depth.wgsl"); + + let downsample_depth_shader = load_embedded_asset!(app, "downsample_depth.wgsl"); let Some(render_app) = app.get_sub_app_mut(RenderApp) else { return; }; render_app + .insert_resource(DownsampleDepthShader(downsample_depth_shader)) .init_resource::>() .add_render_graph_node::(Core3d, Node3d::EarlyDownsampleDepth) .add_render_graph_node::(Core3d, Node3d::LateDownsampleDepth) @@ -294,17 +292,21 @@ pub struct DownsampleDepthPipeline { bind_group_layout: BindGroupLayout, /// A handle that identifies the compiled shader. pipeline_id: Option, + /// The shader asset handle. + shader: Handle, } impl DownsampleDepthPipeline { - /// Creates a new [`DownsampleDepthPipeline`] from a bind group layout. + /// Creates a new [`DownsampleDepthPipeline`] from a bind group layout and the downsample + /// shader. /// /// This doesn't actually specialize the pipeline; that must be done /// afterward. - fn new(bind_group_layout: BindGroupLayout) -> DownsampleDepthPipeline { + fn new(bind_group_layout: BindGroupLayout, shader: Handle) -> DownsampleDepthPipeline { DownsampleDepthPipeline { bind_group_layout, pipeline_id: None, + shader, } } } @@ -335,6 +337,7 @@ fn create_downsample_depth_pipelines( pipeline_cache: Res, mut specialized_compute_pipelines: ResMut>, gpu_preprocessing_support: Res, + downsample_depth_shader: Res, mut has_run: Local, ) { // Only run once. @@ -368,10 +371,22 @@ fn create_downsample_depth_pipelines( // Initialize the pipelines. let mut downsample_depth_pipelines = DownsampleDepthPipelines { - first: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), - second: DownsampleDepthPipeline::new(standard_bind_group_layout.clone()), - first_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), - second_multisample: DownsampleDepthPipeline::new(multisampled_bind_group_layout.clone()), + first: DownsampleDepthPipeline::new( + standard_bind_group_layout.clone(), + downsample_depth_shader.0.clone(), + ), + second: DownsampleDepthPipeline::new( + standard_bind_group_layout.clone(), + downsample_depth_shader.0.clone(), + ), + first_multisample: DownsampleDepthPipeline::new( + multisampled_bind_group_layout.clone(), + downsample_depth_shader.0.clone(), + ), + second_multisample: DownsampleDepthPipeline::new( + multisampled_bind_group_layout.clone(), + downsample_depth_shader.0.clone(), + ), sampler, }; @@ -491,7 +506,7 @@ impl SpecializedComputePipeline for DownsampleDepthPipeline { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: self.shader.clone(), shader_defs, entry_point: Some(if key.contains(DownsampleDepthPipelineKey::SECOND_PHASE) { "downsample_depth_second".into() diff --git a/crates/bevy_core_pipeline/src/post_process/mod.rs b/crates/bevy_core_pipeline/src/post_process/mod.rs index 0077cebdf5..ce77fc1a75 100644 --- a/crates/bevy_core_pipeline/src/post_process/mod.rs +++ b/crates/bevy_core_pipeline/src/post_process/mod.rs @@ -3,7 +3,7 @@ //! Currently, this consists only of chromatic aberration. use bevy_app::{App, Plugin}; -use bevy_asset::{embedded_asset, load_embedded_asset, uuid_handle, Assets, Handle}; +use bevy_asset::{embedded_asset, load_embedded_asset, Assets, Handle}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ component::Component, @@ -47,13 +47,6 @@ use crate::{ FullscreenShader, }; -/// The handle to the default chromatic aberration lookup texture. -/// -/// This is just a 3x1 image consisting of one red pixel, one green pixel, and -/// one blue pixel, in that order. -const DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE: Handle = - uuid_handle!("dc3e3307-40a1-49bb-be6d-e0634e8836b2"); - /// The default chromatic aberration intensity amount, in a fraction of the /// window size. const DEFAULT_CHROMATIC_ABERRATION_INTENSITY: f32 = 0.02; @@ -68,6 +61,9 @@ const DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES: u32 = 8; static DEFAULT_CHROMATIC_ABERRATION_LUT_DATA: [u8; 12] = [255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255]; +#[derive(Resource)] +struct DefaultChromaticAberrationLut(Handle); + /// A plugin that implements a built-in postprocessing stack with some common /// effects. /// @@ -96,14 +92,14 @@ pub struct PostProcessingPlugin; pub struct ChromaticAberration { /// The lookup texture that determines the color gradient. /// - /// By default, this is a 3×1 texel texture consisting of one red pixel, one - /// green pixel, and one blue texel, in that order. This recreates the most - /// typical chromatic aberration pattern. However, you can change it to - /// achieve different artistic effects. + /// By default (if None), this is a 3×1 texel texture consisting of one red + /// pixel, one green pixel, and one blue texel, in that order. This + /// recreates the most typical chromatic aberration pattern. However, you + /// can change it to achieve different artistic effects. /// /// The texture is always sampled in its vertical center, so it should /// ordinarily have a height of 1 texel. - pub color_lut: Handle, + pub color_lut: Option>, /// The size of the streaks around the edges of objects, as a fraction of /// the window size. @@ -192,20 +188,17 @@ impl Plugin for PostProcessingPlugin { // Load the default chromatic aberration LUT. let mut assets = app.world_mut().resource_mut::>(); - assets.insert( - DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE.id(), - Image::new( - Extent3d { - width: 3, - height: 1, - depth_or_array_layers: 1, - }, - TextureDimension::D2, - DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(), - TextureFormat::Rgba8UnormSrgb, - RenderAssetUsages::RENDER_WORLD, - ), - ); + let default_lut = assets.add(Image::new( + Extent3d { + width: 3, + height: 1, + depth_or_array_layers: 1, + }, + TextureDimension::D2, + DEFAULT_CHROMATIC_ABERRATION_LUT_DATA.to_vec(), + TextureFormat::Rgba8UnormSrgb, + RenderAssetUsages::RENDER_WORLD, + )); app.register_type::(); app.add_plugins(ExtractComponentPlugin::::default()); @@ -215,6 +208,7 @@ impl Plugin for PostProcessingPlugin { }; render_app + .insert_resource(DefaultChromaticAberrationLut(default_lut)) .init_resource::>() .init_resource::() .add_systems( @@ -258,7 +252,7 @@ impl Plugin for PostProcessingPlugin { impl Default for ChromaticAberration { fn default() -> Self { Self { - color_lut: DEFAULT_CHROMATIC_ABERRATION_LUT_HANDLE, + color_lut: None, intensity: DEFAULT_CHROMATIC_ABERRATION_INTENSITY, max_samples: DEFAULT_CHROMATIC_ABERRATION_MAX_SAMPLES, } @@ -357,6 +351,7 @@ impl ViewNode for PostProcessingNode { let post_processing_pipeline = world.resource::(); let post_processing_uniform_buffers = world.resource::(); let gpu_image_assets = world.resource::>(); + let default_lut = world.resource::(); // We need a render pipeline to be prepared. let Some(pipeline) = pipeline_cache.get_render_pipeline(**pipeline_id) else { @@ -364,8 +359,12 @@ impl ViewNode for PostProcessingNode { }; // We need the chromatic aberration LUT to be present. - let Some(chromatic_aberration_lut) = gpu_image_assets.get(&chromatic_aberration.color_lut) - else { + let Some(chromatic_aberration_lut) = gpu_image_assets.get( + chromatic_aberration + .color_lut + .as_ref() + .unwrap_or(&default_lut.0), + ) else { return Ok(()); }; 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_light/Cargo.toml b/crates/bevy_light/Cargo.toml index 0dd7e158c8..6a3807f9bb 100644 --- a/crates/bevy_light/Cargo.toml +++ b/crates/bevy_light/Cargo.toml @@ -19,7 +19,6 @@ 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_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" } bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [ @@ -27,7 +26,6 @@ bevy_color = { path = "../bevy_color", version = "0.17.0-dev", features = [ ] } # other -serde = { version = "1", default-features = false, features = ["derive"] } tracing = { version = "0.1", default-features = false } [features] diff --git a/crates/bevy_light/src/lib.rs b/crates/bevy_light/src/lib.rs index cf7e19ed81..655171da63 100644 --- a/crates/bevy_light/src/lib.rs +++ b/crates/bevy_light/src/lib.rs @@ -27,7 +27,7 @@ use cluster::{ mod ambient_light; pub use ambient_light::AmbientLight; mod probe; -pub use probe::{EnvironmentMapLight, LightProbe}; +pub use probe::{EnvironmentMapLight, IrradianceVolume, LightProbe}; mod volumetric; pub use volumetric::{FogVolume, VolumetricFog, VolumetricLight}; pub mod cascade; @@ -121,6 +121,7 @@ impl Plugin for LightPlugin { .register_type::() .register_type::() .register_type::() + .register_type::() .register_type::() .register_type::() .register_type::() diff --git a/crates/bevy_light/src/probe.rs b/crates/bevy_light/src/probe.rs index 7cd7e2f634..5683daa562 100644 --- a/crates/bevy_light/src/probe.rs +++ b/crates/bevy_light/src/probe.rs @@ -107,3 +107,50 @@ impl Default for EnvironmentMapLight { } } } + +/// 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_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 9e10c9387e..299c0f54c3 100644 --- a/crates/bevy_pbr/Cargo.toml +++ b/crates/bevy_pbr/Cargo.toml @@ -21,7 +21,7 @@ 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", @@ -51,7 +51,6 @@ 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", ] } @@ -66,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/lib.rs b/crates/bevy_pbr/src/lib.rs index 994cb72b25..45aa6297d2 100644 --- a/crates/bevy_pbr/src/lib.rs +++ b/crates/bevy_pbr/src/lib.rs @@ -51,9 +51,9 @@ use bevy_light::SimulationLightSystems; pub use bevy_light::{ light_consts, AmbientLight, CascadeShadowConfig, CascadeShadowConfigBuilder, Cascades, ClusteredDecal, DirectionalLight, DirectionalLightShadowMap, DirectionalLightTexture, - FogVolume, LightPlugin, LightProbe, NotShadowCaster, NotShadowReceiver, PointLight, - PointLightShadowMap, PointLightTexture, ShadowFilteringMethod, SpotLight, SpotLightTexture, - TransmittedShadowReceiver, VolumetricFog, VolumetricLight, + FogVolume, IrradianceVolume, LightPlugin, LightProbe, NotShadowCaster, NotShadowReceiver, + PointLight, PointLightShadowMap, PointLightTexture, ShadowFilteringMethod, SpotLight, + SpotLightTexture, TransmittedShadowReceiver, VolumetricFog, VolumetricLight, }; pub use cluster::*; pub use components::*; diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index 49fa9185d3..95ed8dcbd9 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -133,9 +133,8 @@ //! //! [Why ambient cubes?]: #why-ambient-cubes -use bevy_ecs::{component::Component, reflect::ReflectComponent}; use bevy_image::Image; -use bevy_light::LightProbe; +pub use bevy_light::IrradianceVolume; use bevy_render::{ render_asset::RenderAssets, render_resource::{ @@ -145,11 +144,9 @@ 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, @@ -163,53 +160,6 @@ use super::LightProbeComponent; /// (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 2884c86a2f..bce844bb21 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -288,8 +288,7 @@ impl Plugin for LightProbePlugin { load_shader_library!(app, "environment_map.wgsl"); load_shader_library!(app, "irradiance_volume.wgsl"); - app.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/pipelines.rs b/crates/bevy_pbr/src/meshlet/pipelines.rs index 6ac22f0fba..0fe9905d32 100644 --- a/crates/bevy_pbr/src/meshlet/pipelines.rs +++ b/crates/bevy_pbr/src/meshlet/pipelines.rs @@ -1,7 +1,7 @@ use super::resource_manager::ResourceManager; use bevy_asset::{load_embedded_asset, Handle}; use bevy_core_pipeline::{ - core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DOWNSAMPLE_DEPTH_SHADER_HANDLE, + core_3d::CORE_3D_DEPTH_FORMAT, experimental::mip_generation::DownsampleDepthShader, FullscreenShader, }; use bevy_ecs::{ @@ -84,6 +84,7 @@ impl FromWorld for MeshletPipelines { .remap_1d_to_2d_dispatch_bind_group_layout .clone(); + let downsample_depth_shader = (*world.resource::()).clone(); let vertex_state = world.resource::().to_vertex_state(); let fill_counts_layout = resource_manager.fill_counts_bind_group_layout.clone(); @@ -230,7 +231,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec![ "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET".into(), @@ -248,7 +249,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec![ "MESHLET_VISIBILITY_BUFFER_RASTER_PASS_OUTPUT".into(), "MESHLET".into(), @@ -266,7 +267,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader.clone(), shader_defs: vec!["MESHLET".into()], entry_point: Some("downsample_depth_first".into()), ..default() @@ -281,7 +282,7 @@ impl FromWorld for MeshletPipelines { stages: ShaderStages::COMPUTE, range: 0..4, }], - shader: DOWNSAMPLE_DEPTH_SHADER_HANDLE, + shader: downsample_depth_shader, shader_defs: vec!["MESHLET".into()], entry_point: Some("downsample_depth_second".into()), zero_initialize_workgroup_memory: false, diff --git a/crates/bevy_pbr/src/volumetric_fog/mod.rs b/crates/bevy_pbr/src/volumetric_fog/mod.rs index a4099aeb62..e28412d7bd 100644 --- a/crates/bevy_pbr/src/volumetric_fog/mod.rs +++ b/crates/bevy_pbr/src/volumetric_fog/mod.rs @@ -30,12 +30,12 @@ //! [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}; +use bevy_asset::{embedded_asset, Assets, Handle}; use bevy_core_pipeline::core_3d::{ graph::{Core3d, Node3d}, prepare_core_3d_depth_textures, }; -use bevy_ecs::schedule::IntoScheduleConfigs as _; +use bevy_ecs::{resource::Resource, schedule::IntoScheduleConfigs as _}; use bevy_light::FogVolume; use bevy_math::{ primitives::{Cuboid, Plane3d}, @@ -48,9 +48,7 @@ use bevy_render::{ sync_component::SyncComponentPlugin, ExtractSchedule, Render, RenderApp, RenderSystems, }; -use render::{ - VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer, CUBE_MESH, PLANE_MESH, -}; +use render::{VolumetricFogNode, VolumetricFogPipeline, VolumetricFogUniformBuffer}; use crate::graph::NodePbr; @@ -59,13 +57,19 @@ pub mod render; /// A plugin that implements volumetric fog. pub struct VolumetricFogPlugin; +#[derive(Resource)] +pub struct FogAssets { + plane_mesh: Handle, + cube_mesh: Handle, +} + impl Plugin for VolumetricFogPlugin { fn build(&self, app: &mut App) { embedded_asset!(app, "volumetric_fog.wgsl"); let mut meshes = app.world_mut().resource_mut::>(); - 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()); + let plane_mesh = meshes.add(Plane3d::new(Vec3::Z, Vec2::ONE).mesh()); + let cube_mesh = meshes.add(Cuboid::new(1.0, 1.0, 1.0).mesh()); app.add_plugins(SyncComponentPlugin::::default()); @@ -74,6 +78,10 @@ impl Plugin for VolumetricFogPlugin { }; render_app + .insert_resource(FogAssets { + plane_mesh, + cube_mesh, + }) .init_resource::>() .init_resource::() .add_systems(ExtractSchedule, render::extract_volumetric_fog) diff --git a/crates/bevy_pbr/src/volumetric_fog/render.rs b/crates/bevy_pbr/src/volumetric_fog/render.rs index feb04a2983..f24550a456 100644 --- a/crates/bevy_pbr/src/volumetric_fog/render.rs +++ b/crates/bevy_pbr/src/volumetric_fog/render.rs @@ -2,7 +2,7 @@ use core::array; -use bevy_asset::{load_embedded_asset, uuid_handle, AssetId, Handle}; +use bevy_asset::{load_embedded_asset, AssetId, Handle}; use bevy_color::ColorToComponents as _; use bevy_core_pipeline::{ core_3d::Camera3d, @@ -54,6 +54,8 @@ use crate::{ VolumetricLight, }; +use super::FogAssets; + bitflags! { /// Flags that describe the bind group layout used to render volumetric fog. #[derive(Clone, Copy, PartialEq)] @@ -77,20 +79,6 @@ bitflags! { } } -/// The plane mesh, which is used to render a fog volume that the camera is -/// inside. -/// -/// This mesh is simply stretched to the size of the framebuffer, as when the -/// camera is inside a fog volume it's essentially a full-screen effect. -pub const PLANE_MESH: Handle = uuid_handle!("92523617-c708-4fd0-b42f-ceb4300c930b"); - -/// The cube mesh, which is used to render a fog volume that the camera is -/// outside. -/// -/// Note that only the front faces of this cuboid will be rasterized in -/// hardware. The back faces will be calculated in the shader via raytracing. -pub const CUBE_MESH: Handle = uuid_handle!("4a1dd661-2d91-4377-a17a-a914e21e277e"); - /// The total number of bind group layouts. /// /// This is the total number of combinations of all @@ -370,6 +358,7 @@ impl ViewNode for VolumetricFogNode { return Ok(()); }; + let fog_assets = world.resource::(); let render_meshes = world.resource::>(); for view_fog_volume in view_fog_volumes.iter() { @@ -377,9 +366,9 @@ impl ViewNode for VolumetricFogNode { // otherwise, pick the plane mesh. In the latter case we'll be // effectively rendering a full-screen quad. let mesh_handle = if view_fog_volume.exterior { - CUBE_MESH.clone() + fog_assets.cube_mesh.clone() } else { - PLANE_MESH.clone() + fog_assets.plane_mesh.clone() }; let Some(vertex_buffer_slice) = mesh_allocator.mesh_vertex_slice(&mesh_handle.id()) @@ -615,6 +604,7 @@ pub fn prepare_volumetric_fog_pipelines( pipeline_cache: Res, mut pipelines: ResMut>, volumetric_lighting_pipeline: Res, + fog_assets: Res, view_targets: Query< ( Entity, @@ -629,7 +619,7 @@ pub fn prepare_volumetric_fog_pipelines( >, meshes: Res>, ) { - let Some(plane_mesh) = meshes.get(&PLANE_MESH) else { + let Some(plane_mesh) = meshes.get(&fog_assets.plane_mesh) else { // There's an off chance that the mesh won't be prepared yet if `RenderAssetBytesPerFrame` limiting is in use. return; }; 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/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/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index 7012e61d92..58ec9b5ceb 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -6,7 +6,6 @@ //! [`Material2d`]: bevy::sprite::Material2d use bevy::{ - asset::uuid_handle, color::palettes::basic::YELLOW, core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, math::{ops, FloatOrd}, @@ -129,12 +128,16 @@ pub struct ColoredMesh2d; pub struct ColoredMesh2dPipeline { /// This pipeline wraps the standard [`Mesh2dPipeline`] mesh2d_pipeline: Mesh2dPipeline, + /// The shader asset handle. + shader: Handle, } impl FromWorld for ColoredMesh2dPipeline { fn from_world(world: &mut World) -> Self { Self { mesh2d_pipeline: Mesh2dPipeline::from_world(world), + // Get the shader from the shader resource we inserted in the plugin. + shader: world.resource::().0.clone(), } } } @@ -164,14 +167,14 @@ impl SpecializedRenderPipeline for ColoredMesh2dPipeline { RenderPipelineDescriptor { vertex: VertexState { // Use our custom shader - shader: COLORED_MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), // Use our custom vertex buffer buffers: vec![vertex_layout], ..default() }, fragment: Some(FragmentState { // Use our custom shader - shader: COLORED_MESH2D_SHADER_HANDLE, + shader: self.shader.clone(), targets: vec![Some(ColorTargetState { format, blend: Some(BlendState::ALPHA_BLENDING), @@ -278,9 +281,10 @@ fn fragment(in: FragmentInput) -> @location(0) vec4 { /// Plugin that renders [`ColoredMesh2d`]s pub struct ColoredMesh2dPlugin; -/// Handle to the custom shader with a unique random ID -pub const COLORED_MESH2D_SHADER_HANDLE: Handle = - uuid_handle!("f48b148f-7373-4638-9900-392b3b3ccc66"); +/// A resource holding the shader asset handle for the pipeline to take. There are many ways to get +/// the shader into the pipeline - this is just one option. +#[derive(Resource)] +struct ColoredMesh2dShader(Handle); /// Our custom pipeline needs its own instance storage #[derive(Resource, Deref, DerefMut, Default)] @@ -290,15 +294,16 @@ impl Plugin for ColoredMesh2dPlugin { fn build(&self, app: &mut App) { // Load our custom shader let mut shaders = app.world_mut().resource_mut::>(); - shaders.insert( - &COLORED_MESH2D_SHADER_HANDLE, - Shader::from_wgsl(COLORED_MESH2D_SHADER, file!()), - ); + // Here, we construct and add the shader asset manually. There are many ways to load this + // shader, including `embedded_asset`/`load_embedded_asset`. + let shader = shaders.add(Shader::from_wgsl(COLORED_MESH2D_SHADER, file!())); + app.add_plugins(SyncComponentPlugin::::default()); // Register our custom draw function, and add our render systems app.get_sub_app_mut(RenderApp) .unwrap() + .insert_resource(ColoredMesh2dShader(shader)) .add_render_command::() .init_resource::>() .init_resource::() 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 babaa9b8a8..c7cfb86f88 100644 --- a/examples/3d/light_textures.rs +++ b/examples/3d/light_textures.rs @@ -151,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