diff --git a/Cargo.toml b/Cargo.toml index 4a4a005e2fc15..6a20f08160841 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -481,7 +481,7 @@ futures-lite = "2.0.1" async-std = "1.13" crossbeam-channel = "0.5.0" argh = "0.1.12" -thiserror = "1.0" +thiserror = "2.0" event-listener = "5.3.0" hyper = { version = "1", features = ["server", "http1"] } http-body-util = "0.1" @@ -2750,6 +2750,17 @@ description = "Test rendering of many cameras and lights" category = "Stress Tests" wasm = true +[[example]] +name = "many_components" +path = "examples/stress_tests/many_components.rs" +doc-scrape-examples = true + +[package.metadata.example.many_components] +name = "Many Components (and Entities and Systems)" +description = "Test large ECS systems" +category = "Stress Tests" +wasm = false + [[example]] name = "many_cubes" path = "examples/stress_tests/many_cubes.rs" diff --git a/benches/benches/bevy_reflect/function.rs b/benches/benches/bevy_reflect/function.rs index 7489130418896..f40b9149eec64 100644 --- a/benches/benches/bevy_reflect/function.rs +++ b/benches/benches/bevy_reflect/function.rs @@ -1,7 +1,7 @@ use bevy_reflect::func::{ArgList, IntoFunction, IntoFunctionMut, TypedFunction}; use criterion::{criterion_group, BatchSize, Criterion}; -criterion_group!(benches, typed, into, call, clone); +criterion_group!(benches, typed, into, call, overload, clone); fn add(a: i32, b: i32) -> i32 { a + b @@ -78,6 +78,307 @@ fn call(c: &mut Criterion) { }); } +fn overload(c: &mut Criterion) { + fn add>(a: T, b: T) -> T { + a + b + } + + fn complex( + _: T0, + _: T1, + _: T2, + _: T3, + _: T4, + _: T5, + _: T6, + _: T7, + _: T8, + _: T9, + ) { + } + + c.benchmark_group("with_overload") + .bench_function("01_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| func.with_overload(add::), + BatchSize::SmallInput, + ); + }) + .bench_function("01_complex_overload", |b| { + b.iter_batched( + || complex::.into_function(), + |func| { + func.with_overload(complex::) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("03_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| { + func.with_overload(add::) + .with_overload(add::) + .with_overload(add::) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("03_complex_overload", |b| { + b.iter_batched( + || complex::.into_function(), + |func| { + func.with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("10_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| { + func.with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("10_complex_overload", |b| { + b.iter_batched( + || complex::.into_function(), + |func| { + func.with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + .with_overload(complex::) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("01_nested_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| func.with_overload(add::), + BatchSize::SmallInput, + ); + }) + .bench_function("03_nested_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| { + func.with_overload( + add:: + .into_function() + .with_overload(add::.into_function().with_overload(add::)), + ) + }, + BatchSize::SmallInput, + ); + }) + .bench_function("10_nested_simple_overload", |b| { + b.iter_batched( + || add::.into_function(), + |func| { + func.with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add::.into_function().with_overload( + add:: + .into_function() + .with_overload(add::), + ), + ), + ), + ), + ), + ), + ), + ) + }, + BatchSize::SmallInput, + ); + }); + + c.benchmark_group("call_overload") + .bench_function("01_simple_overload", |b| { + b.iter_batched( + || { + ( + add::.into_function().with_overload(add::), + ArgList::new().push_owned(75_i8).push_owned(25_i8), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }) + .bench_function("01_complex_overload", |b| { + b.iter_batched( + || { + ( + complex:: + .into_function() + .with_overload( + complex::, + ), + ArgList::new() + .push_owned(1_i8) + .push_owned(2_i16) + .push_owned(3_i32) + .push_owned(4_i64) + .push_owned(5_i128) + .push_owned(6_u8) + .push_owned(7_u16) + .push_owned(8_u32) + .push_owned(9_u64) + .push_owned(10_u128), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }) + .bench_function("03_simple_overload", |b| { + b.iter_batched( + || { + ( + add:: + .into_function() + .with_overload(add::) + .with_overload(add::) + .with_overload(add::), + ArgList::new().push_owned(75_i32).push_owned(25_i32), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }) + .bench_function("03_complex_overload", |b| { + b.iter_batched( + || { + ( + complex:: + .into_function() + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ), + ArgList::new() + .push_owned(1_i32) + .push_owned(2_i64) + .push_owned(3_i128) + .push_owned(4_u8) + .push_owned(5_u16) + .push_owned(6_u32) + .push_owned(7_u64) + .push_owned(8_u128) + .push_owned(9_i8) + .push_owned(10_i16), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }) + .bench_function("10_simple_overload", |b| { + b.iter_batched( + || { + ( + add:: + .into_function() + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::) + .with_overload(add::), + ArgList::new().push_owned(75_u8).push_owned(25_u8), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }) + .bench_function("10_complex_overload", |b| { + b.iter_batched( + || { + ( + complex:: + .into_function() + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ) + .with_overload( + complex::, + ), + ArgList::new() + .push_owned(1_u8) + .push_owned(2_u16) + .push_owned(3_u32) + .push_owned(4_u64) + .push_owned(5_u128) + .push_owned(6_i8) + .push_owned(7_i16) + .push_owned(8_i32) + .push_owned(9_i64) + .push_owned(10_i128), + ) + }, + |(func, args)| func.call(args), + BatchSize::SmallInput, + ); + }); +} + fn clone(c: &mut Criterion) { c.benchmark_group("clone").bench_function("function", |b| { let add = add.into_function(); diff --git a/crates/bevy_animation/src/graph.rs b/crates/bevy_animation/src/graph.rs index 2507895bf04d5..e570d25ab15e3 100644 --- a/crates/bevy_animation/src/graph.rs +++ b/crates/bevy_animation/src/graph.rs @@ -1,7 +1,9 @@ //! The animation graph, which allows animations to be blended together. -use core::iter; -use core::ops::{Index, IndexMut, Range}; +use core::{ + iter, + ops::{Index, IndexMut, Range}, +}; use std::io::{self, Write}; use bevy_asset::{ @@ -420,7 +422,7 @@ impl AnimationGraph { Self { graph, root, - mask_groups: HashMap::new(), + mask_groups: HashMap::default(), } } diff --git a/crates/bevy_animation/src/lib.rs b/crates/bevy_animation/src/lib.rs index b782c8d709d1a..e03ad9a449982 100644 --- a/crates/bevy_animation/src/lib.rs +++ b/crates/bevy_animation/src/lib.rs @@ -45,9 +45,8 @@ use bevy_reflect::{prelude::ReflectDefault, Reflect, TypePath}; use bevy_time::Time; use bevy_transform::TransformSystem; use bevy_utils::{ - hashbrown::HashMap, tracing::{trace, warn}, - NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap, + HashMap, NoOpHash, PreHashMap, PreHashMapExt, TypeIdMap, }; use petgraph::graph::NodeIndex; use serde::{Deserialize, Serialize}; diff --git a/crates/bevy_app/src/app.rs b/crates/bevy_app/src/app.rs index 4aad33c9d6cde..4ff12abd74a5b 100644 --- a/crates/bevy_app/src/app.rs +++ b/crates/bevy_app/src/app.rs @@ -124,7 +124,7 @@ impl App { Self { sub_apps: SubApps { main: SubApp::new(), - sub_apps: HashMap::new(), + sub_apps: HashMap::default(), }, runner: Box::new(run_once), } diff --git a/crates/bevy_asset/src/handle.rs b/crates/bevy_asset/src/handle.rs index 977a50b98be83..9c61ff0f88c7b 100644 --- a/crates/bevy_asset/src/handle.rs +++ b/crates/bevy_asset/src/handle.rs @@ -515,6 +515,8 @@ pub enum UntypedAssetConversionError { #[cfg(test)] mod tests { use bevy_reflect::PartialReflect; + use bevy_utils::FixedHasher; + use core::hash::BuildHasher; use super::*; @@ -525,9 +527,7 @@ mod tests { /// Simple utility to directly hash a value using a fixed hasher fn hash(data: &T) -> u64 { - let mut hasher = bevy_utils::AHasher::default(); - data.hash(&mut hasher); - hasher.finish() + FixedHasher.hash_one(data) } /// Typed and Untyped `Handles` should be equivalent to each other and themselves diff --git a/crates/bevy_asset/src/id.rs b/crates/bevy_asset/src/id.rs index 7e2abc50e330c..07a6d3db1209e 100644 --- a/crates/bevy_asset/src/id.rs +++ b/crates/bevy_asset/src/id.rs @@ -418,11 +418,9 @@ mod tests { /// Simple utility to directly hash a value using a fixed hasher fn hash(data: &T) -> u64 { - use core::hash::Hasher; + use core::hash::BuildHasher; - let mut hasher = bevy_utils::AHasher::default(); - data.hash(&mut hasher); - hasher.finish() + bevy_utils::FixedHasher.hash_one(data) } /// Typed and Untyped `AssetIds` should be equivalent to each other and themselves diff --git a/crates/bevy_asset/src/io/gated.rs b/crates/bevy_asset/src/io/gated.rs index cb205f12a81bd..388145a4686b0 100644 --- a/crates/bevy_asset/src/io/gated.rs +++ b/crates/bevy_asset/src/io/gated.rs @@ -44,7 +44,7 @@ impl GatedReader { /// Creates a new [`GatedReader`], which wraps the given `reader`. Also returns a [`GateOpener`] which /// can be used to open "path gates" for this [`GatedReader`]. pub fn new(reader: R) -> (Self, GateOpener) { - let gates = Arc::new(RwLock::new(HashMap::new())); + let gates = Arc::new(RwLock::new(HashMap::default())); ( Self { reader, diff --git a/crates/bevy_asset/src/io/source.rs b/crates/bevy_asset/src/io/source.rs index fe0d8df17f289..c0bab2037f8e3 100644 --- a/crates/bevy_asset/src/io/source.rs +++ b/crates/bevy_asset/src/io/source.rs @@ -343,7 +343,7 @@ impl AssetSourceBuilders { /// Builds a new [`AssetSources`] collection. If `watch` is true, the unprocessed sources will watch for changes. /// If `watch_processed` is true, the processed sources will watch for changes. pub fn build_sources(&mut self, watch: bool, watch_processed: bool) -> AssetSources { - let mut sources = HashMap::new(); + let mut sources = >::default(); for (id, source) in &mut self.sources { if let Some(data) = source.build( AssetSourceId::Name(id.clone_owned()), diff --git a/crates/bevy_asset/src/loader.rs b/crates/bevy_asset/src/loader.rs index 4cab51634f151..cbc5aac3c110c 100644 --- a/crates/bevy_asset/src/loader.rs +++ b/crates/bevy_asset/src/loader.rs @@ -153,7 +153,7 @@ pub struct LoadedAsset { impl LoadedAsset { /// Create a new loaded asset. This will use [`VisitAssetDependencies`](crate::VisitAssetDependencies) to populate `dependencies`. pub fn new_with_dependencies(value: A, meta: Option>) -> Self { - let mut dependencies = HashSet::new(); + let mut dependencies = >::default(); value.visit_dependencies(&mut |id| { dependencies.insert(id); }); diff --git a/crates/bevy_asset/src/server/info.rs b/crates/bevy_asset/src/server/info.rs index 3a38797fc83ce..898b3a76ec46f 100644 --- a/crates/bevy_asset/src/server/info.rs +++ b/crates/bevy_asset/src/server/info.rs @@ -395,10 +395,10 @@ impl AssetInfos { loaded_asset.value.insert(loaded_asset_id, world); let mut loading_deps = loaded_asset.dependencies; - let mut failed_deps = HashSet::new(); + let mut failed_deps = >::default(); let mut dep_error = None; let mut loading_rec_deps = loading_deps.clone(); - let mut failed_rec_deps = HashSet::new(); + let mut failed_rec_deps = >::default(); let mut rec_dep_error = None; loading_deps.retain(|dep_id| { if let Some(dep_info) = self.get_mut(*dep_id) { diff --git a/crates/bevy_asset/src/server/mod.rs b/crates/bevy_asset/src/server/mod.rs index 4365eeb1b30aa..6a2e475219041 100644 --- a/crates/bevy_asset/src/server/mod.rs +++ b/crates/bevy_asset/src/server/mod.rs @@ -1544,7 +1544,7 @@ pub fn handle_internal_asset_events(world: &mut World) { } }; - let mut paths_to_reload = HashSet::new(); + let mut paths_to_reload = >::default(); let mut handle_event = |source: AssetSourceId<'static>, event: AssetSourceEvent| { match event { // TODO: if the asset was processed and the processed file was changed, the first modified event diff --git a/crates/bevy_core/src/name.rs b/crates/bevy_core/src/name.rs index 8c00762199dfa..70e7a81cef4f0 100644 --- a/crates/bevy_core/src/name.rs +++ b/crates/bevy_core/src/name.rs @@ -7,9 +7,9 @@ use alloc::borrow::Cow; use bevy_reflect::std_traits::ReflectDefault; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; -use bevy_utils::AHasher; +use bevy_utils::FixedHasher; use core::{ - hash::{Hash, Hasher}, + hash::{BuildHasher, Hash, Hasher}, ops::Deref, }; @@ -80,9 +80,7 @@ impl Name { } fn update_hash(&mut self) { - let mut hasher = AHasher::default(); - self.name.hash(&mut hasher); - self.hash = hasher.finish(); + self.hash = FixedHasher.hash_one(&self.name); } } diff --git a/crates/bevy_core_pipeline/src/core_2d/mod.rs b/crates/bevy_core_pipeline/src/core_2d/mod.rs index 0e9c064af68a9..d57134aa3ec07 100644 --- a/crates/bevy_core_pipeline/src/core_2d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_2d/mod.rs @@ -33,8 +33,9 @@ pub mod graph { use core::ops::Range; use bevy_asset::UntypedAssetId; -use bevy_render::batching::gpu_preprocessing::GpuPreprocessingMode; -use bevy_render::render_phase::PhaseItemBinKey; +use bevy_render::{ + batching::gpu_preprocessing::GpuPreprocessingMode, render_phase::PhaseItemBinKey, +}; use bevy_utils::HashMap; pub use camera_2d::*; pub use main_opaque_pass_2d_node::*; @@ -44,7 +45,6 @@ use crate::{tonemapping::TonemappingNode, upscaling::UpscalingNode}; use bevy_app::{App, Plugin}; use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_math::FloatOrd; -use bevy_render::sync_world::MainEntity; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::ExtractComponentPlugin, @@ -59,7 +59,7 @@ use bevy_render::{ TextureFormat, TextureUsages, }, renderer::RenderDevice, - sync_world::RenderEntity, + sync_world::{MainEntity, RenderEntity}, texture::TextureCache, view::{Msaa, ViewDepthTexture}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, @@ -423,7 +423,7 @@ pub fn prepare_core_2d_depth_textures( opaque_2d_phases: Res>, views_2d: Query<(Entity, &ExtractedCamera, &Msaa), (With,)>, ) { - let mut textures = HashMap::default(); + let mut textures = >::default(); for (view, camera, msaa) in &views_2d { if !opaque_2d_phases.contains_key(&view) || !transparent_2d_phases.contains_key(&view) { continue; diff --git a/crates/bevy_core_pipeline/src/core_3d/mod.rs b/crates/bevy_core_pipeline/src/core_3d/mod.rs index 909306dd4259c..d7e9b446e5fb3 100644 --- a/crates/bevy_core_pipeline/src/core_3d/mod.rs +++ b/crates/bevy_core_pipeline/src/core_3d/mod.rs @@ -65,10 +65,12 @@ pub const DEPTH_TEXTURE_SAMPLING_SUPPORTED: bool = true; use core::ops::Range; -use bevy_render::batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}; -use bevy_render::mesh::allocator::SlabId; -use bevy_render::render_phase::PhaseItemBinKey; -use bevy_render::view::GpuCulling; +use bevy_render::{ + batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, + mesh::allocator::SlabId, + render_phase::PhaseItemBinKey, + view::GpuCulling, +}; pub use camera_3d::*; pub use main_opaque_pass_3d_node::*; pub use main_transparent_pass_3d_node::*; @@ -79,7 +81,6 @@ use bevy_color::LinearRgba; use bevy_ecs::{entity::EntityHashSet, prelude::*}; use bevy_image::{BevyDefault, Image}; use bevy_math::FloatOrd; -use bevy_render::sync_world::MainEntity; use bevy_render::{ camera::{Camera, ExtractedCamera}, extract_component::ExtractComponentPlugin, @@ -95,7 +96,7 @@ use bevy_render::{ TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView, }, renderer::RenderDevice, - sync_world::RenderEntity, + sync_world::{MainEntity, RenderEntity}, texture::{ColorAttachment, TextureCache}, view::{ExtractedView, ViewDepthTexture, ViewTarget}, Extract, ExtractSchedule, Render, RenderApp, RenderSet, @@ -700,7 +701,7 @@ pub fn prepare_core_3d_depth_textures( &Msaa, )>, ) { - let mut render_target_usage = HashMap::default(); + let mut render_target_usage = >::default(); for (view, camera, depth_prepass, camera_3d, _msaa) in &views_3d { if !opaque_3d_phases.contains_key(&view) || !alpha_mask_3d_phases.contains_key(&view) @@ -722,7 +723,7 @@ pub fn prepare_core_3d_depth_textures( .or_insert_with(|| usage); } - let mut textures = HashMap::default(); + let mut textures = >::default(); for (entity, camera, _, camera_3d, msaa) in &views_3d { let Some(physical_target_size) = camera.physical_target_size else { continue; @@ -785,7 +786,7 @@ pub fn prepare_core_3d_transmission_textures( transparent_3d_phases: Res>, views_3d: Query<(Entity, &ExtractedCamera, &Camera3d, &ExtractedView)>, ) { - let mut textures = HashMap::default(); + let mut textures = >::default(); for (entity, camera, camera_3d, view) in &views_3d { if !opaque_3d_phases.contains_key(&entity) || !alpha_mask_3d_phases.contains_key(&entity) @@ -893,11 +894,11 @@ pub fn prepare_prepass_textures( Has, )>, ) { - let mut depth_textures = HashMap::default(); - let mut normal_textures = HashMap::default(); - let mut deferred_textures = HashMap::default(); - let mut deferred_lighting_id_textures = HashMap::default(); - let mut motion_vectors_textures = HashMap::default(); + let mut depth_textures = >::default(); + let mut normal_textures = >::default(); + let mut deferred_textures = >::default(); + let mut deferred_lighting_id_textures = >::default(); + let mut motion_vectors_textures = >::default(); for ( entity, camera, diff --git a/crates/bevy_core_pipeline/src/oit/mod.rs b/crates/bevy_core_pipeline/src/oit/mod.rs index c01b2a286e547..14e8b8d4e36e3 100644 --- a/crates/bevy_core_pipeline/src/oit/mod.rs +++ b/crates/bevy_core_pipeline/src/oit/mod.rs @@ -160,7 +160,7 @@ fn configure_depth_texture_usages( // Find all the render target that potentially uses OIT let primary_window = p.get_single().ok(); - let mut render_target_has_oit = HashSet::new(); + let mut render_target_has_oit = >::default(); for (camera, has_oit) in &cameras { if has_oit { render_target_has_oit.insert(camera.target.normalize(primary_window)); diff --git a/crates/bevy_core_pipeline/src/upscaling/mod.rs b/crates/bevy_core_pipeline/src/upscaling/mod.rs index b686d4484931c..52369fca59abc 100644 --- a/crates/bevy_core_pipeline/src/upscaling/mod.rs +++ b/crates/bevy_core_pipeline/src/upscaling/mod.rs @@ -43,7 +43,7 @@ fn prepare_view_upscaling_pipelines( blit_pipeline: Res, view_targets: Query<(Entity, &ViewTarget, Option<&ExtractedCamera>)>, ) { - let mut output_textures = HashSet::new(); + let mut output_textures = >::default(); for (entity, view_target, camera) in view_targets.iter() { let out_texture_id = view_target.out_texture().id(); let blend_state = if let Some(extracted_camera) = camera { diff --git a/crates/bevy_dev_tools/src/ui_debug_overlay/inset.rs b/crates/bevy_dev_tools/src/ui_debug_overlay/inset.rs index 1e52723d20d22..7c076a2c8aee7 100644 --- a/crates/bevy_dev_tools/src/ui_debug_overlay/inset.rs +++ b/crates/bevy_dev_tools/src/ui_debug_overlay/inset.rs @@ -52,7 +52,7 @@ struct DrawnLines { impl DrawnLines { fn new(width: f32) -> Self { DrawnLines { - lines: HashMap::new(), + lines: HashMap::default(), width, } } diff --git a/crates/bevy_diagnostic/src/diagnostic.rs b/crates/bevy_diagnostic/src/diagnostic.rs index ccd6e17df175d..c9fbe5d600eea 100644 --- a/crates/bevy_diagnostic/src/diagnostic.rs +++ b/crates/bevy_diagnostic/src/diagnostic.rs @@ -3,7 +3,7 @@ use core::hash::{Hash, Hasher}; use bevy_app::{App, SubApp}; use bevy_ecs::system::{Deferred, Res, Resource, SystemBuffer, SystemParam}; -use bevy_utils::{hashbrown::HashMap, Duration, Instant, PassHash}; +use bevy_utils::{Duration, HashMap, Instant, PassHash}; use const_fnv1a_hash::fnv1a_hash_str_64; use crate::DEFAULT_MAX_HISTORY_LENGTH; diff --git a/crates/bevy_ecs/macros/src/component.rs b/crates/bevy_ecs/macros/src/component.rs index 62a84b68960c3..82949e8a92ca2 100644 --- a/crates/bevy_ecs/macros/src/component.rs +++ b/crates/bevy_ecs/macros/src/component.rs @@ -174,13 +174,19 @@ pub fn document_required_components(attr: TokenStream, item: TokenStream) -> Tok .collect::>() .join(", "); + let bevy_ecs_path = crate::bevy_ecs_path() + .to_token_stream() + .to_string() + .replace(' ', ""); + let required_components_path = bevy_ecs_path + "::component::Component#required-components"; + // Insert information about required components after any existing doc comments let mut out = TokenStream::new(); let mut end_of_attributes_reached = false; for tt in item { if !end_of_attributes_reached & matches!(tt, TokenTree::Ident(_)) { end_of_attributes_reached = true; - let doc: TokenStream = format!("#[doc = \"\n\n# Required Components\n{paths} \n\n A component's required components are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.\"]").parse().unwrap(); + let doc: TokenStream = format!("#[doc = \"\n\n# Required Components\n{paths} \n\n A component's [required components]({required_components_path}) are inserted whenever it is inserted. Note that this will also insert the required components _of_ the required components, recursively, in depth-first order.\"]").parse().unwrap(); out.extend(doc); } out.extend(Some(tt)); diff --git a/crates/bevy_ecs/src/archetype.rs b/crates/bevy_ecs/src/archetype.rs index 2c2241e29ec6d..022e550948d66 100644 --- a/crates/bevy_ecs/src/archetype.rs +++ b/crates/bevy_ecs/src/archetype.rs @@ -402,7 +402,7 @@ impl Archetype { // component in the `table_components` vector component_index .entry(component_id) - .or_insert_with(HashMap::new) + .or_default() .insert(id, ArchetypeRecord { column: Some(idx) }); } @@ -420,7 +420,7 @@ impl Archetype { ); component_index .entry(component_id) - .or_insert_with(HashMap::new) + .or_default() .insert(id, ArchetypeRecord { column: None }); } Self { diff --git a/crates/bevy_ecs/src/bundle.rs b/crates/bevy_ecs/src/bundle.rs index 333358240c23b..97833a1d7de59 100644 --- a/crates/bevy_ecs/src/bundle.rs +++ b/crates/bevy_ecs/src/bundle.rs @@ -381,7 +381,7 @@ impl BundleInfo { if deduped.len() != component_ids.len() { // TODO: Replace with `Vec::partition_dedup` once https://github.com/rust-lang/rust/issues/54279 is stabilized - let mut seen = HashSet::new(); + let mut seen = >::default(); let mut dups = Vec::new(); for id in component_ids { if !seen.insert(id) { @@ -1422,8 +1422,11 @@ impl Bundles { .or_insert_with(|| { let (id, storages) = initialize_dynamic_bundle(bundle_infos, components, Vec::from(component_ids)); - self.dynamic_bundle_storages - .insert_unique_unchecked(id, storages); + // SAFETY: The ID always increases when new bundles are added, and so, the ID is unique. + unsafe { + self.dynamic_bundle_storages + .insert_unique_unchecked(id, storages); + } (component_ids.into(), id) }); *bundle_id diff --git a/crates/bevy_ecs/src/entity/hash.rs b/crates/bevy_ecs/src/entity/hash.rs index 1b1ff531ffeb4..2e7c8ff2a3fc6 100644 --- a/crates/bevy_ecs/src/entity/hash.rs +++ b/crates/bevy_ecs/src/entity/hash.rs @@ -28,7 +28,8 @@ impl BuildHasher for EntityHash { /// /// If you have an unusual case -- say all your indices are multiples of 256 /// or most of the entities are dead generations -- then you might want also to -/// try [`AHasher`](bevy_utils::AHasher) for a slower hash computation but fewer lookup conflicts. +/// try [`DefaultHasher`](bevy_utils::DefaultHasher) for a slower hash +/// computation but fewer lookup conflicts. #[derive(Debug, Default)] pub struct EntityHasher { hash: u64, diff --git a/crates/bevy_ecs/src/entity/visit_entities.rs b/crates/bevy_ecs/src/entity/visit_entities.rs index 79b5197d2ea79..abce76853d403 100644 --- a/crates/bevy_ecs/src/entity/visit_entities.rs +++ b/crates/bevy_ecs/src/entity/visit_entities.rs @@ -113,7 +113,7 @@ mod tests { let mut entity_map = EntityHashMap::::default(); let mut remapped = Foo { ordered: vec![], - unordered: HashSet::new(), + unordered: HashSet::default(), single: Entity::PLACEHOLDER, not_an_entity: foo.not_an_entity.clone(), }; diff --git a/crates/bevy_ecs/src/intern.rs b/crates/bevy_ecs/src/intern.rs index 179fdc8b82ad9..6668a2a9a6aed 100644 --- a/crates/bevy_ecs/src/intern.rs +++ b/crates/bevy_ecs/src/intern.rs @@ -164,8 +164,8 @@ impl Default for Interner { #[cfg(test)] mod tests { - use core::hash::{Hash, Hasher}; - use std::collections::hash_map::DefaultHasher; + use bevy_utils::FixedHasher; + use core::hash::{BuildHasher, Hash, Hasher}; use crate::intern::{Internable, Interned, Interner}; @@ -250,13 +250,8 @@ mod tests { assert_eq!(a, b); - let mut hasher = DefaultHasher::default(); - a.hash(&mut hasher); - let hash_a = hasher.finish(); - - let mut hasher = DefaultHasher::default(); - b.hash(&mut hasher); - let hash_b = hasher.finish(); + let hash_a = FixedHasher.hash_one(a); + let hash_b = FixedHasher.hash_one(b); assert_eq!(hash_a, hash_b); } diff --git a/crates/bevy_ecs/src/lib.rs b/crates/bevy_ecs/src/lib.rs index 85ff83c0aade0..c7def12de35d4 100644 --- a/crates/bevy_ecs/src/lib.rs +++ b/crates/bevy_ecs/src/lib.rs @@ -421,7 +421,7 @@ mod tests { let mut world = World::new(); let e = world.spawn((TableStored("abc"), A(123))).id(); let f = world.spawn((TableStored("def"), A(456), B(1))).id(); - let mut results = HashSet::new(); + let mut results = >::default(); world .query::<(Entity, &A)>() .iter(&world) @@ -598,7 +598,9 @@ mod tests { .collect::>(); assert_eq!( ents, - HashSet::from([(e, None, A(123)), (f, Some(SparseStored(1)), A(456))]) + [(e, None, A(123)), (f, Some(SparseStored(1)), A(456))] + .into_iter() + .collect::>() ); } @@ -630,7 +632,9 @@ mod tests { .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - HashSet::from([(e1, A(1), B(3)), (e2, A(2), B(4))]) + [(e1, A(1), B(3)), (e2, A(2), B(4))] + .into_iter() + .collect::>() ); assert_eq!(world.entity_mut(e1).take::(), Some(A(1))); assert_eq!( @@ -647,7 +651,9 @@ mod tests { .iter(&world) .map(|(e, &B(b), &TableStored(s))| (e, b, s)) .collect::>(), - HashSet::from([(e2, 4, "xyz"), (e1, 3, "abc")]) + [(e2, 4, "xyz"), (e1, 3, "abc")] + .into_iter() + .collect::>() ); world.entity_mut(e1).insert(A(43)); assert_eq!( @@ -656,7 +662,9 @@ mod tests { .iter(&world) .map(|(e, &i, &b)| (e, i, b)) .collect::>(), - HashSet::from([(e2, A(2), B(4)), (e1, A(43), B(3))]) + [(e2, A(2), B(4)), (e1, A(43), B(3))] + .into_iter() + .collect::>() ); world.entity_mut(e1).insert(C); assert_eq!( @@ -954,7 +962,7 @@ mod tests { assert_eq!( get_filtered::>(&mut world), - HashSet::from([e1, e3]) + [e1, e3].into_iter().collect::>() ); // ensure changing an entity's archetypes also moves its changed state @@ -962,7 +970,7 @@ mod tests { assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3, e1]), + [e3, e1].into_iter().collect::>(), "changed entities list should not change" ); @@ -971,7 +979,7 @@ mod tests { assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3, e1]), + [e3, e1].into_iter().collect::>(), "changed entities list should not change" ); @@ -979,7 +987,7 @@ mod tests { assert!(world.despawn(e2)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3, e1]), + [e3, e1].into_iter().collect::>(), "changed entities list should not change" ); @@ -987,7 +995,7 @@ mod tests { assert!(world.despawn(e1)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3]), + [e3].into_iter().collect::>(), "e1 should no longer be returned" ); @@ -998,11 +1006,20 @@ mod tests { let e4 = world.spawn_empty().id(); world.entity_mut(e4).insert(A(0)); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); world.entity_mut(e4).insert(A(1)); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); world.clear_trackers(); @@ -1011,9 +1028,18 @@ mod tests { world.entity_mut(e4).insert((A(0), B(0))); assert!(get_filtered::>(&mut world).is_empty()); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e4])); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); + assert_eq!( + get_filtered::>(&mut world), + [e4].into_iter().collect::>() + ); } #[test] @@ -1045,19 +1071,19 @@ mod tests { assert_eq!( get_filtered::>(&mut world), - HashSet::from([e1, e3]) + [e1, e3].into_iter().collect::>() ); // ensure changing an entity's archetypes also moves its changed state world.entity_mut(e1).insert(C); - assert_eq!(get_filtered::>(&mut world), HashSet::from([e3, e1]), "changed entities list should not change (although the order will due to archetype moves)"); + assert_eq!(get_filtered::>(&mut world), [e3, e1].into_iter().collect::>(), "changed entities list should not change (although the order will due to archetype moves)"); // spawning a new SparseStored entity should not change existing changed state world.entity_mut(e1).insert(SparseStored(0)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3, e1]), + [e3, e1].into_iter().collect::>(), "changed entities list should not change" ); @@ -1065,7 +1091,7 @@ mod tests { assert!(world.despawn(e2)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3, e1]), + [e3, e1].into_iter().collect::>(), "changed entities list should not change" ); @@ -1073,7 +1099,7 @@ mod tests { assert!(world.despawn(e1)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e3]), + [e3].into_iter().collect::>(), "e1 should no longer be returned" ); @@ -1086,17 +1112,17 @@ mod tests { world.entity_mut(e4).insert(SparseStored(0)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e4]) + [e4].into_iter().collect::>() ); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e4]) + [e4].into_iter().collect::>() ); world.entity_mut(e4).insert(A(1)); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e4]) + [e4].into_iter().collect::>() ); world.clear_trackers(); @@ -1108,7 +1134,7 @@ mod tests { assert!(get_filtered::>(&mut world).is_empty()); assert_eq!( get_filtered::>(&mut world), - HashSet::from([e4]) + [e4].into_iter().collect::>() ); } @@ -1292,7 +1318,12 @@ mod tests { .iter(&world) .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, HashSet::from([(1, "1"), (2, "2"), (3, "3"),])); + assert_eq!( + results, + [(1, "1"), (2, "2"), (3, "3"),] + .into_iter() + .collect::>() + ); let removed_bundle = world.entity_mut(e2).take::<(B, TableStored)>().unwrap(); assert_eq!(removed_bundle, (B(2), TableStored("2"))); @@ -1301,11 +1332,14 @@ mod tests { .iter(&world) .map(|(a, b)| (a.0, b.0)) .collect::>(); - assert_eq!(results, HashSet::from([(1, "1"), (3, "3"),])); + assert_eq!( + results, + [(1, "1"), (3, "3"),].into_iter().collect::>() + ); let mut a_query = world.query::<&A>(); let results = a_query.iter(&world).map(|a| a.0).collect::>(); - assert_eq!(results, HashSet::from([1, 3, 2])); + assert_eq!(results, [1, 3, 2].into_iter().collect::>()); let entity_ref = world.entity(e2); assert_eq!( diff --git a/crates/bevy_ecs/src/schedule/graph/graph_map.rs b/crates/bevy_ecs/src/schedule/graph/graph_map.rs index 680774bce5f4d..1b00698fc192a 100644 --- a/crates/bevy_ecs/src/schedule/graph/graph_map.rs +++ b/crates/bevy_ecs/src/schedule/graph/graph_map.rs @@ -4,10 +4,10 @@ //! //! [`petgraph`]: https://docs.rs/petgraph/0.6.5/petgraph/ -use bevy_utils::{hashbrown::HashSet, AHasher}; +use bevy_utils::{hashbrown::HashSet, FixedHasher}; use core::{ fmt, - hash::{BuildHasher, BuildHasherDefault, Hash}, + hash::{BuildHasher, Hash}, }; use indexmap::IndexMap; use smallvec::SmallVec; @@ -20,13 +20,13 @@ use Direction::{Incoming, Outgoing}; /// /// For example, an edge between *1* and *2* is equivalent to an edge between /// *2* and *1*. -pub type UnGraph> = Graph; +pub type UnGraph = Graph; /// A `Graph` with directed edges. /// /// For example, an edge from *1* to *2* is distinct from an edge from *2* to /// *1*. -pub type DiGraph> = Graph; +pub type DiGraph = Graph; /// `Graph` is a graph datastructure using an associative array /// of its node weights `NodeId`. @@ -45,7 +45,7 @@ pub type DiGraph> = Graph; /// /// `Graph` does not allow parallel edges, but self loops are allowed. #[derive(Clone)] -pub struct Graph> +pub struct Graph where S: BuildHasher, { @@ -63,14 +63,6 @@ impl Graph where S: BuildHasher, { - /// Create a new `Graph` - pub(crate) fn new() -> Self - where - S: Default, - { - Self::default() - } - /// Create a new `Graph` with estimated capacity. pub(crate) fn with_capacity(nodes: usize, edges: usize) -> Self where @@ -274,7 +266,7 @@ where } } -impl Graph { +impl DiGraph { /// Iterate over all *Strongly Connected Components* in this graph. pub(crate) fn iter_sccs(&self) -> impl Iterator> + '_ { super::tarjan_scc::new_tarjan_scc(self) @@ -408,7 +400,7 @@ mod tests { fn node_order_preservation() { use NodeId::System; - let mut graph = Graph::::new(); + let mut graph = ::default(); graph.add_node(System(1)); graph.add_node(System(2)); @@ -450,7 +442,7 @@ mod tests { fn strongly_connected_components() { use NodeId::System; - let mut graph = Graph::::new(); + let mut graph = ::default(); graph.add_edge(System(1), System(2)); graph.add_edge(System(2), System(1)); diff --git a/crates/bevy_ecs/src/schedule/graph/mod.rs b/crates/bevy_ecs/src/schedule/graph/mod.rs index 553be0bc195a3..1532184f1761e 100644 --- a/crates/bevy_ecs/src/schedule/graph/mod.rs +++ b/crates/bevy_ecs/src/schedule/graph/mod.rs @@ -1,10 +1,8 @@ -use alloc::vec; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use core::fmt::Debug; -use core::hash::BuildHasherDefault; use smallvec::SmallVec; -use bevy_utils::{AHasher, HashMap, HashSet}; +use bevy_utils::{HashMap, HashSet}; use fixedbitset::FixedBitSet; use crate::schedule::set::*; @@ -96,11 +94,11 @@ impl Default for CheckGraphResults { fn default() -> Self { Self { reachable: FixedBitSet::new(), - connected: HashSet::new(), + connected: HashSet::default(), disconnected: Vec::new(), transitive_edges: Vec::new(), - transitive_reduction: DiGraph::new(), - transitive_closure: DiGraph::new(), + transitive_reduction: DiGraph::default(), + transitive_closure: DiGraph::default(), } } } @@ -124,8 +122,8 @@ pub(crate) fn check_graph(graph: &DiGraph, topological_order: &[NodeId]) -> Chec let n = graph.node_count(); // build a copy of the graph where the nodes and edges appear in topsorted order - let mut map = HashMap::with_capacity(n); - let mut topsorted = DiGraph::>::new(); + let mut map = >::with_capacity_and_hasher(n, Default::default()); + let mut topsorted = ::default(); // iterate nodes in topological order for (i, &node) in topological_order.iter().enumerate() { map.insert(node, i); @@ -137,12 +135,12 @@ pub(crate) fn check_graph(graph: &DiGraph, topological_order: &[NodeId]) -> Chec } let mut reachable = FixedBitSet::with_capacity(n * n); - let mut connected = HashSet::new(); + let mut connected = >::default(); let mut disconnected = Vec::new(); let mut transitive_edges = Vec::new(); - let mut transitive_reduction = DiGraph::new(); - let mut transitive_closure = DiGraph::new(); + let mut transitive_reduction = DiGraph::default(); + let mut transitive_closure = DiGraph::default(); let mut visited = FixedBitSet::with_capacity(n); @@ -227,7 +225,7 @@ pub fn simple_cycles_in_component(graph: &DiGraph, scc: &[NodeId]) -> Vec>::new(); + let mut subgraph = ::default(); for &node in &scc { subgraph.add_node(node); } @@ -243,16 +241,17 @@ pub fn simple_cycles_in_component(graph: &DiGraph, scc: &[NodeId]) -> Vec = + HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default()); // connects nodes along path segments that can't be part of a cycle (given current root) // those nodes can be unblocked at the same time let mut unblock_together: HashMap> = - HashMap::with_capacity(subgraph.node_count()); + HashMap::with_capacity_and_hasher(subgraph.node_count(), Default::default()); // stack for unblocking nodes let mut unblock_stack = Vec::with_capacity(subgraph.node_count()); // nodes can be involved in multiple cycles let mut maybe_in_more_cycles: HashSet = - HashSet::with_capacity(subgraph.node_count()); + HashSet::with_capacity_and_hasher(subgraph.node_count(), Default::default()); // stack for DFS let mut stack = Vec::with_capacity(subgraph.node_count()); diff --git a/crates/bevy_ecs/src/schedule/schedule.rs b/crates/bevy_ecs/src/schedule/schedule.rs index 449b609a28ff0..e9781cec568da 100644 --- a/crates/bevy_ecs/src/schedule/schedule.rs +++ b/crates/bevy_ecs/src/schedule/schedule.rs @@ -1,13 +1,12 @@ use alloc::collections::BTreeSet; use core::fmt::{Debug, Write}; -use core::hash::BuildHasherDefault; #[cfg(feature = "trace")] use bevy_utils::tracing::info_span; use bevy_utils::{ default, tracing::{error, info, warn}, - AHasher, HashMap, HashSet, + HashMap, HashSet, }; use disqualified::ShortName; use fixedbitset::FixedBitSet; @@ -39,7 +38,7 @@ impl Schedules { /// Constructs an empty `Schedules` with zero initial capacity. pub fn new() -> Self { Self { - inner: HashMap::new(), + inner: HashMap::default(), ignored_scheduling_ambiguities: BTreeSet::new(), } } @@ -516,7 +515,7 @@ impl Schedule { #[derive(Default)] pub struct Dag { /// A directed graph. - graph: DiGraph>, + graph: DiGraph, /// A cached topological ordering of the graph. topsort: Vec, } @@ -524,7 +523,7 @@ pub struct Dag { impl Dag { fn new() -> Self { Self { - graph: DiGraph::new(), + graph: DiGraph::default(), topsort: Vec::new(), } } @@ -609,7 +608,7 @@ pub struct ScheduleGraph { hierarchy: Dag, /// Directed acyclic graph of the dependency (which systems/sets have to run before which other systems/sets) dependency: Dag, - ambiguous_with: UnGraph>, + ambiguous_with: UnGraph, ambiguous_with_all: HashSet, conflicting_systems: Vec<(NodeId, NodeId, Vec)>, anonymous_sets: usize, @@ -628,18 +627,18 @@ impl ScheduleGraph { system_conditions: Vec::new(), system_sets: Vec::new(), system_set_conditions: Vec::new(), - system_set_ids: HashMap::new(), + system_set_ids: HashMap::default(), uninit: Vec::new(), hierarchy: Dag::new(), dependency: Dag::new(), - ambiguous_with: UnGraph::new(), - ambiguous_with_all: HashSet::new(), + ambiguous_with: UnGraph::default(), + ambiguous_with_all: HashSet::default(), conflicting_systems: Vec::new(), anonymous_sets: 0, changed: false, settings: default(), no_sync_edges: BTreeSet::new(), - auto_sync_node_ids: HashMap::new(), + auto_sync_node_ids: HashMap::default(), } } @@ -1154,7 +1153,8 @@ impl ScheduleGraph { // calculate the number of sync points each sync point is from the beginning of the graph // use the same sync point if the distance is the same - let mut distances: HashMap> = HashMap::with_capacity(topo.len()); + let mut distances: HashMap> = + HashMap::with_capacity_and_hasher(topo.len(), Default::default()); for node in &topo { let add_sync_after = self.systems[node.index()].get().unwrap().has_deferred(); @@ -1231,8 +1231,9 @@ impl ScheduleGraph { hierarchy_graph: &DiGraph, ) -> (HashMap>, HashMap) { let mut set_systems: HashMap> = - HashMap::with_capacity(self.system_sets.len()); - let mut set_system_bitsets = HashMap::with_capacity(self.system_sets.len()); + HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); + let mut set_system_bitsets = + HashMap::with_capacity_and_hasher(self.system_sets.len(), Default::default()); for &id in hierarchy_topsort.iter().rev() { if id.is_system() { continue; @@ -1311,7 +1312,7 @@ impl ScheduleGraph { } fn get_ambiguous_with_flattened(&self, set_systems: &HashMap>) -> UnGraph { - let mut ambiguous_with_flattened = UnGraph::new(); + let mut ambiguous_with_flattened = UnGraph::default(); for (lhs, rhs) in self.ambiguous_with.all_edges() { match (lhs, rhs) { (NodeId::System(_), NodeId::System(_)) => { @@ -1919,7 +1920,7 @@ impl ScheduleGraph { } fn names_of_sets_containing_node(&self, id: &NodeId) -> Vec { - let mut sets = HashSet::new(); + let mut sets = >::default(); self.traverse_sets_containing_node(*id, &mut |set_id| { !self.system_sets[set_id.index()].is_system_type() && sets.insert(set_id) }); diff --git a/crates/bevy_ecs/src/system/commands/mod.rs b/crates/bevy_ecs/src/system/commands/mod.rs index 0879b8520f94f..53731b6cfae86 100644 --- a/crates/bevy_ecs/src/system/commands/mod.rs +++ b/crates/bevy_ecs/src/system/commands/mod.rs @@ -4,7 +4,7 @@ use core::{marker::PhantomData, panic::Location}; use super::{ Deferred, IntoObserverSystem, IntoSystem, RegisterSystem, Resource, RunSystemCachedWith, - UnregisterSystem, + UnregisterSystem, UnregisterSystemCached, }; use crate::{ self as bevy_ecs, @@ -15,7 +15,7 @@ use crate::{ event::{Event, SendEvent}, observer::{Observer, TriggerEvent, TriggerTargets}, schedule::ScheduleLabel, - system::{input::SystemInput, RunSystemWithInput, SystemId}, + system::{input::SystemInput, RunSystemWith, SystemId}, world::{ command_queue::RawCommandQueue, unsafe_world_cell::UnsafeWorldCell, Command, CommandQueue, EntityWorldMut, FromWorld, SpawnBatchIter, World, @@ -810,25 +810,25 @@ impl<'w, 's> Commands<'w, 's> { /// /// There is no way to get the output of a system when run as a command, because the /// execution of the system happens later. To get the output of a system, use - /// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. + /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. pub fn run_system(&mut self, id: SystemId) { - self.run_system_with_input(id, ()); + self.run_system_with(id, ()); } /// Runs the system corresponding to the given [`SystemId`]. /// Systems are ran in an exclusive and single threaded way. /// Running slow systems can become a bottleneck. /// - /// Calls [`World::run_system_with_input`](World::run_system_with_input). + /// Calls [`World::run_system_with`](World::run_system_with). /// /// There is no way to get the output of a system when run as a command, because the /// execution of the system happens later. To get the output of a system, use - /// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. - pub fn run_system_with_input(&mut self, id: SystemId, input: I::Inner<'static>) + /// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. + pub fn run_system_with(&mut self, id: SystemId, input: I::Inner<'static>) where I: SystemInput: Send> + 'static, { - self.queue(RunSystemWithInput::new_with_input(id, input)); + self.queue(RunSystemWith::new_with_input(id, input)); } /// Registers a system and returns a [`SystemId`] so it can later be called by [`World::run_system`]. @@ -904,6 +904,21 @@ impl<'w, 's> Commands<'w, 's> { self.queue(UnregisterSystem::new(system_id)); } + /// Removes a system previously registered with [`World::register_system_cached`]. + /// + /// See [`World::unregister_system_cached`] for more information. + pub fn unregister_system_cached< + I: SystemInput + Send + 'static, + O: 'static, + M: 'static, + S: IntoSystem + Send + 'static, + >( + &mut self, + system: S, + ) { + self.queue(UnregisterSystemCached::new(system)); + } + /// Similar to [`Self::run_system`], but caching the [`SystemId`] in a /// [`CachedSystemId`](crate::system::CachedSystemId) resource. /// @@ -915,7 +930,7 @@ impl<'w, 's> Commands<'w, 's> { self.run_system_cached_with(system, ()); } - /// Similar to [`Self::run_system_with_input`], but caching the [`SystemId`] in a + /// Similar to [`Self::run_system_with`], but caching the [`SystemId`] in a /// [`CachedSystemId`](crate::system::CachedSystemId) resource. /// /// See [`World::register_system_cached`] for more information. @@ -1720,6 +1735,12 @@ impl<'a> EntityCommands<'a> { /// Clones an entity and returns the [`EntityCommands`] of the clone. /// + /// The clone will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). + /// + /// To configure cloning behavior (such as only cloning certain components), + /// use [`EntityCommands::clone_and_spawn_with`]. + /// /// # Panics /// /// The command will panic when applied if the original entity does not exist. @@ -1735,20 +1756,27 @@ impl<'a> EntityCommands<'a> { /// struct ComponentB(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create a new entity and keep its EntityCommands. + /// // Create a new entity and keep its EntityCommands /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// /// // Create a clone of the first entity - /// let entity_clone = entity.clone(); + /// let mut entity_clone = entity.clone_and_spawn(); /// } /// # bevy_ecs::system::assert_is_system(example_system); - pub fn clone(&mut self) -> EntityCommands<'_> { - self.clone_with(|_| {}) + pub fn clone_and_spawn(&mut self) -> EntityCommands<'_> { + self.clone_and_spawn_with(|_| {}) } /// Clones an entity and allows configuring cloning behavior using [`EntityCloneBuilder`], /// returning the [`EntityCommands`] of the clone. /// + /// By default, the clone will receive all the components of the original that implement + /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). + /// + /// To exclude specific components, use [`EntityCloneBuilder::deny`]. + /// To only include specific components, use [`EntityCloneBuilder::deny_all`] + /// followed by [`EntityCloneBuilder::allow`]. + /// /// # Panics /// /// The command will panic when applied if the original entity does not exist. @@ -1764,21 +1792,21 @@ impl<'a> EntityCommands<'a> { /// struct ComponentB(u32); /// /// fn example_system(mut commands: Commands) { - /// // Create a new entity and keep its EntityCommands. + /// // Create a new entity and keep its EntityCommands /// let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); /// /// // Create a clone of the first entity, but without ComponentB - /// let entity_clone = entity.clone_with(|builder| { + /// let mut entity_clone = entity.clone_and_spawn_with(|builder| { /// builder.deny::(); /// }); /// } /// # bevy_ecs::system::assert_is_system(example_system); - pub fn clone_with( + pub fn clone_and_spawn_with( &mut self, f: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, ) -> EntityCommands<'_> { let entity_clone = self.commands().spawn_empty().id(); - self.queue(clone_entity_with(entity_clone, f)); + self.queue(clone_and_spawn_with(entity_clone, f)); EntityCommands { commands: self.commands_mut().reborrow(), entity: entity_clone, @@ -2258,7 +2286,7 @@ fn observe( } } -fn clone_entity_with( +fn clone_and_spawn_with( entity_clone: Entity, f: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static, ) -> impl EntityCommand { @@ -2604,6 +2632,25 @@ mod tests { assert!(world.get::(e).is_some()); } + #[test] + fn unregister_system_cached_commands() { + let mut world = World::default(); + let mut queue = CommandQueue::default(); + + fn nothing() {} + + assert!(world.iter_resources().count() == 0); + let id = world.register_system_cached(nothing); + assert!(world.iter_resources().count() == 1); + assert!(world.get_entity(id.entity).is_ok()); + + let mut commands = Commands::new(&mut queue, &world); + commands.unregister_system_cached(nothing); + queue.apply(&mut world); + assert!(world.iter_resources().count() == 0); + assert!(world.get_entity(id.entity).is_err()); + } + fn is_send() {} fn is_sync() {} diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index 26440c0c643f4..b36be3fee7597 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -1335,7 +1335,7 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// ## Panics /// - /// This will panic if `NewD` is not a subset of the original fetch `Q` + /// This will panic if `NewD` is not a subset of the original fetch `D` /// /// ## Example /// @@ -1376,19 +1376,108 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// ## Allowed Transmutes /// - /// Besides removing parameters from the query, you can also - /// make limited changes to the types of parameters. + /// Besides removing parameters from the query, + /// you can also make limited changes to the types of parameters. + /// The new query must have a subset of the *read*, *write*, and *required* access of the original query. + /// + /// * `&mut T` and [`Mut`](crate::change_detection::Mut) have read, write, and required access to `T` + /// * `&T` and [`Ref`](crate::change_detection::Ref) have read and required access to `T` + /// * [`Option`] and [`AnyOf<(D, ...)>`](crate::query::AnyOf) have the read and write access of the subqueries, but no required access + /// * Tuples of query data and `#[derive(QueryData)]` structs have the union of the access of their subqueries + /// * [`EntityMut`](crate::world::EntityMut) has read and write access to all components, but no required access + /// * [`EntityRef`](crate::world::EntityRef) has read access to all components, but no required access + /// * [`Entity`], [`EntityLocation`], [`&Archetype`], [`Has`], and [`PhantomData`] have no access at all, + /// so can be added to any query + /// * [`FilteredEntityRef`](crate::world::FilteredEntityRef) and [`FilteredEntityMut`](crate::world::FilteredEntityMut) + /// have access determined by the [`QueryBuilder`](crate::query::QueryBuilder) used to construct them. + /// Any query can be transmuted to them, and they will receive the access of the source query, + /// but only if they are the top-level query and not nested + /// * [`Added`](crate::query::Added) and [`Changed`](crate::query::Changed) filters have read and required access to `T` + /// * [`With`](crate::query::With) and [`Without`](crate::query::Without) filters have no access at all, + /// so can be added to any query + /// * Tuples of query filters and `#[derive(QueryFilter)]` structs have the union of the access of their subqueries + /// * [`Or<(F, ...)>`](crate::query::Or) filters have the read access of the subqueries, but no required access + /// + /// ### Examples of valid transmutes /// - /// * Can always add/remove [`Entity`] - /// * Can always add/remove [`EntityLocation`] - /// * Can always add/remove [`&Archetype`] - /// * `Ref` <-> `&T` - /// * `&mut T` -> `&T` - /// * `&mut T` -> `Ref` - /// * [`EntityMut`](crate::world::EntityMut) -> [`EntityRef`](crate::world::EntityRef) + /// ```rust + /// # use bevy_ecs::{ + /// # prelude::*, + /// # archetype::Archetype, + /// # entity::EntityLocation, + /// # query::{QueryData, QueryFilter}, + /// # world::{FilteredEntityMut, FilteredEntityRef}, + /// # }; + /// # use std::marker::PhantomData; + /// # + /// # fn assert_valid_transmute() { + /// # assert_valid_transmute_filtered::(); + /// # } + /// # + /// # fn assert_valid_transmute_filtered() { + /// # let mut world = World::new(); + /// # // Make sure all components in the new query are initialized + /// # let state = world.query_filtered::(); + /// # let state = world.query_filtered::(); + /// # state.transmute_filtered::(&world); + /// # } + /// # + /// # #[derive(Component)] + /// # struct T; + /// # + /// # #[derive(Component)] + /// # struct U; + /// # + /// # #[derive(Component)] + /// # struct V; + /// # + /// // `&mut T` and `Mut` access the same data and can be transmuted to each other, + /// // `&T` and `Ref` access the same data and can be transmuted to each other, + /// // and mutable versions can be transmuted to read-only versions + /// assert_valid_transmute::<&mut T, &T>(); + /// assert_valid_transmute::<&mut T, Mut>(); + /// assert_valid_transmute::, &mut T>(); + /// assert_valid_transmute::<&T, Ref>(); + /// assert_valid_transmute::, &T>(); + /// + /// // The structure can be rearranged, or subqueries dropped + /// assert_valid_transmute::<(&T, &U), &T>(); + /// assert_valid_transmute::<((&T, &U), &V), (&T, (&U, &V))>(); + /// assert_valid_transmute::, (Option<&T>, Option<&U>)>(); + /// + /// // Queries with no access can be freely added + /// assert_valid_transmute::< + /// &T, + /// (&T, Entity, EntityLocation, &Archetype, Has, PhantomData), + /// >(); + /// + /// // Required access can be transmuted to optional, + /// // and optional access can be transmuted to other optional access + /// assert_valid_transmute::<&T, Option<&T>>(); + /// assert_valid_transmute::, Option<&T>>(); + /// // Note that removing subqueries from `AnyOf` will result + /// // in an `AnyOf` where all subqueries can yield `None`! + /// assert_valid_transmute::, AnyOf<(&T, &U)>>(); + /// assert_valid_transmute::>(); + /// + /// // Anything can be transmuted to `FilteredEntityRef` or `FilteredEntityMut` + /// // This will create a `FilteredEntityMut` that only has read access to `T` + /// assert_valid_transmute::<&T, FilteredEntityMut>(); + /// // This transmute will succeed, but the `FilteredEntityMut` will have no access! + /// // It must be the top-level query to be given access, but here it is nested in a tuple. + /// assert_valid_transmute::<&T, (Entity, FilteredEntityMut)>(); + /// + /// // `Added` and `Changed` filters have the same access as `&T` data + /// // Remember that they are only evaluated on the transmuted query, not the original query! + /// assert_valid_transmute_filtered::, &T, ()>(); + /// assert_valid_transmute_filtered::<&mut T, (), &T, Added>(); + /// // Nested inside of an `Or` filter, they have the same access as `Option<&T>`. + /// assert_valid_transmute_filtered::, (), Entity, Or<(Changed, With)>>(); + /// ``` /// /// [`EntityLocation`]: crate::entity::EntityLocation /// [`&Archetype`]: crate::archetype::Archetype + /// [`Has`]: crate::query::Has #[track_caller] pub fn transmute_lens(&mut self) -> QueryLens<'_, NewD> { self.transmute_lens_filtered::() diff --git a/crates/bevy_ecs/src/system/system.rs b/crates/bevy_ecs/src/system/system.rs index c1c628cb2e1aa..2103119b5ff63 100644 --- a/crates/bevy_ecs/src/system/system.rs +++ b/crates/bevy_ecs/src/system/system.rs @@ -322,14 +322,14 @@ pub trait RunSystemOnce: Sized { where T: IntoSystem<(), Out, Marker>, { - self.run_system_once_with((), system) + self.run_system_once_with(system, ()) } /// Tries to run a system with given input and apply deferred parameters. fn run_system_once_with( self, - input: SystemIn<'_, T::System>, system: T, + input: SystemIn<'_, T::System>, ) -> Result where T: IntoSystem, @@ -339,8 +339,8 @@ pub trait RunSystemOnce: Sized { impl RunSystemOnce for &mut World { fn run_system_once_with( self, - input: SystemIn<'_, T::System>, system: T, + input: SystemIn<'_, T::System>, ) -> Result where T: IntoSystem, @@ -392,7 +392,7 @@ mod tests { } let mut world = World::default(); - let n = world.run_system_once_with(1, system).unwrap(); + let n = world.run_system_once_with(system, 1).unwrap(); assert_eq!(n, 2); assert_eq!(world.resource::().0, 1); } diff --git a/crates/bevy_ecs/src/system/system_registry.rs b/crates/bevy_ecs/src/system/system_registry.rs index f00740a1f7942..5d101d43ebad5 100644 --- a/crates/bevy_ecs/src/system/system_registry.rs +++ b/crates/bevy_ecs/src/system/system_registry.rs @@ -201,7 +201,7 @@ impl World { /// This is different from [`RunSystemOnce::run_system_once`](crate::system::RunSystemOnce::run_system_once), /// because it keeps local state between calls and change detection works correctly. /// - /// In order to run a chained system with an input, use [`World::run_system_with_input`] instead. + /// In order to run a chained system with an input, use [`World::run_system_with`] instead. /// /// # Limitations /// @@ -286,7 +286,7 @@ impl World { &mut self, id: SystemId<(), O>, ) -> Result> { - self.run_system_with_input(id, ()) + self.run_system_with(id, ()) } /// Run a stored chained system by its [`SystemId`], providing an input value. @@ -309,13 +309,13 @@ impl World { /// let mut world = World::default(); /// let counter_one = world.register_system(increment); /// let counter_two = world.register_system(increment); - /// assert_eq!(world.run_system_with_input(counter_one, 1).unwrap(), 1); - /// assert_eq!(world.run_system_with_input(counter_one, 20).unwrap(), 21); - /// assert_eq!(world.run_system_with_input(counter_two, 30).unwrap(), 30); + /// assert_eq!(world.run_system_with(counter_one, 1).unwrap(), 1); + /// assert_eq!(world.run_system_with(counter_one, 20).unwrap(), 21); + /// assert_eq!(world.run_system_with(counter_two, 30).unwrap(), 30); /// ``` /// /// See [`World::run_system`] for more examples. - pub fn run_system_with_input( + pub fn run_system_with( &mut self, id: SystemId, input: I::Inner<'_>, @@ -451,11 +451,11 @@ impl World { S: IntoSystem + 'static, { let id = self.register_system_cached(system); - self.run_system_with_input(id, input) + self.run_system_with(id, input) } } -/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with_input`]. +/// The [`Command`] type for [`World::run_system`] or [`World::run_system_with`]. /// /// This command runs systems in an exclusive and single threaded way. /// Running slow systems can become a bottleneck. @@ -465,9 +465,9 @@ impl World { /// /// There is no way to get the output of a system when run as a command, because the /// execution of the system happens later. To get the output of a system, use -/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. +/// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. #[derive(Debug, Clone)] -pub struct RunSystemWithInput { +pub struct RunSystemWith { system_id: SystemId, input: I::Inner<'static>, } @@ -478,12 +478,12 @@ pub struct RunSystemWithInput { /// Running slow systems can become a bottleneck. /// /// If the system needs an [`In<_>`](crate::system::In) input value to run, use the -/// [`RunSystemWithInput`] type instead. +/// [`RunSystemWith`] type instead. /// /// There is no way to get the output of a system when run as a command, because the /// execution of the system happens later. To get the output of a system, use -/// [`World::run_system`] or [`World::run_system_with_input`] instead of running the system as a command. -pub type RunSystem = RunSystemWithInput<()>; +/// [`World::run_system`] or [`World::run_system_with`] instead of running the system as a command. +pub type RunSystem = RunSystemWith<()>; impl RunSystem { /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). @@ -492,7 +492,7 @@ impl RunSystem { } } -impl RunSystemWithInput { +impl RunSystemWith { /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands) /// in order to run the specified system with the provided [`In<_>`](crate::system::In) input value. pub fn new_with_input(system_id: SystemId, input: I::Inner<'static>) -> Self { @@ -500,13 +500,13 @@ impl RunSystemWithInput { } } -impl Command for RunSystemWithInput +impl Command for RunSystemWith where I: SystemInput: Send> + 'static, { #[inline] fn apply(self, world: &mut World) { - _ = world.run_system_with_input(self.system_id, self.input); + _ = world.run_system_with(self.system_id, self.input); } } @@ -570,6 +570,42 @@ where } } +/// The [`Command`] type for unregistering one-shot systems from [`Commands`](crate::system::Commands). +pub struct UnregisterSystemCached +where + I: SystemInput + 'static, + S: IntoSystem + Send + 'static, +{ + system: S, + _phantom: PhantomData (I, O, M)>, +} + +impl UnregisterSystemCached +where + I: SystemInput + 'static, + S: IntoSystem + Send + 'static, +{ + /// Creates a new [`Command`] struct, which can be added to [`Commands`](crate::system::Commands). + pub fn new(system: S) -> Self { + Self { + system, + _phantom: PhantomData, + } + } +} + +impl Command for UnregisterSystemCached +where + I: SystemInput + 'static, + O: 'static, + M: 'static, + S: IntoSystem + Send + 'static, +{ + fn apply(self, world: &mut World) { + let _ = world.unregister_system_cached(self.system); + } +} + /// The [`Command`] type for running a cached one-shot system from /// [`Commands`](crate::system::Commands). /// @@ -730,22 +766,22 @@ mod tests { assert_eq!(*world.resource::(), Counter(1)); world - .run_system_with_input(id, NonCopy(1)) + .run_system_with(id, NonCopy(1)) .expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(2)); world - .run_system_with_input(id, NonCopy(1)) + .run_system_with(id, NonCopy(1)) .expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(3)); world - .run_system_with_input(id, NonCopy(20)) + .run_system_with(id, NonCopy(20)) .expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(23)); world - .run_system_with_input(id, NonCopy(1)) + .run_system_with(id, NonCopy(1)) .expect("system runs successfully"); assert_eq!(*world.resource::(), Counter(24)); } @@ -828,7 +864,7 @@ mod tests { fn nested(query: Query<&Callback>, mut commands: Commands) { for callback in query.iter() { - commands.run_system_with_input(callback.0, callback.1); + commands.run_system_with(callback.0, callback.1); } } @@ -922,7 +958,7 @@ mod tests { world.insert_resource(Counter(0)); let id = world.register_system(with_ref); - world.run_system_with_input(id, &2).unwrap(); + world.run_system_with(id, &2).unwrap(); assert_eq!(*world.resource::(), Counter(2)); } @@ -944,15 +980,11 @@ mod tests { let post_system = world.register_system(post); let mut event = MyEvent { cancelled: false }; - world - .run_system_with_input(post_system, &mut event) - .unwrap(); + world.run_system_with(post_system, &mut event).unwrap(); assert!(!event.cancelled); world.resource_mut::().0 = 1; - world - .run_system_with_input(post_system, &mut event) - .unwrap(); + world.run_system_with(post_system, &mut event).unwrap(); assert!(event.cancelled); } diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 0ac19e312b878..22a84f69f9134 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -728,7 +728,7 @@ impl<'w> EntityMut<'w> { /// let mut entity_mut = world.entity_mut(entity); /// let mut ptrs = entity_mut.get_mut_by_id(&HashSet::from_iter([x_id, y_id])) /// # .unwrap(); - /// # let [mut x_ptr, mut y_ptr] = ptrs.get_many_mut([&x_id, &y_id]).unwrap(); + /// # let [Some(mut x_ptr), Some(mut y_ptr)] = ptrs.get_many_mut([&x_id, &y_id]) else { unreachable!() }; /// # assert_eq!((unsafe { x_ptr.as_mut().deref_mut::() }, unsafe { y_ptr.as_mut().deref_mut::() }), (&mut X(42), &mut Y(10))); /// ``` #[inline] @@ -3656,7 +3656,7 @@ unsafe impl DynamicComponentFetch for &'_ HashSet { self, cell: UnsafeEntityCell<'_>, ) -> Result, EntityComponentError> { - let mut ptrs = HashMap::with_capacity(self.len()); + let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); for &id in self { ptrs.insert( id, @@ -3671,7 +3671,7 @@ unsafe impl DynamicComponentFetch for &'_ HashSet { self, cell: UnsafeEntityCell<'_>, ) -> Result, EntityComponentError> { - let mut ptrs = HashMap::with_capacity(self.len()); + let mut ptrs = HashMap::with_capacity_and_hasher(self.len(), Default::default()); for &id in self { ptrs.insert( id, diff --git a/crates/bevy_ecs/src/world/mod.rs b/crates/bevy_ecs/src/world/mod.rs index 5152e549a8ee4..c2a5199f86281 100644 --- a/crates/bevy_ecs/src/world/mod.rs +++ b/crates/bevy_ecs/src/world/mod.rs @@ -1235,8 +1235,7 @@ impl World { /// # use bevy_ecs::prelude::*; /// # use bevy_ecs::entity::EntityHash; /// # use bevy_ecs::entity::EntityHashSet; - /// # use bevy_utils::hashbrown::HashSet; - /// # use bevy_utils::hashbrown::hash_map::DefaultHashBuilder; + /// # use bevy_utils::HashSet; /// # let mut world = World::new(); /// # let id1 = world.spawn_empty().id(); /// # let id2 = world.spawn_empty().id(); @@ -3462,7 +3461,7 @@ impl World { /// // probably use something like `ReflectFromPtr` in a real-world scenario. /// /// // Create the hash map that will store the closures for each resource type - /// let mut closures: HashMap)>> = HashMap::new(); + /// let mut closures: HashMap)>> = HashMap::default(); /// /// // Add closure for `A` /// closures.insert(TypeId::of::(), Box::new(|ptr| { @@ -3539,7 +3538,7 @@ impl World { /// // probably use something like `ReflectFromPtr` in a real-world scenario. /// /// // Create the hash map that will store the mutator closures for each resource type - /// let mut mutators: HashMap)>> = HashMap::new(); + /// let mut mutators: HashMap)>> = HashMap::default(); /// /// // Add mutator closure for `A` /// mutators.insert(TypeId::of::(), Box::new(|mut_untyped| { @@ -4299,38 +4298,46 @@ mod tests { let baz_id = TypeId::of::(); assert_eq!( to_type_ids(world.inspect_entity(ent0).collect()), - [Some(foo_id), Some(bar_id), Some(baz_id)].into() + [Some(foo_id), Some(bar_id), Some(baz_id)] + .into_iter() + .collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent1).collect()), - [Some(foo_id), Some(bar_id)].into() + [Some(foo_id), Some(bar_id)] + .into_iter() + .collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent2).collect()), - [Some(bar_id), Some(baz_id)].into() + [Some(bar_id), Some(baz_id)] + .into_iter() + .collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent3).collect()), - [Some(foo_id), Some(baz_id)].into() + [Some(foo_id), Some(baz_id)] + .into_iter() + .collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent4).collect()), - [Some(foo_id)].into() + [Some(foo_id)].into_iter().collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent5).collect()), - [Some(bar_id)].into() + [Some(bar_id)].into_iter().collect::>() ); assert_eq!( to_type_ids(world.inspect_entity(ent6).collect()), - [Some(baz_id)].into() + [Some(baz_id)].into_iter().collect::>() ); } #[test] fn iterate_entities() { let mut world = World::new(); - let mut entity_counters = HashMap::new(); + let mut entity_counters = >::default(); let iterate_and_count_entities = |world: &World, entity_counters: &mut HashMap<_, _>| { entity_counters.clear(); diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index f3c0a4d9fb470..319bfa7590e72 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -217,7 +217,7 @@ async fn load_gltf<'a, 'b, 'c>( .to_string(); let buffer_data = load_buffers(&gltf, load_context).await?; - let mut linear_textures = HashSet::default(); + let mut linear_textures = >::default(); for material in gltf.materials() { if let Some(texture) = material.normal_texture() { @@ -259,11 +259,11 @@ async fn load_gltf<'a, 'b, 'c>( #[cfg(feature = "bevy_animation")] let paths = { - let mut paths = HashMap::)>::new(); + let mut paths = HashMap::)>::default(); for scene in gltf.scenes() { for node in scene.nodes() { let root_index = node.index(); - paths_recur(node, &[], &mut paths, root_index, &mut HashSet::new()); + paths_recur(node, &[], &mut paths, root_index, &mut HashSet::default()); } } paths @@ -272,12 +272,14 @@ async fn load_gltf<'a, 'b, 'c>( #[cfg(feature = "bevy_animation")] let (animations, named_animations, animation_roots) = { use bevy_animation::{animated_field, animation_curves::*, gltf_curves::*, VariableCurve}; - use bevy_math::curve::{ConstantCurve, Interval, UnevenSampleAutoCurve}; - use bevy_math::{Quat, Vec4}; + use bevy_math::{ + curve::{ConstantCurve, Interval, UnevenSampleAutoCurve}, + Quat, Vec4, + }; use gltf::animation::util::ReadOutputs; let mut animations = vec![]; - let mut named_animations = HashMap::default(); - let mut animation_roots = HashSet::default(); + let mut named_animations = >::default(); + let mut animation_roots = >::default(); for animation in gltf.animations() { let mut animation_clip = AnimationClip::default(); for channel in animation.channels() { @@ -603,7 +605,7 @@ async fn load_gltf<'a, 'b, 'c>( } let mut materials = vec![]; - let mut named_materials = HashMap::default(); + let mut named_materials = >::default(); // Only include materials in the output if they're set to be retained in the MAIN_WORLD and/or RENDER_WORLD by the load_materials flag if !settings.load_materials.is_empty() { // NOTE: materials must be loaded after textures because image load() calls will happen before load_with_settings, preventing is_srgb from being set properly @@ -616,9 +618,9 @@ async fn load_gltf<'a, 'b, 'c>( } } let mut meshes = vec![]; - let mut named_meshes = HashMap::default(); - let mut meshes_on_skinned_nodes = HashSet::default(); - let mut meshes_on_non_skinned_nodes = HashSet::default(); + let mut named_meshes = >::default(); + let mut meshes_on_skinned_nodes = >::default(); + let mut meshes_on_non_skinned_nodes = >::default(); for gltf_node in gltf.nodes() { if gltf_node.skin().is_some() { if let Some(mesh) = gltf_node.mesh() { @@ -783,10 +785,10 @@ async fn load_gltf<'a, 'b, 'c>( }) .collect(); - let mut nodes = HashMap::>::new(); - let mut named_nodes = HashMap::new(); + let mut nodes = HashMap::>::default(); + let mut named_nodes = >::default(); let mut skins = vec![]; - let mut named_skins = HashMap::default(); + let mut named_skins = >::default(); for node in GltfTreeIterator::try_new(&gltf)? { let skin = node.skin().map(|skin| { let joints = skin @@ -848,12 +850,12 @@ async fn load_gltf<'a, 'b, 'c>( .collect(); let mut scenes = vec![]; - let mut named_scenes = HashMap::default(); + let mut named_scenes = >::default(); let mut active_camera_found = false; for scene in gltf.scenes() { let mut err = None; let mut world = World::default(); - let mut node_index_to_entity_map = HashMap::new(); + let mut node_index_to_entity_map = >::default(); let mut entity_to_skin_index_map = EntityHashMap::default(); let mut scene_load_context = load_context.begin_labeled_asset(); @@ -1904,7 +1906,7 @@ impl<'a> GltfTreeIterator<'a> { .collect::>(); let mut nodes = Vec::new(); - let mut warned_about_max_joints = HashSet::new(); + let mut warned_about_max_joints = >::default(); while let Some(index) = empty_children.pop_front() { if let Some(skin) = unprocessed_nodes.get(&index).unwrap().0.skin() { if skin.joints().len() > MAX_JOINTS && warned_about_max_joints.insert(skin.index()) diff --git a/crates/bevy_hierarchy/src/child_builder.rs b/crates/bevy_hierarchy/src/child_builder.rs index ad5819479d049..a3ed780ad0960 100644 --- a/crates/bevy_hierarchy/src/child_builder.rs +++ b/crates/bevy_hierarchy/src/child_builder.rs @@ -385,8 +385,8 @@ pub trait BuildChildren { /// Adds a single child. /// - /// If the children were previously children of another parent, that parent's [`Children`] component - /// will have those children removed from its list. Removing all children from a parent causes its + /// If the child was previously the child of another parent, that parent's [`Children`] component + /// will have the child removed from its list. Removing all children from a parent causes its /// [`Children`] component to be removed from the entity. /// /// # Panics diff --git a/crates/bevy_hierarchy/src/hierarchy.rs b/crates/bevy_hierarchy/src/hierarchy.rs index b3215bfb66d53..e5a8ba6b1967d 100644 --- a/crates/bevy_hierarchy/src/hierarchy.rs +++ b/crates/bevy_hierarchy/src/hierarchy.rs @@ -444,7 +444,7 @@ mod tests { .id(); let e_clone = commands .entity(e) - .clone_with(|builder| { + .clone_and_spawn_with(|builder| { builder.recursive(true); }) .id(); @@ -483,7 +483,7 @@ mod tests { let child_clone = commands .entity(child) - .clone_with(|builder| { + .clone_and_spawn_with(|builder| { builder.as_child(true); }) .id(); diff --git a/crates/bevy_math/src/ops.rs b/crates/bevy_math/src/ops.rs index 143f98ec0f5b8..42fb55286cc7b 100644 --- a/crates/bevy_math/src/ops.rs +++ b/crates/bevy_math/src/ops.rs @@ -143,7 +143,7 @@ mod std_ops { f32::atan(x) } - /// Computes the four quadrant arctangent of `self` (`y`) and `other` (`x`) in radians. + /// Computes the four-quadrant arctangent of `y` and `x` in radians. /// /// * `x = 0`, `y = 0`: `0` /// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]` @@ -152,8 +152,8 @@ mod std_ops { /// /// Precision is specified when the `libm` feature is enabled. #[inline(always)] - pub fn atan2(x: f32, y: f32) -> f32 { - f32::atan2(x, y) + pub fn atan2(y: f32, x: f32) -> f32 { + f32::atan2(y, x) } /// Simultaneously computes the sine and cosine of the number, `x`. Returns @@ -358,7 +358,7 @@ mod libm_ops { libm::atanf(x) } - /// Computes the four quadrant arctangent of `self` (`y`) and `other` (`x`) in radians. + /// Computes the four-quadrant arctangent of `y` and `x` in radians. /// /// * `x = 0`, `y = 0`: `0` /// * `x >= 0`: `arctan(y/x)` -> `[-pi/2, pi/2]` @@ -367,8 +367,8 @@ mod libm_ops { /// /// Precision is specified when the `libm` feature is enabled. #[inline(always)] - pub fn atan2(x: f32, y: f32) -> f32 { - libm::atan2f(x, y) + pub fn atan2(y: f32, x: f32) -> f32 { + libm::atan2f(y, x) } /// Simultaneously computes the sine and cosine of the number, `x`. Returns diff --git a/crates/bevy_math/src/primitives/dim2.rs b/crates/bevy_math/src/primitives/dim2.rs index c1c4f9b15877c..d476fd86077eb 100644 --- a/crates/bevy_math/src/primitives/dim2.rs +++ b/crates/bevy_math/src/primitives/dim2.rs @@ -8,6 +8,9 @@ use crate::{ Dir2, Vec2, }; +#[cfg(feature = "alloc")] +use super::polygon::is_polygon_simple; + #[cfg(feature = "bevy_reflect")] use bevy_reflect::{std_traits::ReflectDefault, Reflect}; #[cfg(all(feature = "serialize", feature = "bevy_reflect"))] @@ -1629,6 +1632,15 @@ impl Polygon { pub fn new(vertices: impl IntoIterator) -> Self { Self::from_iter(vertices) } + + /// Tests if the polygon is simple. + /// + /// A polygon is simple if it is not self intersecting and not self tangent. + /// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge. + #[cfg(feature = "alloc")] + pub fn is_simple(&self) -> bool { + is_polygon_simple(&self.vertices) + } } impl From> for Polygon { @@ -1745,6 +1757,14 @@ impl BoxedPolygon { pub fn new(vertices: impl IntoIterator) -> Self { Self::from_iter(vertices) } + + /// Tests if the polygon is simple. + /// + /// A polygon is simple if it is not self intersecting and not self tangent. + /// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge. + pub fn is_simple(&self) -> bool { + is_polygon_simple(&self.vertices) + } } /// A polygon centered on the origin where all vertices lie on a circle, equally far apart. diff --git a/crates/bevy_math/src/primitives/mod.rs b/crates/bevy_math/src/primitives/mod.rs index 460e635867ecb..b5c6644b13cc0 100644 --- a/crates/bevy_math/src/primitives/mod.rs +++ b/crates/bevy_math/src/primitives/mod.rs @@ -6,6 +6,7 @@ mod dim2; pub use dim2::*; mod dim3; pub use dim3::*; +mod polygon; #[cfg(feature = "serialize")] mod serde; diff --git a/crates/bevy_math/src/primitives/polygon.rs b/crates/bevy_math/src/primitives/polygon.rs new file mode 100644 index 0000000000000..1167d07981094 --- /dev/null +++ b/crates/bevy_math/src/primitives/polygon.rs @@ -0,0 +1,348 @@ +#[cfg(feature = "alloc")] +use alloc::{collections::BTreeMap, vec::Vec}; + +use core::cmp::Ordering; + +use crate::Vec2; + +use super::{Measured2d, Triangle2d}; + +#[derive(Debug, Clone, Copy)] +enum Endpoint { + Left, + Right, +} + +/// An event in the [`EventQueue`] is either the left or right vertex of an edge of the polygon. +/// +/// Events are ordered so that any event `e1` which is to the left of another event `e2` is less than that event. +/// If `e1.position().x == e2.position().x` the events are ordered from bottom to top. +/// +/// This is the order expected by the [`SweepLine`]. +#[derive(Debug, Clone, Copy)] +struct SweepLineEvent { + segment: Segment, + /// Type of the vertex (left or right) + endpoint: Endpoint, +} +impl SweepLineEvent { + fn position(&self) -> Vec2 { + match self.endpoint { + Endpoint::Left => self.segment.left, + Endpoint::Right => self.segment.right, + } + } +} +impl PartialEq for SweepLineEvent { + fn eq(&self, other: &Self) -> bool { + self.position() == other.position() + } +} +impl Eq for SweepLineEvent {} +impl PartialOrd for SweepLineEvent { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SweepLineEvent { + fn cmp(&self, other: &Self) -> Ordering { + xy_order(self.position(), other.position()) + } +} + +/// Orders 2D points according to the order expected by the sweep line and event queue from -X to +X and then -Y to Y. +fn xy_order(a: Vec2, b: Vec2) -> Ordering { + match a.x.total_cmp(&b.x) { + Ordering::Equal => a.y.total_cmp(&b.y), + ord => ord, + } +} + +/// The event queue holds an ordered list of all events the [`SweepLine`] will encounter when checking the current polygon. +#[cfg(feature = "alloc")] +#[derive(Debug, Clone)] +struct EventQueue { + events: Vec, +} +#[cfg(feature = "alloc")] +impl EventQueue { + /// Initialize a new `EventQueue` with all events from the polygon represented by `vertices`. + /// + /// The events in the event queue will be ordered. + fn new(vertices: &[Vec2]) -> Self { + if vertices.is_empty() { + return Self { events: Vec::new() }; + } + + let mut events = Vec::with_capacity(vertices.len() * 2); + for i in 0..vertices.len() { + let v1 = vertices[i]; + let v2 = *vertices.get(i + 1).unwrap_or(&vertices[0]); + let (left, right) = if xy_order(v1, v2) == Ordering::Less { + (v1, v2) + } else { + (v2, v1) + }; + + let segment = Segment { + edge_index: i, + left, + right, + }; + events.push(SweepLineEvent { + segment, + endpoint: Endpoint::Left, + }); + events.push(SweepLineEvent { + segment, + endpoint: Endpoint::Right, + }); + } + + events.sort(); + + Self { events } + } +} + +/// Represents a segment or rather an edge of the polygon in the [`SweepLine`]. +/// +/// Segments are ordered from bottom to top based on their left vertices if possible. +/// If their y values are identical, the segments are ordered based on the y values of their right vertices. +#[derive(Debug, Clone, Copy)] +struct Segment { + edge_index: usize, + left: Vec2, + right: Vec2, +} +impl PartialEq for Segment { + fn eq(&self, other: &Self) -> bool { + self.edge_index == other.edge_index + } +} +impl Eq for Segment {} +impl PartialOrd for Segment { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for Segment { + fn cmp(&self, other: &Self) -> Ordering { + match self.left.y.total_cmp(&other.left.y) { + Ordering::Equal => self.right.y.total_cmp(&other.right.y), + ord => ord, + } + } +} + +/// Holds information about which segment is above and which is below a given [`Segment`] +#[derive(Debug, Clone, Copy)] +struct SegmentOrder { + above: Option, + below: Option, +} + +/// A sweep line allows for an efficient search for intersections between [segments](`Segment`). +/// +/// It can be thought of as a vertical line sweeping from -X to +X across the polygon that keeps track of the order of the segments +/// the sweep line is intersecting at any given moment. +#[cfg(feature = "alloc")] +#[derive(Debug, Clone)] +struct SweepLine<'a> { + vertices: &'a [Vec2], + tree: BTreeMap, +} +#[cfg(feature = "alloc")] +impl<'a> SweepLine<'a> { + fn new(vertices: &'a [Vec2]) -> Self { + Self { + vertices, + tree: BTreeMap::new(), + } + } + + /// Determine whether the given edges of the polygon intersect. + fn intersects(&self, edge1: Option, edge2: Option) -> bool { + let Some(edge1) = edge1 else { + return false; + }; + let Some(edge2) = edge2 else { + return false; + }; + + // All adjacent edges intersect at their shared vertex + // but these intersections do not count so we ignore them here. + // Likewise a segment will always intersect itself / an identical edge. + if edge1 == edge2 + || (edge1 + 1) % self.vertices.len() == edge2 + || (edge2 + 1) % self.vertices.len() == edge1 + { + return false; + } + + let s11 = self.vertices[edge1]; + let s12 = *self.vertices.get(edge1 + 1).unwrap_or(&self.vertices[0]); + let s21 = self.vertices[edge2]; + let s22 = *self.vertices.get(edge2 + 1).unwrap_or(&self.vertices[0]); + + // When both points of the second edge are on the same side of the first edge, no intersection is possible. + if point_side(s11, s12, s21) * point_side(s11, s12, s22) > 0.0 { + return false; + } + if point_side(s21, s22, s11) * point_side(s21, s22, s12) > 0.0 { + return false; + } + + true + } + + /// Add a new segment to the sweep line + fn add(&mut self, s: Segment) -> SegmentOrder { + let above = if let Some((next_s, next_ord)) = self.tree.range_mut(s..).next() { + next_ord.below.replace(s.edge_index); + Some(next_s.edge_index) + } else { + None + }; + let below = if let Some((prev_s, prev_ord)) = self.tree.range_mut(..s).next_back() { + prev_ord.above.replace(s.edge_index); + Some(prev_s.edge_index) + } else { + None + }; + + let s_ord = SegmentOrder { above, below }; + self.tree.insert(s, s_ord); + s_ord + } + + /// Get the segment order for the given segment. + /// + /// If `s` has not been added to the [`SweepLine`] `None` will be returned. + fn find(&self, s: &Segment) -> Option<&SegmentOrder> { + self.tree.get(s) + } + + /// Remove `s` from the [`SweepLine`]. + fn remove(&mut self, s: &Segment) { + let Some(s_ord) = self.tree.get(s).copied() else { + return; + }; + + if let Some((_, above_ord)) = self.tree.range_mut(s..).next() { + above_ord.below = s_ord.below; + } + if let Some((_, below_ord)) = self.tree.range_mut(..s).next_back() { + below_ord.above = s_ord.above; + } + + self.tree.remove(s); + } +} + +/// Test what side of the line through `p1` and `p2` `q` is. +/// +/// The result will be `0` if the `q` is on the segment, negative for one side and positive for the other. +#[inline(always)] +fn point_side(p1: Vec2, p2: Vec2, q: Vec2) -> f32 { + (p2.x - p1.x) * (q.y - p1.y) - (q.x - p1.x) * (p2.y - p1.y) +} + +/// Tests whether the `vertices` describe a simple polygon. +/// The last vertex must not be equal to the first vertex. +/// +/// A polygon is simple if it is not self intersecting and not self tangent. +/// As such, no two edges of the polygon may cross each other and each vertex must not lie on another edge. +/// +/// Any 'polygon' with less than three vertices is simple. +/// +/// The algorithm used is the Shamos-Hoey algorithm, a version of the Bentley-Ottman algorithm adapted to only detect whether any intersections exist. +/// This function will run in O(n * log n) +#[cfg(feature = "alloc")] +pub fn is_polygon_simple(vertices: &[Vec2]) -> bool { + if vertices.len() < 3 { + return true; + } + if vertices.len() == 3 { + return Triangle2d::new(vertices[0], vertices[1], vertices[2]).area() > 0.0; + } + + let event_queue = EventQueue::new(vertices); + let mut sweep_line = SweepLine::new(vertices); + + for e in event_queue.events { + match e.endpoint { + Endpoint::Left => { + let s = sweep_line.add(e.segment); + if sweep_line.intersects(Some(e.segment.edge_index), s.above) + || sweep_line.intersects(Some(e.segment.edge_index), s.below) + { + return false; + } + } + Endpoint::Right => { + if let Some(s) = sweep_line.find(&e.segment) { + if sweep_line.intersects(s.above, s.below) { + return false; + } + sweep_line.remove(&e.segment); + } + } + } + } + + true +} + +#[cfg(test)] +mod tests { + use crate::{primitives::polygon::is_polygon_simple, Vec2}; + + #[test] + fn complex_polygon() { + // A square with one side punching through the opposite side. + let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(2.0, 0.5)]; + assert!(!is_polygon_simple(&verts)); + + // A square with a vertex from one side touching the opposite side. + let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y, Vec2::new(1.0, 0.5)]; + assert!(!is_polygon_simple(&verts)); + + // A square with one side touching the opposite side. + let verts = [ + Vec2::ZERO, + Vec2::X, + Vec2::ONE, + Vec2::Y, + Vec2::new(1.0, 0.6), + Vec2::new(1.0, 0.4), + ]; + assert!(!is_polygon_simple(&verts)); + + // Four points lying on a line + let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::new(5., 3.), Vec2::NEG_X]; + assert!(!is_polygon_simple(&verts)); + + // Three points lying on a line + let verts = [Vec2::ONE, Vec2::new(3., 2.), Vec2::NEG_X]; + assert!(!is_polygon_simple(&verts)); + + // Two identical points and one other point + let verts = [Vec2::ONE, Vec2::ONE, Vec2::NEG_X]; + assert!(!is_polygon_simple(&verts)); + + // Two triangles with one shared side + let verts = [Vec2::ZERO, Vec2::X, Vec2::Y, Vec2::ONE, Vec2::X, Vec2::Y]; + assert!(!is_polygon_simple(&verts)); + } + + #[test] + fn simple_polygon() { + // A square + let verts = [Vec2::ZERO, Vec2::X, Vec2::ONE, Vec2::Y]; + assert!(is_polygon_simple(&verts)); + + let verts = []; + assert!(is_polygon_simple(&verts)); + } +} diff --git a/crates/bevy_mesh/src/primitives/dim2.rs b/crates/bevy_mesh/src/primitives/dim2.rs index 72a304c6994a3..3eda19eed9037 100644 --- a/crates/bevy_mesh/src/primitives/dim2.rs +++ b/crates/bevy_mesh/src/primitives/dim2.rs @@ -1059,7 +1059,7 @@ mod tests { use crate::{Mesh, MeshBuilder, Meshable, VertexAttributeValues}; fn count_distinct_positions(points: &[[f32; 3]]) -> usize { - let mut map = HashSet::new(); + let mut map = >::default(); for point in points { map.insert(point.map(FloatOrd)); } diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index bbab0dff08b55..41932a2aaadc1 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -24,7 +24,7 @@ use bevy_render::{ sync_world::RenderEntity, Extract, }; -use bevy_utils::{hashbrown::HashSet, tracing::warn}; +use bevy_utils::{tracing::warn, HashSet}; pub(crate) use crate::cluster::assign::assign_objects_to_clusters; use crate::MeshPipeline; diff --git a/crates/bevy_pbr/src/extended_material.rs b/crates/bevy_pbr/src/extended_material.rs index ae8f9f710e0d2..a443b953d5cfc 100644 --- a/crates/bevy_pbr/src/extended_material.rs +++ b/crates/bevy_pbr/src/extended_material.rs @@ -155,14 +155,32 @@ impl AsBindGroup for ExtendedMaterial { layout: &BindGroupLayout, render_device: &RenderDevice, (base_param, extended_param): &mut SystemParamItem<'_, '_, Self::Param>, + mut force_no_bindless: bool, ) -> Result, AsBindGroupError> { + // Only allow bindless mode if both the base material and the extension + // support it. + force_no_bindless = force_no_bindless + || B::BINDLESS_SLOT_COUNT.is_none() + || E::BINDLESS_SLOT_COUNT.is_none(); + // add together the bindings of the base material and the user material let UnpreparedBindGroup { mut bindings, data: base_data, - } = B::unprepared_bind_group(&self.base, layout, render_device, base_param)?; - let extended_bindgroup = - E::unprepared_bind_group(&self.extension, layout, render_device, extended_param)?; + } = B::unprepared_bind_group( + &self.base, + layout, + render_device, + base_param, + force_no_bindless, + )?; + let extended_bindgroup = E::unprepared_bind_group( + &self.extension, + layout, + render_device, + extended_param, + force_no_bindless, + )?; bindings.extend(extended_bindgroup.bindings.0); @@ -174,13 +192,23 @@ impl AsBindGroup for ExtendedMaterial { fn bind_group_layout_entries( render_device: &RenderDevice, + mut force_no_bindless: bool, ) -> Vec where Self: Sized, { + // Only allow bindless mode if both the base material and the extension + // support it. + force_no_bindless = force_no_bindless + || B::BINDLESS_SLOT_COUNT.is_none() + || E::BINDLESS_SLOT_COUNT.is_none(); + // add together the bindings of the standard material and the user material - let mut entries = B::bind_group_layout_entries(render_device); - entries.extend(E::bind_group_layout_entries(render_device)); + let mut entries = B::bind_group_layout_entries(render_device, force_no_bindless); + entries.extend(E::bind_group_layout_entries( + render_device, + force_no_bindless, + )); entries } } diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index 5391d75c862d3..f5516e413bab0 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -631,7 +631,7 @@ where fn new() -> RenderViewLightProbes { RenderViewLightProbes { binding_index_to_textures: vec![], - cubemap_to_binding_index: HashMap::new(), + cubemap_to_binding_index: HashMap::default(), render_light_probes: vec![], view_light_probe_info: C::ViewLightProbeInfo::default(), } diff --git a/crates/bevy_pbr/src/material.rs b/crates/bevy_pbr/src/material.rs index 22bb037d7ce4c..8ee7a45a57509 100644 --- a/crates/bevy_pbr/src/material.rs +++ b/crates/bevy_pbr/src/material.rs @@ -1062,6 +1062,7 @@ impl RenderAsset for PreparedMaterial { &pipeline.material_layout, render_device, material_param, + false, ) { Ok(unprepared) => { bind_group_allocator.init(render_device, *material_binding_id, unprepared); diff --git a/crates/bevy_pbr/src/material_bind_groups.rs b/crates/bevy_pbr/src/material_bind_groups.rs index b64ca339519ab..ab3f4b464f81e 100644 --- a/crates/bevy_pbr/src/material_bind_groups.rs +++ b/crates/bevy_pbr/src/material_bind_groups.rs @@ -23,10 +23,7 @@ use bevy_render::{ texture::FallbackImage, }; use bevy_utils::{default, tracing::error, HashMap}; -use core::any; -use core::iter; -use core::marker::PhantomData; -use core::num::NonZero; +use core::{any, iter, marker::PhantomData, num::NonZero}; /// An object that creates and stores bind groups for a single material type. /// @@ -767,7 +764,7 @@ where fn from_world(world: &mut World) -> Self { // Create a new bind group allocator. let render_device = world.resource::(); - let bind_group_layout_entries = M::bind_group_layout_entries(render_device); + let bind_group_layout_entries = M::bind_group_layout_entries(render_device, false); let bind_group_layout = render_device.create_bind_group_layout(M::label(), &bind_group_layout_entries); let fallback_buffers = @@ -818,7 +815,7 @@ impl MaterialFallbackBuffers { render_device: &RenderDevice, bind_group_layout_entries: &[BindGroupLayoutEntry], ) -> MaterialFallbackBuffers { - let mut fallback_buffers = HashMap::new(); + let mut fallback_buffers = HashMap::default(); for bind_group_layout_entry in bind_group_layout_entries { // Create a dummy buffer of the appropriate size. let BindingType::Buffer { diff --git a/crates/bevy_pbr/src/meshlet/from_mesh.rs b/crates/bevy_pbr/src/meshlet/from_mesh.rs index 7a5365296759d..14b46e55ba46e 100644 --- a/crates/bevy_pbr/src/meshlet/from_mesh.rs +++ b/crates/bevy_pbr/src/meshlet/from_mesh.rs @@ -273,7 +273,7 @@ fn find_connected_meshlets( } // For each meshlet pair, count how many vertices they share - let mut meshlet_pair_to_shared_vertex_count = HashMap::new(); + let mut meshlet_pair_to_shared_vertex_count = >::default(); for vertex_meshlet_ids in vertices_to_meshlets { for (meshlet_queue_id1, meshlet_queue_id2) in vertex_meshlet_ids.into_iter().tuple_combinations() diff --git a/crates/bevy_pbr/src/meshlet/instance_manager.rs b/crates/bevy_pbr/src/meshlet/instance_manager.rs index f0c47fddf6072..c190d7ea367b0 100644 --- a/crates/bevy_pbr/src/meshlet/instance_manager.rs +++ b/crates/bevy_pbr/src/meshlet/instance_manager.rs @@ -76,8 +76,8 @@ impl InstanceManager { view_instance_visibility: EntityHashMap::default(), next_material_id: 0, - material_id_lookup: HashMap::new(), - material_ids_present_in_scene: HashSet::new(), + material_id_lookup: HashMap::default(), + material_ids_present_in_scene: HashSet::default(), } } @@ -120,7 +120,14 @@ impl InstanceManager { return; }; - let mesh_uniform = MeshUniform::new(&transforms, 0, mesh_material_binding_id.slot, None); + let mesh_uniform = MeshUniform::new( + &transforms, + 0, + mesh_material_binding_id.slot, + None, + None, + None, + ); // Append instance data self.instances.push(( diff --git a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs index 4170ac6279446..4cae0d50d19f5 100644 --- a/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs +++ b/crates/bevy_pbr/src/meshlet/meshlet_mesh_manager.rs @@ -47,7 +47,7 @@ impl FromWorld for MeshletMeshManager { "meshlet_simplification_errors", render_device, ), - meshlet_mesh_slices: HashMap::new(), + meshlet_mesh_slices: HashMap::default(), } } } diff --git a/crates/bevy_pbr/src/pbr_material.rs b/crates/bevy_pbr/src/pbr_material.rs index 1a679c08fa572..2e93b68094e17 100644 --- a/crates/bevy_pbr/src/pbr_material.rs +++ b/crates/bevy_pbr/src/pbr_material.rs @@ -31,6 +31,7 @@ pub enum UvChannel { #[derive(Asset, AsBindGroup, Reflect, Debug, Clone)] #[bind_group_data(StandardMaterialKey)] #[uniform(0, StandardMaterialUniform)] +#[bindless(16)] #[reflect(Default, Debug)] pub struct StandardMaterial { /// The color of the surface of the material before lighting. diff --git a/crates/bevy_pbr/src/prepass/mod.rs b/crates/bevy_pbr/src/prepass/mod.rs index 05cbc9cda9fe2..1c2f1f4ec27a5 100644 --- a/crates/bevy_pbr/src/prepass/mod.rs +++ b/crates/bevy_pbr/src/prepass/mod.rs @@ -253,6 +253,11 @@ pub struct PrepassPipeline { pub deferred_material_vertex_shader: Option>, pub deferred_material_fragment_shader: Option>, pub material_pipeline: MaterialPipeline, + + /// Whether skins will use uniform buffers on account of storage buffers + /// being unavailable on this platform. + pub skins_use_uniform_buffers: bool, + pub depth_clip_control_supported: bool, _marker: PhantomData, } @@ -345,6 +350,7 @@ impl FromWorld for PrepassPipeline { }, material_layout: M::bind_group_layout(render_device), material_pipeline: world.resource::>().clone(), + skins_use_uniform_buffers: skin::skins_use_uniform_buffers(render_device), depth_clip_control_supported, _marker: PhantomData, } @@ -494,6 +500,11 @@ where shader_defs.push("HAS_PREVIOUS_MORPH".into()); } + // If bindless mode is on, add a `BINDLESS` define. + if self.material_pipeline.bindless { + shader_defs.push("BINDLESS".into()); + } + if key .mesh_key .contains(MeshPipelineKey::VISIBILITY_RANGE_DITHER) @@ -516,6 +527,7 @@ where &key.mesh_key, &mut shader_defs, &mut vertex_attributes, + self.skins_use_uniform_buffers, ); bind_group_layouts.insert(1, bind_group); diff --git a/crates/bevy_pbr/src/prepass/prepass.wgsl b/crates/bevy_pbr/src/prepass/prepass.wgsl index 8f7d45c2fd476..26011d609b50c 100644 --- a/crates/bevy_pbr/src/prepass/prepass.wgsl +++ b/crates/bevy_pbr/src/prepass/prepass.wgsl @@ -69,7 +69,11 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); #ifdef SKINNED - var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); + var world_from_local = skinning::skin_model( + vertex.joint_indices, + vertex.joint_weights, + vertex_no_morph.instance_index + ); #else // SKINNED // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 @@ -142,6 +146,7 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { let prev_model = skinning::skin_prev_model( prev_vertex.joint_indices, prev_vertex.joint_weights, + vertex_no_morph.instance_index ); #else // HAS_PREVIOUS_SKIN let prev_model = mesh_functions::get_previous_world_from_local(prev_vertex.instance_index); diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 57ed8f268685e..e13db2a8bbe18 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -15,6 +15,7 @@ use bevy_render::{ batching::gpu_preprocessing::{GpuPreprocessingMode, GpuPreprocessingSupport}, camera::SortedCameras, mesh::allocator::MeshAllocator, + view::GpuCulling, }; use bevy_render::{ diagnostic::RecordDiagnostics, @@ -31,7 +32,7 @@ use bevy_render::{ }; use bevy_render::{ mesh::allocator::SlabId, - sync_world::{MainEntity, RenderEntity, TemporaryRenderEntity}, + sync_world::{MainEntity, RenderEntity}, }; use bevy_transform::{components::GlobalTransform, prelude::Transform}; #[cfg(feature = "trace")] @@ -286,7 +287,7 @@ pub fn extract_lights( let render_cubemap_visible_entities = RenderCubemapVisibleEntities { data: cubemap_visible_entities .iter() - .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v)) + .map(|v| create_render_visible_mesh_entities(&mapper, v)) .collect::>() .try_into() .unwrap(), @@ -343,7 +344,7 @@ pub fn extract_lights( continue; } let render_visible_entities = - create_render_visible_mesh_entities(&mut commands, &mapper, visible_entities); + create_render_visible_mesh_entities(&mapper, visible_entities); let texel_size = 2.0 * ops::tan(spot_light.outer_angle) / directional_light_shadow_map.size as f32; @@ -430,7 +431,7 @@ pub fn extract_lights( cascade_visible_entities.insert( entity, v.iter() - .map(|v| create_render_visible_mesh_entities(&mut commands, &mapper, v)) + .map(|v| create_render_visible_mesh_entities(&mapper, v)) .collect(), ); } else { @@ -469,7 +470,6 @@ pub fn extract_lights( } fn create_render_visible_mesh_entities( - commands: &mut Commands, mapper: &Extract>, visible_entities: &VisibleMeshEntities, ) -> RenderVisibleMeshEntities { @@ -477,9 +477,7 @@ fn create_render_visible_mesh_entities( entities: visible_entities .iter() .map(|e| { - let render_entity = mapper - .get(*e) - .unwrap_or_else(|_| commands.spawn(TemporaryRenderEntity).id()); + let render_entity = mapper.get(*e).unwrap_or(Entity::PLACEHOLDER); (render_entity, MainEntity::from(*e)) }) .collect(), @@ -689,6 +687,7 @@ pub fn prepare_lights( &ExtractedView, &ExtractedClusterConfig, Option<&RenderLayers>, + Has, ), With, >, @@ -1097,7 +1096,7 @@ pub fn prepare_lights( let mut live_views = EntityHashSet::with_capacity_and_hasher(views_count, EntityHash); // set up light data for each view - for (entity, extracted_view, clusters, maybe_layers) in sorted_cameras + for (entity, extracted_view, clusters, maybe_layers, has_gpu_culling) in sorted_cameras .0 .iter() .filter_map(|sorted_camera| views.get(sorted_camera.entity).ok()) @@ -1105,6 +1104,12 @@ pub fn prepare_lights( live_views.insert(entity); let mut view_lights = Vec::new(); + let gpu_preprocessing_mode = gpu_preprocessing_support.min(if has_gpu_culling { + GpuPreprocessingMode::Culling + } else { + GpuPreprocessingMode::PreprocessingOnly + }); + let is_orthographic = extracted_view.clip_from_view.w_axis.w == 1.0; let cluster_factors_zw = calculate_cluster_factors( clusters.near, @@ -1232,15 +1237,15 @@ pub fn prepare_lights( }, )); + if matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) { + commands.entity(view_light_entity).insert(GpuCulling); + } + view_lights.push(view_light_entity); if first { // Subsequent views with the same light entity will reuse the same shadow map - // TODO: Implement GPU culling for shadow passes. - shadow_render_phases.insert_or_clear( - view_light_entity, - gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), - ); + shadow_render_phases.insert_or_clear(view_light_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1324,14 +1329,15 @@ pub fn prepare_lights( LightEntity::Spot { light_entity }, )); + if matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) { + commands.entity(view_light_entity).insert(GpuCulling); + } + view_lights.push(view_light_entity); if first { // Subsequent views with the same light entity will reuse the same shadow map - shadow_render_phases.insert_or_clear( - view_light_entity, - gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), - ); + shadow_render_phases.insert_or_clear(view_light_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(view_light_entity); } } @@ -1457,15 +1463,17 @@ pub fn prepare_lights( cascade_index, }, )); + + if matches!(gpu_preprocessing_mode, GpuPreprocessingMode::Culling) { + commands.entity(view_light_entity).insert(GpuCulling); + } + view_lights.push(view_light_entity); // Subsequent views with the same light entity will **NOT** reuse the same shadow map // (Because the cascades are unique to each view) // TODO: Implement GPU culling for shadow passes. - shadow_render_phases.insert_or_clear( - view_light_entity, - gpu_preprocessing_support.min(GpuPreprocessingMode::PreprocessingOnly), - ); + shadow_render_phases.insert_or_clear(view_light_entity, gpu_preprocessing_mode); live_shadow_mapping_lights.insert(view_light_entity); } } diff --git a/crates/bevy_pbr/src/render/mesh.rs b/crates/bevy_pbr/src/render/mesh.rs index 450579fb70970..c0f3db33d5f3e 100644 --- a/crates/bevy_pbr/src/render/mesh.rs +++ b/crates/bevy_pbr/src/render/mesh.rs @@ -49,6 +49,7 @@ use bevy_utils::{ HashMap, Parallel, }; use material_bind_groups::MaterialBindingId; +use render::skin::{self, SkinIndex}; use crate::{ render::{ @@ -152,7 +153,6 @@ impl Plugin for MeshRenderPlugin { if let Some(render_app) = app.get_sub_app_mut(RenderApp) { render_app .init_resource::() - .init_resource::() .init_resource::() .init_resource::() .init_resource::() @@ -189,7 +189,9 @@ impl Plugin for MeshRenderPlugin { let mut mesh_bindings_shader_defs = Vec::with_capacity(1); if let Some(render_app) = app.get_sub_app_mut(RenderApp) { - render_app.init_resource::(); + render_app + .init_resource::() + .init_resource::(); let gpu_preprocessing_support = render_app.world().resource::(); @@ -220,6 +222,7 @@ impl Plugin for MeshRenderPlugin { collect_meshes_for_gpu_building .in_set(RenderSet::PrepareAssets) .after(allocator::allocate_and_free_meshes) + .after(extract_skins) // This must be before // `set_mesh_motion_vector_flags` so it doesn't // overwrite those flags. @@ -307,12 +310,12 @@ pub struct MeshUniform { /// [`MeshAllocator`]). This value stores the offset of the first vertex in /// this mesh in that buffer. pub first_vertex_index: u32, + /// The current skin index, or `u32::MAX` if there's no skin. + pub current_skin_index: u32, + /// The previous skin index, or `u32::MAX` if there's no previous skin. + pub previous_skin_index: u32, /// Index of the material inside the bind group data. pub material_bind_group_slot: u32, - /// Padding. - pub pad_a: u32, - /// Padding. - pub pad_b: u32, } /// Information that has to be transferred from CPU to GPU in order to produce @@ -349,12 +352,12 @@ pub struct MeshInputUniform { /// [`MeshAllocator`]). This value stores the offset of the first vertex in /// this mesh in that buffer. pub first_vertex_index: u32, + /// The current skin index, or `u32::MAX` if there's no skin. + pub current_skin_index: u32, + /// The previous skin index, or `u32::MAX` if there's no previous skin. + pub previous_skin_index: u32, /// Index of the material inside the bind group data. pub material_bind_group_slot: u32, - /// Padding. - pub pad_a: u32, - /// Padding. - pub pad_b: u32, } /// Information about each mesh instance needed to cull it on GPU. @@ -386,6 +389,8 @@ impl MeshUniform { first_vertex_index: u32, material_bind_group_slot: MaterialBindGroupSlot, maybe_lightmap_uv_rect: Option, + current_skin_index: Option, + previous_skin_index: Option, ) -> Self { let (local_from_world_transpose_a, local_from_world_transpose_b) = mesh_transforms.world_from_local.inverse_transpose_3x3(); @@ -397,9 +402,9 @@ impl MeshUniform { local_from_world_transpose_b, flags: mesh_transforms.flags, first_vertex_index, + current_skin_index: current_skin_index.unwrap_or(u32::MAX), + previous_skin_index: previous_skin_index.unwrap_or(u32::MAX), material_bind_group_slot: *material_bind_group_slot, - pad_a: 0, - pad_b: 0, } } } @@ -880,6 +885,7 @@ impl RenderMeshInstanceGpuBuilder { current_input_buffer: &mut InstanceInputUniformBuffer, previous_input_buffer: &mut InstanceInputUniformBuffer, mesh_allocator: &MeshAllocator, + skin_indices: &SkinIndices, ) -> u32 { let first_vertex_index = match mesh_allocator.mesh_vertex_slice(&self.shared.mesh_asset_id) { @@ -887,6 +893,15 @@ impl RenderMeshInstanceGpuBuilder { None => 0, }; + let current_skin_index = match skin_indices.current.get(&entity) { + Some(skin_indices) => skin_indices.index(), + None => u32::MAX, + }; + let previous_skin_index = match skin_indices.prev.get(&entity) { + Some(skin_indices) => skin_indices.index(), + None => u32::MAX, + }; + // Create the mesh input uniform. let mut mesh_input_uniform = MeshInputUniform { world_from_local: self.world_from_local.to_transpose(), @@ -894,9 +909,9 @@ impl RenderMeshInstanceGpuBuilder { flags: self.mesh_flags.bits(), previous_input_index: u32::MAX, first_vertex_index, + current_skin_index, + previous_skin_index, material_bind_group_slot: *self.shared.material_bindings_index.slot, - pad_a: 0, - pad_b: 0, }; // Did the last frame contain this entity as well? @@ -917,11 +932,13 @@ impl RenderMeshInstanceGpuBuilder { // Write in the new mesh input uniform. current_input_buffer.set(current_uniform_index, mesh_input_uniform); - occupied_entry.replace_entry(RenderMeshInstanceGpu { - translation: self.world_from_local.translation, - shared: self.shared, - current_uniform_index: NonMaxU32::new(current_uniform_index) - .unwrap_or_default(), + occupied_entry.replace_entry_with(|_, _| { + Some(RenderMeshInstanceGpu { + translation: self.world_from_local.translation, + shared: self.shared, + current_uniform_index: NonMaxU32::new(current_uniform_index) + .unwrap_or_default(), + }) }); } @@ -1109,7 +1126,7 @@ pub fn extract_meshes_for_cpu_building( render_mesh_instances.clear(); for queue in render_mesh_instance_queues.iter_mut() { for (entity, render_mesh_instance) in queue.drain(..) { - render_mesh_instances.insert_unique_unchecked(entity.into(), render_mesh_instance); + render_mesh_instances.insert(entity.into(), render_mesh_instance); } } } @@ -1312,6 +1329,7 @@ pub fn collect_meshes_for_gpu_building( mut mesh_culling_data_buffer: ResMut, mut render_mesh_instance_queues: ResMut, mesh_allocator: Res, + skin_indices: Res, ) { let RenderMeshInstances::GpuBuilding(ref mut render_mesh_instances) = render_mesh_instances.into_inner() @@ -1347,6 +1365,7 @@ pub fn collect_meshes_for_gpu_building( current_input_buffer, previous_input_buffer, &mesh_allocator, + &skin_indices, ); } @@ -1370,6 +1389,7 @@ pub fn collect_meshes_for_gpu_building( current_input_buffer, previous_input_buffer, &mesh_allocator, + &skin_indices, ); mesh_culling_builder .update(&mut mesh_culling_data_buffer, instance_data_index as usize); @@ -1417,6 +1437,10 @@ pub struct MeshPipeline { /// /// This affects whether reflection probes can be used. pub binding_arrays_are_usable: bool, + + /// Whether skins will use uniform buffers on account of storage buffers + /// being unavailable on this platform. + pub skins_use_uniform_buffers: bool, } impl FromWorld for MeshPipeline { @@ -1474,6 +1498,7 @@ impl FromWorld for MeshPipeline { mesh_layouts: MeshLayouts::new(&render_device), per_object_buffer_batch_size: GpuArrayBuffer::::batch_size(&render_device), binding_arrays_are_usable: binding_arrays_are_usable(&render_device), + skins_use_uniform_buffers: skin::skins_use_uniform_buffers(&render_device), } } } @@ -1506,6 +1531,7 @@ impl GetBatchData for MeshPipeline { SRes, SRes>, SRes, + SRes, ); // The material bind group ID, the mesh ID, and the lightmap ID, // respectively. @@ -1518,7 +1544,7 @@ impl GetBatchData for MeshPipeline { type BufferData = MeshUniform; fn get_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator): &SystemParamItem, + (mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem, (_entity, main_entity): (Entity, MainEntity), ) -> Option<(Self::BufferData, Option)> { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1536,6 +1562,9 @@ impl GetBatchData for MeshPipeline { }; let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); + let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index); + let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index); + let material_bind_group_index = mesh_instance.material_bindings_index; Some(( @@ -1544,6 +1573,8 @@ impl GetBatchData for MeshPipeline { first_vertex_index, material_bind_group_index.slot, maybe_lightmap.map(|lightmap| lightmap.uv_rect), + current_skin_index, + previous_skin_index, ), mesh_instance.should_batch().then_some(( material_bind_group_index.group, @@ -1558,7 +1589,7 @@ impl GetFullBatchData for MeshPipeline { type BufferInputData = MeshInputUniform; fn get_index_and_compare_data( - (mesh_instances, lightmaps, _, _): &SystemParamItem, + (mesh_instances, lightmaps, _, _, _): &SystemParamItem, (_entity, main_entity): (Entity, MainEntity), ) -> Option<(NonMaxU32, Option)> { // This should only be called during GPU building. @@ -1584,7 +1615,7 @@ impl GetFullBatchData for MeshPipeline { } fn get_binned_batch_data( - (mesh_instances, lightmaps, _, mesh_allocator): &SystemParamItem, + (mesh_instances, lightmaps, _, mesh_allocator, skin_indices): &SystemParamItem, (_entity, main_entity): (Entity, MainEntity), ) -> Option { let RenderMeshInstances::CpuBuilding(ref mesh_instances) = **mesh_instances else { @@ -1601,16 +1632,21 @@ impl GetFullBatchData for MeshPipeline { }; let maybe_lightmap = lightmaps.render_lightmaps.get(&main_entity); + let current_skin_index = skin_indices.current.get(&main_entity).map(SkinIndex::index); + let previous_skin_index = skin_indices.prev.get(&main_entity).map(SkinIndex::index); + Some(MeshUniform::new( &mesh_instance.transforms, first_vertex_index, mesh_instance.material_bindings_index.slot, maybe_lightmap.map(|lightmap| lightmap.uv_rect), + current_skin_index, + previous_skin_index, )) } fn get_binned_index( - (mesh_instances, _, _, _): &SystemParamItem, + (mesh_instances, _, _, _, _): &SystemParamItem, (_entity, main_entity): (Entity, MainEntity), ) -> Option { // This should only be called during GPU building. @@ -1628,7 +1664,7 @@ impl GetFullBatchData for MeshPipeline { } fn get_batch_indirect_parameters_index( - (mesh_instances, _, meshes, mesh_allocator): &SystemParamItem, + (mesh_instances, _, meshes, mesh_allocator, _): &SystemParamItem, indirect_parameters_buffer: &mut IndirectParametersBuffer, entity: (Entity, MainEntity), instance_index: u32, @@ -1868,15 +1904,22 @@ pub fn setup_morph_and_skinning_defs( key: &MeshPipelineKey, shader_defs: &mut Vec, vertex_attributes: &mut Vec, + skins_use_uniform_buffers: bool, ) -> BindGroupLayout { + let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); + let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED); + let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS); + + if skins_use_uniform_buffers { + shader_defs.push("SKINS_USE_UNIFORM_BUFFERS".into()); + } + let mut add_skin_data = || { shader_defs.push("SKINNED".into()); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_INDEX.at_shader_location(offset)); vertex_attributes.push(Mesh::ATTRIBUTE_JOINT_WEIGHT.at_shader_location(offset + 1)); }; - let is_morphed = key.intersects(MeshPipelineKey::MORPH_TARGETS); - let is_lightmapped = key.intersects(MeshPipelineKey::LIGHTMAPPED); - let motion_vector_prepass = key.intersects(MeshPipelineKey::MOTION_VECTOR_PREPASS); + match ( is_skinned(layout), is_morphed, @@ -1985,6 +2028,7 @@ impl SpecializedMeshPipeline for MeshPipeline { &key, &mut shader_defs, &mut vertex_attributes, + self.skins_use_uniform_buffers, )); if key.contains(MeshPipelineKey::SCREEN_SPACE_AMBIENT_OCCLUSION) { @@ -2477,6 +2521,7 @@ impl RenderCommand

for SetMeshViewBindGroup pub struct SetMeshBindGroup; impl RenderCommand

for SetMeshBindGroup { type Param = ( + SRes, SRes, SRes, SRes, @@ -2491,11 +2536,14 @@ impl RenderCommand

for SetMeshBindGroup { item: &P, has_motion_vector_prepass: bool, _item_query: Option<()>, - (bind_groups, mesh_instances, skin_indices, morph_indices, lightmaps): SystemParamItem< - 'w, - '_, - Self::Param, - >, + ( + render_device, + bind_groups, + mesh_instances, + skin_indices, + morph_indices, + lightmaps, + ): SystemParamItem<'w, '_, Self::Param>, pass: &mut TrackedRenderPass<'w>, ) -> RenderCommandResult { let bind_groups = bind_groups.into_inner(); @@ -2508,6 +2556,7 @@ impl RenderCommand

for SetMeshBindGroup { let Some(mesh_asset_id) = mesh_instances.mesh_asset_id(*entity) else { return RenderCommandResult::Success; }; + let current_skin_index = skin_indices.current.get(entity); let prev_skin_index = skin_indices.prev.get(entity); let current_morph_index = morph_indices.current.get(entity); @@ -2542,8 +2591,10 @@ impl RenderCommand

for SetMeshBindGroup { offset_count += 1; } if let Some(current_skin_index) = current_skin_index { - dynamic_offsets[offset_count] = current_skin_index.index; - offset_count += 1; + if skin::skins_use_uniform_buffers(&render_device) { + dynamic_offsets[offset_count] = current_skin_index.byte_offset; + offset_count += 1; + } } if let Some(current_morph_index) = current_morph_index { dynamic_offsets[offset_count] = current_morph_index.index; @@ -2554,9 +2605,11 @@ impl RenderCommand

for SetMeshBindGroup { if has_motion_vector_prepass { // Attach the previous skin index for motion vector computation. If // there isn't one, just use zero as the shader will ignore it. - if current_skin_index.is_some() { + if current_skin_index.is_some() && skin::skins_use_uniform_buffers(&render_device) { match prev_skin_index { - Some(prev_skin_index) => dynamic_offsets[offset_count] = prev_skin_index.index, + Some(prev_skin_index) => { + dynamic_offsets[offset_count] = prev_skin_index.byte_offset; + } None => dynamic_offsets[offset_count] = 0, } offset_count += 1; diff --git a/crates/bevy_pbr/src/render/mesh.wgsl b/crates/bevy_pbr/src/render/mesh.wgsl index 3971a53902ef8..95684684f5140 100644 --- a/crates/bevy_pbr/src/render/mesh.wgsl +++ b/crates/bevy_pbr/src/render/mesh.wgsl @@ -44,7 +44,11 @@ fn vertex(vertex_no_morph: Vertex) -> VertexOutput { let mesh_world_from_local = mesh_functions::get_world_from_local(vertex_no_morph.instance_index); #ifdef SKINNED - var world_from_local = skinning::skin_model(vertex.joint_indices, vertex.joint_weights); + var world_from_local = skinning::skin_model( + vertex.joint_indices, + vertex.joint_weights, + vertex_no_morph.instance_index + ); #else // Use vertex_no_morph.instance_index instead of vertex.instance_index to work around a wgpu dx12 bug. // See https://github.com/gfx-rs/naga/issues/2416 . diff --git a/crates/bevy_pbr/src/render/mesh_bindings.rs b/crates/bevy_pbr/src/render/mesh_bindings.rs index cda05314fb180..cc1c5bec23bb7 100644 --- a/crates/bevy_pbr/src/render/mesh_bindings.rs +++ b/crates/bevy_pbr/src/render/mesh_bindings.rs @@ -8,6 +8,12 @@ use bevy_render::{ use crate::render::skin::MAX_JOINTS; const MORPH_WEIGHT_SIZE: usize = size_of::(); + +/// This is used to allocate buffers. +/// The correctness of the value depends on the GPU/platform. +/// The current value is chosen because it is guaranteed to work everywhere. +/// To allow for bigger values, a check must be made for the limits +/// of the GPU at runtime, which would mean not using consts anymore. pub const MORPH_BUFFER_SIZE: usize = MAX_MORPH_WEIGHTS * MORPH_WEIGHT_SIZE; const JOINT_SIZE: usize = size_of::(); @@ -16,10 +22,13 @@ pub(crate) const JOINT_BUFFER_SIZE: usize = MAX_JOINTS * JOINT_SIZE; /// Individual layout entries. mod layout_entry { use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; - use crate::MeshUniform; + use crate::{render::skin, MeshUniform}; use bevy_render::{ render_resource::{ - binding_types::{sampler, texture_2d, texture_3d, uniform_buffer_sized}, + binding_types::{ + sampler, storage_buffer_read_only_sized, texture_2d, texture_3d, + uniform_buffer_sized, + }, BindGroupLayoutEntryBuilder, BufferSize, GpuArrayBuffer, SamplerBindingType, ShaderStages, TextureSampleType, }, @@ -30,8 +39,15 @@ mod layout_entry { GpuArrayBuffer::::binding_layout(render_device) .visibility(ShaderStages::VERTEX_FRAGMENT) } - pub(super) fn skinning() -> BindGroupLayoutEntryBuilder { - uniform_buffer_sized(true, BufferSize::new(JOINT_BUFFER_SIZE as u64)) + pub(super) fn skinning(render_device: &RenderDevice) -> BindGroupLayoutEntryBuilder { + // If we can use storage buffers, do so. Otherwise, fall back to uniform + // buffers. + let size = BufferSize::new(JOINT_BUFFER_SIZE as u64); + if skin::skins_use_uniform_buffers(render_device) { + uniform_buffer_sized(true, size) + } else { + storage_buffer_read_only_sized(false, size) + } } pub(super) fn weights() -> BindGroupLayoutEntryBuilder { uniform_buffer_sized(true, BufferSize::new(MORPH_BUFFER_SIZE as u64)) @@ -50,29 +66,44 @@ mod layout_entry { /// Individual [`BindGroupEntry`] /// for bind groups. mod entry { + use crate::render::skin; + use super::{JOINT_BUFFER_SIZE, MORPH_BUFFER_SIZE}; - use bevy_render::render_resource::{ - BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, Sampler, TextureView, + use bevy_render::{ + render_resource::{ + BindGroupEntry, BindingResource, Buffer, BufferBinding, BufferSize, Sampler, + TextureView, + }, + renderer::RenderDevice, }; - fn entry(binding: u32, size: u64, buffer: &Buffer) -> BindGroupEntry { + fn entry(binding: u32, size: Option, buffer: &Buffer) -> BindGroupEntry { BindGroupEntry { binding, resource: BindingResource::Buffer(BufferBinding { buffer, offset: 0, - size: Some(BufferSize::new(size).unwrap()), + size: size.map(|size| BufferSize::new(size).unwrap()), }), } } pub(super) fn model(binding: u32, resource: BindingResource) -> BindGroupEntry { BindGroupEntry { binding, resource } } - pub(super) fn skinning(binding: u32, buffer: &Buffer) -> BindGroupEntry { - entry(binding, JOINT_BUFFER_SIZE as u64, buffer) + pub(super) fn skinning<'a>( + render_device: &RenderDevice, + binding: u32, + buffer: &'a Buffer, + ) -> BindGroupEntry<'a> { + let size = if skin::skins_use_uniform_buffers(render_device) { + Some(JOINT_BUFFER_SIZE as u64) + } else { + None + }; + entry(binding, size, buffer) } pub(super) fn weights(binding: u32, buffer: &Buffer) -> BindGroupEntry { - entry(binding, MORPH_BUFFER_SIZE as u64, buffer) + entry(binding, Some(MORPH_BUFFER_SIZE as u64), buffer) } pub(super) fn targets(binding: u32, texture: &TextureView) -> BindGroupEntry { BindGroupEntry { @@ -169,7 +200,7 @@ impl MeshLayouts { ( (0, layout_entry::model(render_device)), // The current frame's joint matrix buffer. - (1, layout_entry::skinning()), + (1, layout_entry::skinning(render_device)), ), ), ) @@ -185,9 +216,9 @@ impl MeshLayouts { ( (0, layout_entry::model(render_device)), // The current frame's joint matrix buffer. - (1, layout_entry::skinning()), + (1, layout_entry::skinning(render_device)), // The previous frame's joint matrix buffer. - (6, layout_entry::skinning()), + (6, layout_entry::skinning(render_device)), ), ), ) @@ -238,7 +269,7 @@ impl MeshLayouts { ( (0, layout_entry::model(render_device)), // The current frame's joint matrix buffer. - (1, layout_entry::skinning()), + (1, layout_entry::skinning(render_device)), // The current frame's morph weight buffer. (2, layout_entry::weights()), (3, layout_entry::targets()), @@ -257,12 +288,12 @@ impl MeshLayouts { ( (0, layout_entry::model(render_device)), // The current frame's joint matrix buffer. - (1, layout_entry::skinning()), + (1, layout_entry::skinning(render_device)), // The current frame's morph weight buffer. (2, layout_entry::weights()), (3, layout_entry::targets()), // The previous frame's joint matrix buffer. - (6, layout_entry::skinning()), + (6, layout_entry::skinning(render_device)), // The previous frame's morph weight buffer. (7, layout_entry::weights()), ), @@ -323,7 +354,7 @@ impl MeshLayouts { &self.skinned, &[ entry::model(0, model.clone()), - entry::skinning(1, current_skin), + entry::skinning(render_device, 1, current_skin), ], ) } @@ -347,8 +378,8 @@ impl MeshLayouts { &self.skinned_motion, &[ entry::model(0, model.clone()), - entry::skinning(1, current_skin), - entry::skinning(6, prev_skin), + entry::skinning(render_device, 1, current_skin), + entry::skinning(render_device, 6, prev_skin), ], ) } @@ -414,7 +445,7 @@ impl MeshLayouts { &self.morphed_skinned, &[ entry::model(0, model.clone()), - entry::skinning(1, current_skin), + entry::skinning(render_device, 1, current_skin), entry::weights(2, current_weights), entry::targets(3, targets), ], @@ -444,10 +475,10 @@ impl MeshLayouts { &self.morphed_skinned_motion, &[ entry::model(0, model.clone()), - entry::skinning(1, current_skin), + entry::skinning(render_device, 1, current_skin), entry::weights(2, current_weights), entry::targets(3, targets), - entry::skinning(6, prev_skin), + entry::skinning(render_device, 6, prev_skin), entry::weights(7, prev_weights), ], ) diff --git a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl index 5132691930cad..3300c4d925d6b 100644 --- a/crates/bevy_pbr/src/render/mesh_preprocess.wgsl +++ b/crates/bevy_pbr/src/render/mesh_preprocess.wgsl @@ -23,10 +23,10 @@ struct MeshInput { // applicable. If not present, this is `u32::MAX`. previous_input_index: u32, first_vertex_index: u32, + current_skin_index: u32, + previous_skin_index: u32, // Index of the material inside the bind group data. material_bind_group_slot: u32, - pad_a: u32, - pad_b: u32, } // Information about each mesh instance needed to cull it on GPU. @@ -192,6 +192,8 @@ fn main(@builtin(global_invocation_id) global_invocation_id: vec3) { output[mesh_output_index].flags = current_input[input_index].flags; output[mesh_output_index].lightmap_uv_rect = current_input[input_index].lightmap_uv_rect; output[mesh_output_index].first_vertex_index = current_input[input_index].first_vertex_index; + output[mesh_output_index].current_skin_index = current_input[input_index].current_skin_index; + output[mesh_output_index].previous_skin_index = current_input[input_index].previous_skin_index; output[mesh_output_index].material_bind_group_slot = current_input[input_index].material_bind_group_slot; } diff --git a/crates/bevy_pbr/src/render/mesh_types.wgsl b/crates/bevy_pbr/src/render/mesh_types.wgsl index 7cf8cdf7ed512..f94074d813465 100644 --- a/crates/bevy_pbr/src/render/mesh_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_types.wgsl @@ -17,10 +17,10 @@ struct Mesh { lightmap_uv_rect: vec2, // The index of the mesh's first vertex in the vertex buffer. first_vertex_index: u32, + current_skin_index: u32, + previous_skin_index: u32, // Index of the material inside the bind group data. material_bind_group_slot: u32, - pad_a: u32, - pad_b: u32, }; #ifdef SKINNED diff --git a/crates/bevy_pbr/src/render/mod.rs b/crates/bevy_pbr/src/render/mod.rs index 2a69e28bf3a44..8e26e869a1c96 100644 --- a/crates/bevy_pbr/src/render/mod.rs +++ b/crates/bevy_pbr/src/render/mod.rs @@ -5,7 +5,7 @@ pub(crate) mod mesh; mod mesh_bindings; mod mesh_view_bindings; mod morph; -mod skin; +pub(crate) mod skin; pub use fog::*; pub use gpu_preprocess::*; diff --git a/crates/bevy_pbr/src/render/parallax_mapping.wgsl b/crates/bevy_pbr/src/render/parallax_mapping.wgsl index a375c83bafc23..d6fe4f9bbec95 100644 --- a/crates/bevy_pbr/src/render/parallax_mapping.wgsl +++ b/crates/bevy_pbr/src/render/parallax_mapping.wgsl @@ -1,8 +1,12 @@ #define_import_path bevy_pbr::parallax_mapping -#import bevy_pbr::pbr_bindings::{depth_map_texture, depth_map_sampler} +#import bevy_pbr::{ + pbr_bindings::{depth_map_texture, depth_map_sampler}, + mesh_bindings::mesh +} -fn sample_depth_map(uv: vec2) -> f32 { +fn sample_depth_map(uv: vec2, instance_index: u32) -> f32 { + let slot = mesh[instance_index].material_bind_group_slot; // We use `textureSampleLevel` over `textureSample` because the wgpu DX12 // backend (Fxc) panics when using "gradient instructions" inside a loop. // It results in the whole loop being unrolled by the shader compiler, @@ -13,7 +17,17 @@ fn sample_depth_map(uv: vec2) -> f32 { // the MIP level, so no gradient instructions are used, and we can use // sample_depth_map in our loop. // See https://stackoverflow.com/questions/56581141/direct3d11-gradient-instruction-used-in-a-loop-with-varying-iteration-forcing - return textureSampleLevel(depth_map_texture, depth_map_sampler, uv, 0.0).r; + return textureSampleLevel( +#ifdef BINDLESS + depth_map_texture[slot], + depth_map_sampler[slot], +#else // BINDLESS + depth_map_texture, + depth_map_sampler, +#endif // BINDLESS + uv, + 0.0 + ).r; } // An implementation of parallax mapping, see https://en.wikipedia.org/wiki/Parallax_mapping @@ -26,6 +40,7 @@ fn parallaxed_uv( original_uv: vec2, // The vector from the camera to the fragment at the surface in tangent space Vt: vec3, + instance_index: u32, ) -> vec2 { if max_layer_count < 1.0 { return original_uv; @@ -53,7 +68,7 @@ fn parallaxed_uv( var delta_uv = depth_scale * layer_depth * Vt.xy * vec2(1.0, -1.0) / view_steepness; var current_layer_depth = 0.0; - var texture_depth = sample_depth_map(uv); + var texture_depth = sample_depth_map(uv, instance_index); // texture_depth > current_layer_depth means the depth map depth is deeper // than the depth the ray would be at this UV offset so the ray has not @@ -61,7 +76,7 @@ fn parallaxed_uv( for (var i: i32 = 0; texture_depth > current_layer_depth && i <= i32(layer_count); i++) { current_layer_depth += layer_depth; uv += delta_uv; - texture_depth = sample_depth_map(uv); + texture_depth = sample_depth_map(uv, instance_index); } #ifdef RELIEF_MAPPING @@ -79,7 +94,7 @@ fn parallaxed_uv( current_layer_depth -= delta_depth; for (var i: u32 = 0u; i < max_steps; i++) { - texture_depth = sample_depth_map(uv); + texture_depth = sample_depth_map(uv, instance_index); // Halve the deltas for the next step delta_uv *= 0.5; @@ -103,7 +118,7 @@ fn parallaxed_uv( // may skip small details and result in writhing material artifacts. let previous_uv = uv - delta_uv; let next_depth = texture_depth - current_layer_depth; - let previous_depth = sample_depth_map(previous_uv) - current_layer_depth + layer_depth; + let previous_depth = sample_depth_map(previous_uv, instance_index) - current_layer_depth + layer_depth; let weight = next_depth / (next_depth - previous_depth); diff --git a/crates/bevy_pbr/src/render/pbr_bindings.wgsl b/crates/bevy_pbr/src/render/pbr_bindings.wgsl index 731fba343f84a..9b9d9dcc92825 100644 --- a/crates/bevy_pbr/src/render/pbr_bindings.wgsl +++ b/crates/bevy_pbr/src/render/pbr_bindings.wgsl @@ -2,6 +2,21 @@ #import bevy_pbr::pbr_types::StandardMaterial +#ifdef BINDLESS +@group(2) @binding(0) var material: binding_array; +@group(2) @binding(1) var base_color_texture: binding_array, 16>; +@group(2) @binding(2) var base_color_sampler: binding_array; +@group(2) @binding(3) var emissive_texture: binding_array, 16>; +@group(2) @binding(4) var emissive_sampler: binding_array; +@group(2) @binding(5) var metallic_roughness_texture: binding_array, 16>; +@group(2) @binding(6) var metallic_roughness_sampler: binding_array; +@group(2) @binding(7) var occlusion_texture: binding_array, 16>; +@group(2) @binding(8) var occlusion_sampler: binding_array; +@group(2) @binding(9) var normal_map_texture: binding_array, 16>; +@group(2) @binding(10) var normal_map_sampler: binding_array; +@group(2) @binding(11) var depth_map_texture: binding_array, 16>; +@group(2) @binding(12) var depth_map_sampler: binding_array; +#else // BINDLESS @group(2) @binding(0) var material: StandardMaterial; @group(2) @binding(1) var base_color_texture: texture_2d; @group(2) @binding(2) var base_color_sampler: sampler; @@ -15,23 +30,50 @@ @group(2) @binding(10) var normal_map_sampler: sampler; @group(2) @binding(11) var depth_map_texture: texture_2d; @group(2) @binding(12) var depth_map_sampler: sampler; +#endif // BINDLESS + #ifdef PBR_ANISOTROPY_TEXTURE_SUPPORTED +#ifdef BINDLESS +@group(2) @binding(13) var anisotropy_texture: binding_array, 16>; +@group(2) @binding(14) var anisotropy_sampler: binding_array; +#else // BINDLESS @group(2) @binding(13) var anisotropy_texture: texture_2d; @group(2) @binding(14) var anisotropy_sampler: sampler; -#endif +#endif // BINDLESS +#endif // PBR_ANISOTROPY_TEXTURE_SUPPORTED + #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED +#ifdef BINDLESS +@group(2) @binding(15) var specular_transmission_texture: binding_array, 16>; +@group(2) @binding(16) var specular_transmission_sampler: binding_array; +@group(2) @binding(17) var thickness_texture: binding_array, 16>; +@group(2) @binding(18) var thickness_sampler: binding_array; +@group(2) @binding(19) var diffuse_transmission_texture: binding_array, 16>; +@group(2) @binding(20) var diffuse_transmission_sampler: binding_array; +#else // BINDLESS @group(2) @binding(15) var specular_transmission_texture: texture_2d; @group(2) @binding(16) var specular_transmission_sampler: sampler; @group(2) @binding(17) var thickness_texture: texture_2d; @group(2) @binding(18) var thickness_sampler: sampler; @group(2) @binding(19) var diffuse_transmission_texture: texture_2d; @group(2) @binding(20) var diffuse_transmission_sampler: sampler; -#endif +#endif // BINDLESS +#endif // PBR_TRANSMISSION_TEXTURES_SUPPORTED + #ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED +#ifdef BINDLESS +@group(2) @binding(21) var clearcoat_texture: binding_array, 16>; +@group(2) @binding(22) var clearcoat_sampler: binding_array; +@group(2) @binding(23) var clearcoat_roughness_texture: binding_array, 16>; +@group(2) @binding(24) var clearcoat_roughness_sampler: binding_array; +@group(2) @binding(25) var clearcoat_normal_texture: binding_array, 16>; +@group(2) @binding(26) var clearcoat_normal_sampler: binding_array; +#else // BINDLESS @group(2) @binding(21) var clearcoat_texture: texture_2d; @group(2) @binding(22) var clearcoat_sampler: sampler; @group(2) @binding(23) var clearcoat_roughness_texture: texture_2d; @group(2) @binding(24) var clearcoat_roughness_sampler: sampler; @group(2) @binding(25) var clearcoat_normal_texture: texture_2d; @group(2) @binding(26) var clearcoat_normal_sampler: sampler; -#endif +#endif // BINDLESS +#endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED diff --git a/crates/bevy_pbr/src/render/pbr_fragment.wgsl b/crates/bevy_pbr/src/render/pbr_fragment.wgsl index 91e104ede525f..7bdc8632ec334 100644 --- a/crates/bevy_pbr/src/render/pbr_fragment.wgsl +++ b/crates/bevy_pbr/src/render/pbr_fragment.wgsl @@ -70,12 +70,23 @@ fn pbr_input_from_standard_material( in: VertexOutput, is_front: bool, ) -> pbr_types::PbrInput { - let double_sided = (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u; +#ifdef BINDLESS + let slot = mesh[in.instance_index].material_bind_group_slot; + let flags = pbr_bindings::material[slot].flags; + let base_color = pbr_bindings::material[slot].base_color; + let deferred_lighting_pass_id = pbr_bindings::material[slot].deferred_lighting_pass_id; +#else // BINDLESS + let flags = pbr_bindings::material.flags; + let base_color = pbr_bindings::material.base_color; + let deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; +#endif + + let double_sided = (flags & pbr_types::STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT) != 0u; var pbr_input: pbr_types::PbrInput = pbr_input_from_vertex_output(in, is_front, double_sided); - pbr_input.material.flags = pbr_bindings::material.flags; - pbr_input.material.base_color *= pbr_bindings::material.base_color; - pbr_input.material.deferred_lighting_pass_id = pbr_bindings::material.deferred_lighting_pass_id; + pbr_input.material.flags = flags; + pbr_input.material.base_color *= base_color; + pbr_input.material.deferred_lighting_pass_id = deferred_lighting_pass_id; // Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886" let NdotV = max(dot(pbr_input.N, pbr_input.V), 0.0001); @@ -91,7 +102,13 @@ fn pbr_input_from_standard_material( // TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass #ifdef VERTEX_UVS + +#ifdef BINDLESS + let uv_transform = pbr_bindings::material[slot].uv_transform; +#else // BINDLESS let uv_transform = pbr_bindings::material.uv_transform; +#endif // BINDLESS + #ifdef VERTEX_UVS_A var uv = (uv_transform * vec3(in.uv, 1.0)).xy; #endif @@ -104,7 +121,7 @@ fn pbr_input_from_standard_material( #endif #ifdef VERTEX_TANGENTS - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) { + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_DEPTH_MAP_BIT) != 0u) { let V = pbr_input.V; let TBN = pbr_functions::calculate_tbn_mikktspace(in.world_normal, in.world_tangent); let T = TBN[0]; @@ -115,28 +132,42 @@ fn pbr_input_from_standard_material( #ifdef VERTEX_UVS_A // TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass uv = parallaxed_uv( +#ifdef BINDLESS + pbr_bindings::material[slot].parallax_depth_scale, + pbr_bindings::material[slot].max_parallax_layer_count, + pbr_bindings::material[slot].max_relief_mapping_search_steps, +#else // BINDLESS pbr_bindings::material.parallax_depth_scale, pbr_bindings::material.max_parallax_layer_count, pbr_bindings::material.max_relief_mapping_search_steps, +#endif // BINDLESS uv, // Flip the direction of Vt to go toward the surface to make the // parallax mapping algorithm easier to understand and reason // about. -Vt, + in.instance_index, ); #endif #ifdef VERTEX_UVS_B // TODO: Transforming UVs mean we need to apply derivative chain rule for meshlet mesh material pass uv_b = parallaxed_uv( +#ifdef BINDLESS + pbr_bindings::material[slot].parallax_depth_scale, + pbr_bindings::material[slot].max_parallax_layer_count, + pbr_bindings::material[slot].max_relief_mapping_search_steps, +#else // BINDLESS pbr_bindings::material.parallax_depth_scale, pbr_bindings::material.max_parallax_layer_count, pbr_bindings::material.max_relief_mapping_search_steps, +#endif // BINDLESS uv_b, // Flip the direction of Vt to go toward the surface to make the // parallax mapping algorithm easier to understand and reason // about. -Vt, + in.instance_index, ); #else uv_b = uv; @@ -144,27 +175,47 @@ fn pbr_input_from_standard_material( } #endif // VERTEX_TANGENTS - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { - pbr_input.material.base_color *= pbr_functions::sample_texture( - pbr_bindings::base_color_texture, - pbr_bindings::base_color_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u) { + pbr_input.material.base_color *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::base_color_texture[slot], + pbr_bindings::base_color_sampler[slot], +#else // BINDLESS + pbr_bindings::base_color_texture, + pbr_bindings::base_color_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_BASE_COLOR_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS ); #ifdef ALPHA_TO_COVERAGE // Sharpen alpha edges. // // https://bgolus.medium.com/anti-aliased-alpha-test-the-esoteric-alpha-to-coverage-8b177335ae4f - let alpha_mode = pbr_bindings::material.flags & - pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_ALPHA_TO_COVERAGE { - pbr_input.material.base_color.a = (pbr_input.material.base_color.a - - pbr_bindings::material.alpha_cutoff) / + +#ifdef BINDLESS + let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; +#else // BINDLESS + let alpha_cutoff = pbr_bindings::material.alpha_cutoff; +#endif // BINDLESS + + pbr_input.material.base_color.a = (pbr_input.material.base_color.a - alpha_cutoff) / max(fwidth(pbr_input.material.base_color.a), 0.0001) + 0.5; } #endif // ALPHA_TO_COVERAGE @@ -172,50 +223,100 @@ fn pbr_input_from_standard_material( } #endif // VERTEX_UVS - pbr_input.material.flags = pbr_bindings::material.flags; + pbr_input.material.flags = flags; // NOTE: Unlit bit not set means == 0 is true, so the true case is if lit - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_UNLIT_BIT) == 0u) { +#ifdef BINDLESS + pbr_input.material.reflectance = pbr_bindings::material[slot].reflectance; + pbr_input.material.ior = pbr_bindings::material[slot].ior; + pbr_input.material.attenuation_color = pbr_bindings::material[slot].attenuation_color; + pbr_input.material.attenuation_distance = pbr_bindings::material[slot].attenuation_distance; + pbr_input.material.alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; +#else // BINDLESS pbr_input.material.reflectance = pbr_bindings::material.reflectance; pbr_input.material.ior = pbr_bindings::material.ior; pbr_input.material.attenuation_color = pbr_bindings::material.attenuation_color; pbr_input.material.attenuation_distance = pbr_bindings::material.attenuation_distance; pbr_input.material.alpha_cutoff = pbr_bindings::material.alpha_cutoff; +#endif // BINDLESS // emissive +#ifdef BINDLESS + var emissive: vec4 = pbr_bindings::material[slot].emissive; +#else // BINDLESS var emissive: vec4 = pbr_bindings::material.emissive; +#endif // BINDLESS + #ifdef VERTEX_UVS - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { - emissive = vec4(emissive.rgb * pbr_functions::sample_texture( - pbr_bindings::emissive_texture, - pbr_bindings::emissive_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_EMISSIVE_TEXTURE_BIT) != 0u) { + emissive = vec4(emissive.rgb * +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::emissive_texture[slot], + pbr_bindings::emissive_sampler[slot], +#else // BINDLESS + pbr_bindings::emissive_texture, + pbr_bindings::emissive_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_EMISSIVE_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).rgb, emissive.a); +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).rgb, + emissive.a); } #endif pbr_input.material.emissive = emissive; // metallic and perceptual roughness +#ifdef BINDLESS + var metallic: f32 = pbr_bindings::material[slot].metallic; + var perceptual_roughness: f32 = pbr_bindings::material[slot].perceptual_roughness; +#else // BINDLESS var metallic: f32 = pbr_bindings::material.metallic; var perceptual_roughness: f32 = pbr_bindings::material.perceptual_roughness; +#endif // BINDLESS + let roughness = lighting::perceptualRoughnessToRoughness(perceptual_roughness); #ifdef VERTEX_UVS - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { - let metallic_roughness = pbr_functions::sample_texture( - pbr_bindings::metallic_roughness_texture, - pbr_bindings::metallic_roughness_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_METALLIC_ROUGHNESS_TEXTURE_BIT) != 0u) { + let metallic_roughness = +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::metallic_roughness_texture[slot], + pbr_bindings::metallic_roughness_sampler[slot], +#else // BINDLESS + pbr_bindings::metallic_roughness_texture, + pbr_bindings::metallic_roughness_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_METALLIC_ROUGHNESS_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ); +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ); // Sampling from GLTF standard channels for now metallic *= metallic_roughness.b; perceptual_roughness *= metallic_roughness.g; @@ -225,76 +326,158 @@ fn pbr_input_from_standard_material( pbr_input.material.perceptual_roughness = perceptual_roughness; // Clearcoat factor +#ifdef BINDLESS + pbr_input.material.clearcoat = pbr_bindings::material[slot].clearcoat; +#else // BINDLESS pbr_input.material.clearcoat = pbr_bindings::material.clearcoat; +#endif // BINDLESS + #ifdef VERTEX_UVS #ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_TEXTURE_BIT) != 0u) { - pbr_input.material.clearcoat *= pbr_functions::sample_texture( - pbr_bindings::clearcoat_texture, - pbr_bindings::clearcoat_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_TEXTURE_BIT) != 0u) { + pbr_input.material.clearcoat *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::clearcoat_texture[slot], + pbr_bindings::clearcoat_sampler[slot], +#else // BINDLESS + pbr_bindings::clearcoat_texture, + pbr_bindings::clearcoat_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_CLEARCOAT_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).r; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).r; } #endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED #endif // VERTEX_UVS // Clearcoat roughness - pbr_input.material.clearcoat_perceptual_roughness = pbr_bindings::material.clearcoat_perceptual_roughness; +#ifdef BINDLESS + pbr_input.material.clearcoat_perceptual_roughness = + pbr_bindings::material[slot].clearcoat_perceptual_roughness; +#else // BINDLESS + pbr_input.material.clearcoat_perceptual_roughness = + pbr_bindings::material.clearcoat_perceptual_roughness; +#endif // BINDLESS + #ifdef VERTEX_UVS #ifdef PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_ROUGHNESS_TEXTURE_BIT) != 0u) { - pbr_input.material.clearcoat_perceptual_roughness *= pbr_functions::sample_texture( - pbr_bindings::clearcoat_roughness_texture, - pbr_bindings::clearcoat_roughness_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_CLEARCOAT_ROUGHNESS_TEXTURE_BIT) != 0u) { + pbr_input.material.clearcoat_perceptual_roughness *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::clearcoat_roughness_texture[slot], + pbr_bindings::clearcoat_roughness_sampler[slot], +#else // BINDLESS + pbr_bindings::clearcoat_roughness_texture, + pbr_bindings::clearcoat_roughness_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_CLEARCOAT_ROUGHNESS_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).g; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).g; } #endif // PBR_MULTI_LAYER_MATERIAL_TEXTURES_SUPPORTED #endif // VERTEX_UVS +#ifdef BINDLESS + var specular_transmission: f32 = pbr_bindings::material[slot].specular_transmission; +#else // BINDLESS var specular_transmission: f32 = pbr_bindings::material.specular_transmission; +#endif // BINDLESS + #ifdef VERTEX_UVS #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT) != 0u) { - specular_transmission *= pbr_functions::sample_texture( - pbr_bindings::specular_transmission_texture, - pbr_bindings::specular_transmission_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_SPECULAR_TRANSMISSION_TEXTURE_BIT) != 0u) { + specular_transmission *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::specular_transmission_texture[slot], + pbr_bindings::specular_transmission_sampler[slot], +#else // BINDLESS + pbr_bindings::specular_transmission_texture, + pbr_bindings::specular_transmission_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_SPECULAR_TRANSMISSION_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).r; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).r; } #endif #endif pbr_input.material.specular_transmission = specular_transmission; +#ifdef BINDLESS + var thickness: f32 = pbr_bindings::material[slot].thickness; +#else // BINDLESS var thickness: f32 = pbr_bindings::material.thickness; +#endif // BINDLESS + #ifdef VERTEX_UVS #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT) != 0u) { - thickness *= pbr_functions::sample_texture( - pbr_bindings::thickness_texture, - pbr_bindings::thickness_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_THICKNESS_TEXTURE_BIT) != 0u) { + thickness *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::thickness_texture[slot], + pbr_bindings::thickness_sampler[slot], +#else // BINDLESS + pbr_bindings::thickness_texture, + pbr_bindings::thickness_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_THICKNESS_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).g; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).g; } #endif #endif @@ -307,20 +490,40 @@ fn pbr_input_from_standard_material( #endif pbr_input.material.thickness = thickness; +#ifdef BINDLESS + var diffuse_transmission = pbr_bindings::material[slot].diffuse_transmission; +#else // BINDLESS var diffuse_transmission = pbr_bindings::material.diffuse_transmission; +#endif // BINDLESS + #ifdef VERTEX_UVS #ifdef PBR_TRANSMISSION_TEXTURES_SUPPORTED - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT) != 0u) { - diffuse_transmission *= pbr_functions::sample_texture( - pbr_bindings::diffuse_transmission_texture, - pbr_bindings::diffuse_transmission_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_DIFFUSE_TRANSMISSION_TEXTURE_BIT) != 0u) { + diffuse_transmission *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::diffuse_transmission_texture[slot], + pbr_bindings::diffuse_transmission_sampler[slot], +#else // BINDLESS + pbr_bindings::diffuse_transmission_texture, + pbr_bindings::diffuse_transmission_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).a; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).a; } #endif #endif @@ -329,17 +532,32 @@ fn pbr_input_from_standard_material( var diffuse_occlusion: vec3 = vec3(1.0); var specular_occlusion: f32 = 1.0; #ifdef VERTEX_UVS - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { - diffuse_occlusion *= pbr_functions::sample_texture( - pbr_bindings::occlusion_texture, - pbr_bindings::occlusion_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_OCCLUSION_TEXTURE_BIT) != 0u) { + diffuse_occlusion *= +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::occlusion_texture[slot], + pbr_bindings::occlusion_sampler[slot], +#else // BINDLESS + pbr_bindings::occlusion_texture, + pbr_bindings::occlusion_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_OCCLUSION_UV_B - uv_b, + uv_b, #else - uv, + uv, #endif - bias, - ).r; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).r; } #endif #ifdef SCREEN_SPACE_AMBIENT_OCCLUSION @@ -366,24 +584,33 @@ fn pbr_input_from_standard_material( #ifdef STANDARD_MATERIAL_NORMAL_MAP - let Nt = pbr_functions::sample_texture( - pbr_bindings::normal_map_texture, - pbr_bindings::normal_map_sampler, + let Nt = +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::normal_map_texture[slot], + pbr_bindings::normal_map_sampler[slot], +#else // BINDLESS + pbr_bindings::normal_map_texture, + pbr_bindings::normal_map_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_NORMAL_MAP_UV_B uv_b, #else uv, #endif - bias, - ).rgb; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).rgb; - pbr_input.N = pbr_functions::apply_normal_mapping( - pbr_bindings::material.flags, - TBN, - double_sided, - is_front, - Nt, - ); + pbr_input.N = pbr_functions::apply_normal_mapping(flags, TBN, double_sided, is_front, Nt); #endif // STANDARD_MATERIAL_NORMAL_MAP @@ -395,19 +622,34 @@ fn pbr_input_from_standard_material( #ifdef STANDARD_MATERIAL_CLEARCOAT_NORMAL_MAP - let clearcoat_Nt = pbr_functions::sample_texture( - pbr_bindings::clearcoat_normal_texture, - pbr_bindings::clearcoat_normal_sampler, + let clearcoat_Nt = +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::clearcoat_normal_texture[slot], + pbr_bindings::clearcoat_normal_sampler[slot], +#else // BINDLESS + pbr_bindings::clearcoat_normal_texture, + pbr_bindings::clearcoat_normal_sampler, +#endif // BINDLESS #ifdef STANDARD_MATERIAL_CLEARCOAT_NORMAL_UV_B uv_b, #else uv, #endif - bias, - ).rgb; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).rgb; pbr_input.clearcoat_N = pbr_functions::apply_normal_mapping( - pbr_bindings::material.flags, + flags, TBN, double_sided, is_front, @@ -429,21 +671,41 @@ fn pbr_input_from_standard_material( #ifdef VERTEX_TANGENTS #ifdef STANDARD_MATERIAL_ANISOTROPY +#ifdef BINDLESS + var anisotropy_strength = pbr_bindings::material[slot].anisotropy_strength; + var anisotropy_direction = pbr_bindings::material[slot].anisotropy_rotation; +#else // BINDLESS var anisotropy_strength = pbr_bindings::material.anisotropy_strength; var anisotropy_direction = pbr_bindings::material.anisotropy_rotation; +#endif // BINDLESS // Adjust based on the anisotropy map if there is one. - if ((pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ANISOTROPY_TEXTURE_BIT) != 0u) { - let anisotropy_texel = pbr_functions::sample_texture( - pbr_bindings::anisotropy_texture, - pbr_bindings::anisotropy_sampler, + if ((flags & pbr_types::STANDARD_MATERIAL_FLAGS_ANISOTROPY_TEXTURE_BIT) != 0u) { + let anisotropy_texel = +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::anisotropy_texture[slot], + pbr_bindings::anisotropy_sampler[slot], +#else // BINDLESS + pbr_bindings::anisotropy_texture, + pbr_bindings::anisotropy_sampler, +#endif #ifdef STANDARD_MATERIAL_ANISOTROPY_UV_B - uv_b, + uv_b, #else // STANDARD_MATERIAL_ANISOTROPY_UV_B - uv, + uv, #endif // STANDARD_MATERIAL_ANISOTROPY_UV_B - bias, - ).rgb; +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).rgb; let anisotropy_direction_from_texture = normalize(anisotropy_texel.rg * 2.0 - 1.0); // Rotate by the anisotropy direction. @@ -468,10 +730,14 @@ fn pbr_input_from_standard_material( // TODO: Meshlet support #ifdef LIGHTMAP - pbr_input.lightmap_light = lightmap( - in.uv_b, - pbr_bindings::material.lightmap_exposure, - in.instance_index); + +#ifdef BINDLESS + let lightmap_exposure = pbr_bindings::material[slot].lightmap_exposure; +#else // BINDLESS + let lightmap_exposure = pbr_bindings::material.lightmap_exposure; +#endif // BINDLESS + + pbr_input.lightmap_light = lightmap(in.uv_b, lightmap_exposure, in.instance_index); #endif } diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 093af38e591a8..1983257199577 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -121,21 +121,6 @@ fn alpha_discard(material: pbr_types::StandardMaterial, output_color: vec4) return color; } -// Samples a texture using the appropriate biasing metric for the type of mesh -// in use (mesh vs. meshlet). -fn sample_texture( - texture: texture_2d, - samp: sampler, - uv: vec2, - bias: SampleBias, -) -> vec4 { -#ifdef MESHLET_MESH_MATERIAL_PASS - return textureSampleGrad(texture, samp, uv, bias.ddx_uv, bias.ddy_uv); -#else - return textureSampleBias(texture, samp, uv, bias.mip_bias); -#endif -} - fn prepare_world_normal( world_normal: vec3, double_sided: bool, diff --git a/crates/bevy_pbr/src/render/pbr_prepass.wgsl b/crates/bevy_pbr/src/render/pbr_prepass.wgsl index a2758ce6981f7..8acf21d4aaf15 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass.wgsl @@ -76,12 +76,27 @@ fn fragment( bias.mip_bias = view.mip_bias; #endif // MESHLET_MESH_MATERIAL_PASS - let Nt = pbr_functions::sample_texture( - pbr_bindings::normal_map_texture, - pbr_bindings::normal_map_sampler, - uv, - bias, - ).rgb; + let Nt = +#ifdef MESHLET_MESH_MATERIAL_PASS + textureSampleGrad( +#else // MESHLET_MESH_MATERIAL_PASS + textureSampleBias( +#endif // MESHLET_MESH_MATERIAL_PASS +#ifdef BINDLESS + pbr_bindings::normal_map_texture[slot], + pbr_bindings::normal_map_sampler[slot], +#else // BINDLESS + pbr_bindings::normal_map_texture, + pbr_bindings::normal_map_sampler, +#endif // BINDLESS + uv, +#ifdef MESHLET_MESH_MATERIAL_PASS + bias.ddx_uv, + bias.ddy_uv, +#else // MESHLET_MESH_MATERIAL_PASS + bias.mip_bias, +#endif // MESHLET_MESH_MATERIAL_PASS + ).rgb; let TBN = pbr_functions::calculate_tbn_mikktspace(normal, in.world_tangent); normal = pbr_functions::apply_normal_mapping( diff --git a/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl index abdaccbd303f6..493e56f27781d 100644 --- a/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_prepass_functions.wgsl @@ -3,6 +3,7 @@ #import bevy_pbr::{ prepass_io::VertexOutput, prepass_bindings::previous_view_uniforms, + mesh_bindings::mesh, mesh_view_bindings::view, pbr_bindings, pbr_types, @@ -15,7 +16,12 @@ const PREMULTIPLIED_ALPHA_CUTOFF = 0.05; fn prepass_alpha_discard(in: VertexOutput) { #ifdef MAY_DISCARD +#ifdef BINDLESS + let slot = mesh[in.instance_index].material_bind_group_slot; + var output_color: vec4 = pbr_bindings::material[slot].base_color; +#else // BINDLESS var output_color: vec4 = pbr_bindings::material.base_color; +#endif // BINDLESS #ifdef VERTEX_UVS #ifdef STANDARD_MATERIAL_BASE_COLOR_UV_B @@ -24,16 +30,38 @@ fn prepass_alpha_discard(in: VertexOutput) { var uv = in.uv; #endif // STANDARD_MATERIAL_BASE_COLOR_UV_B +#ifdef BINDLESS + let uv_transform = pbr_bindings::material[slot].uv_transform; + let flags = pbr_bindings::material[slot].flags; +#else // BINDLESS let uv_transform = pbr_bindings::material.uv_transform; + let flags = pbr_bindings::material.flags; +#endif // BINDLESS + uv = (uv_transform * vec3(uv, 1.0)).xy; - if (pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { - output_color = output_color * textureSampleBias(pbr_bindings::base_color_texture, pbr_bindings::base_color_sampler, uv, view.mip_bias); + if (flags & pbr_types::STANDARD_MATERIAL_FLAGS_BASE_COLOR_TEXTURE_BIT) != 0u { + output_color = output_color * textureSampleBias( +#ifdef BINDLESS + pbr_bindings::base_color_texture[slot], + pbr_bindings::base_color_sampler[slot], +#else // BINDLESS + pbr_bindings::base_color_texture, + pbr_bindings::base_color_sampler, +#endif // BINDLESS + uv, + view.mip_bias + ); } #endif // VERTEX_UVS - let alpha_mode = pbr_bindings::material.flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; + let alpha_mode = flags & pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS; if alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK { - if output_color.a < pbr_bindings::material.alpha_cutoff { +#ifdef BINDLESS + let alpha_cutoff = pbr_bindings::material[slot].alpha_cutoff; +#else // BINDLESS + let alpha_cutoff = pbr_bindings::material.alpha_cutoff; +#endif // BINDLESS + if output_color.a < alpha_cutoff { discard; } } else if (alpha_mode == pbr_types::STANDARD_MATERIAL_FLAGS_ALPHA_MODE_BLEND || diff --git a/crates/bevy_pbr/src/render/skin.rs b/crates/bevy_pbr/src/render/skin.rs index b6f35fc0bf49d..c248821ccafd3 100644 --- a/crates/bevy_pbr/src/render/skin.rs +++ b/crates/bevy_pbr/src/render/skin.rs @@ -1,4 +1,5 @@ use core::mem::{self, size_of}; +use std::sync::OnceLock; use bevy_asset::Assets; use bevy_ecs::prelude::*; @@ -15,20 +16,35 @@ use bevy_render::{ use bevy_transform::prelude::GlobalTransform; /// Maximum number of joints supported for skinned meshes. +/// +/// It is used to allocate buffers. +/// The correctness of the value depends on the GPU/platform. +/// The current value is chosen because it is guaranteed to work everywhere. +/// To allow for bigger values, a check must be made for the limits +/// of the GPU at runtime, which would mean not using consts anymore. pub const MAX_JOINTS: usize = 256; +/// The location of the first joint matrix in the skin uniform buffer. #[derive(Component)] pub struct SkinIndex { - pub index: u32, + /// The byte offset of the first joint matrix. + pub byte_offset: u32, } impl SkinIndex { /// Index to be in address space based on the size of a skin uniform. const fn new(start: usize) -> Self { SkinIndex { - index: (start * size_of::()) as u32, + byte_offset: (start * size_of::()) as u32, } } + + /// Returns this skin index in elements (not bytes). + /// + /// Each element is a 4x4 matrix. + pub fn index(&self) -> u32 { + self.byte_offset / size_of::() as u32 + } } /// Maps each skinned mesh to the applicable offset within the [`SkinUniforms`] @@ -64,15 +80,30 @@ pub struct SkinUniforms { pub prev_buffer: RawBufferVec, } -impl Default for SkinUniforms { - fn default() -> Self { +impl FromWorld for SkinUniforms { + fn from_world(world: &mut World) -> Self { + let device = world.resource::(); + let buffer_usages = if skins_use_uniform_buffers(device) { + BufferUsages::UNIFORM + } else { + BufferUsages::STORAGE + }; + Self { - current_buffer: RawBufferVec::new(BufferUsages::UNIFORM), - prev_buffer: RawBufferVec::new(BufferUsages::UNIFORM), + current_buffer: RawBufferVec::new(buffer_usages), + prev_buffer: RawBufferVec::new(buffer_usages), } } } +/// Returns true if skinning must use uniforms (and dynamic offsets) because +/// storage buffers aren't supported on the current platform. +pub fn skins_use_uniform_buffers(render_device: &RenderDevice) -> bool { + static SKINS_USE_UNIFORM_BUFFERS: OnceLock = OnceLock::new(); + *SKINS_USE_UNIFORM_BUFFERS + .get_or_init(|| render_device.limits().max_storage_buffers_per_shader_stage == 0) +} + pub fn prepare_skins( render_device: Res, render_queue: Res, @@ -124,7 +155,10 @@ pub fn extract_skins( query: Extract>, inverse_bindposes: Extract>>, joints: Extract>, + render_device: Res, ) { + let skins_use_uniform_buffers = skins_use_uniform_buffers(&render_device); + // Borrow check workaround. let (skin_indices, uniform) = (skin_indices.into_inner(), uniform.into_inner()); @@ -164,9 +198,12 @@ pub fn extract_skins( } last_start = last_start.max(start); - // Pad to 256 byte alignment - while buffer.len() % 4 != 0 { - buffer.push(Mat4::ZERO); + // Pad to 256 byte alignment if we're using a uniform buffer. + // There's no need to do this if we're using storage buffers, though. + if skins_use_uniform_buffers { + while buffer.len() % 4 != 0 { + buffer.push(Mat4::ZERO); + } } skin_indices @@ -181,11 +218,16 @@ pub fn extract_skins( } // NOTE: The skinned joints uniform buffer has to be bound at a dynamic offset per -// entity and so cannot currently be batched. +// entity and so cannot currently be batched on WebGL 2. pub fn no_automatic_skin_batching( mut commands: Commands, query: Query, Without)>, + render_device: Res, ) { + if !skins_use_uniform_buffers(&render_device) { + return; + } + for entity in &query { commands.entity(entity).try_insert(NoAutomaticBatching); } diff --git a/crates/bevy_pbr/src/render/skinning.wgsl b/crates/bevy_pbr/src/render/skinning.wgsl index 1ed9393308995..92e977aeb1b92 100644 --- a/crates/bevy_pbr/src/render/skinning.wgsl +++ b/crates/bevy_pbr/src/render/skinning.wgsl @@ -1,10 +1,15 @@ #define_import_path bevy_pbr::skinning #import bevy_pbr::mesh_types::SkinnedMesh +#import bevy_pbr::mesh_bindings::mesh #ifdef SKINNED +#ifdef SKINS_USE_UNIFORM_BUFFERS @group(1) @binding(1) var joint_matrices: SkinnedMesh; +#else // SKINS_USE_UNIFORM_BUFFERS +@group(1) @binding(1) var joint_matrices: array>; +#endif // SKINS_USE_UNIFORM_BUFFERS // An array of matrices specifying the joint positions from the previous frame. // @@ -12,16 +17,29 @@ // // If this is the first frame, or we're otherwise prevented from using data from // the previous frame, this is simply the same as `joint_matrices` above. +#ifdef SKINS_USE_UNIFORM_BUFFERS @group(1) @binding(6) var prev_joint_matrices: SkinnedMesh; +#else // SKINS_USE_UNIFORM_BUFFERS +@group(1) @binding(6) var prev_joint_matrices: array>; +#endif // SKINS_USE_UNIFORM_BUFFERS fn skin_model( indexes: vec4, weights: vec4, + instance_index: u32, ) -> mat4x4 { +#ifdef SKINS_USE_UNIFORM_BUFFERS return weights.x * joint_matrices.data[indexes.x] + weights.y * joint_matrices.data[indexes.y] + weights.z * joint_matrices.data[indexes.z] + weights.w * joint_matrices.data[indexes.w]; +#else // SKINS_USE_UNIFORM_BUFFERS + let skin_index = mesh[instance_index].current_skin_index; + return weights.x * joint_matrices[skin_index + indexes.x] + + weights.y * joint_matrices[skin_index + indexes.y] + + weights.z * joint_matrices[skin_index + indexes.z] + + weights.w * joint_matrices[skin_index + indexes.w]; +#endif // SKINS_USE_UNIFORM_BUFFERS } // Returns the skinned position of a vertex with the given weights from the @@ -31,11 +49,20 @@ fn skin_model( fn skin_prev_model( indexes: vec4, weights: vec4, + instance_index: u32, ) -> mat4x4 { +#ifdef SKINS_USE_UNIFORM_BUFFERS return weights.x * prev_joint_matrices.data[indexes.x] + weights.y * prev_joint_matrices.data[indexes.y] + weights.z * prev_joint_matrices.data[indexes.z] + weights.w * prev_joint_matrices.data[indexes.w]; +#else // SKINS_USE_UNIFORM_BUFFERS + let skin_index = mesh[instance_index].previous_skin_index; + return weights.x * prev_joint_matrices[skin_index + indexes.x] + + weights.y * prev_joint_matrices[skin_index + indexes.y] + + weights.z * prev_joint_matrices[skin_index + indexes.z] + + weights.w * prev_joint_matrices[skin_index + indexes.w]; +#endif // SKINS_USE_UNIFORM_BUFFERS } fn inverse_transpose_3x3m(in: mat3x3) -> mat3x3 { diff --git a/crates/bevy_picking/src/events.rs b/crates/bevy_picking/src/events.rs index aea4995818fcd..bb2458b7af0a7 100644 --- a/crates/bevy_picking/src/events.rs +++ b/crates/bevy_picking/src/events.rs @@ -31,7 +31,7 @@ //! //! The events this module defines fall into a few broad categories: //! + Hovering and movement: [`Over`], [`Move`], and [`Out`]. -//! + Clicking and pressing: [`Down`], [`Up`], and [`Click`]. +//! + Clicking and pressing: [`Pressed`], [`Released`], and [`Click`]. //! + Dragging and dropping: [`DragStart`], [`Drag`], [`DragEnd`], [`DragEnter`], [`DragOver`], [`DragDrop`], [`DragLeave`]. //! //! When received by an observer, these events will always be wrapped by the [`Pointer`] type, which contains @@ -167,7 +167,7 @@ pub struct Out { /// Fires when a pointer button is pressed over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] -pub struct Down { +pub struct Pressed { /// Pointer button pressed to trigger this event. pub button: PointerButton, /// Information about the picking intersection. @@ -176,14 +176,14 @@ pub struct Down { /// Fires when a pointer button is released over the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] -pub struct Up { +pub struct Released { /// Pointer button lifted to trigger this event. pub button: PointerButton, /// Information about the picking intersection. pub hit: HitData, } -/// Fires when a pointer sends a pointer down event followed by a pointer up event, with the same +/// Fires when a pointer sends a pointer pressed event followed by a pointer released event, with the same /// `target` entity for both events. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct Click { @@ -204,7 +204,7 @@ pub struct Move { pub delta: Vec2, } -/// Fires when the `target` entity receives a pointer down event followed by a pointer move event. +/// Fires when the `target` entity receives a pointer pressed event followed by a pointer move event. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragStart { /// Pointer button pressed and moved to trigger this event. @@ -224,10 +224,10 @@ pub struct Drag { pub delta: Vec2, } -/// Fires when a pointer is dragging the `target` entity and a pointer up event is received. +/// Fires when a pointer is dragging the `target` entity and a pointer released event is received. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragEnd { - /// Pointer button pressed, moved, and lifted to trigger this event. + /// Pointer button pressed, moved, and released to trigger this event. pub button: PointerButton, /// The vector of drag movement measured from start to final pointer position. pub distance: Vec2, @@ -269,7 +269,7 @@ pub struct DragLeave { /// Fires when a pointer drops the `dropped` entity onto the `target` entity. #[derive(Clone, PartialEq, Debug, Reflect)] pub struct DragDrop { - /// Pointer button lifted to drop. + /// Pointer button released to drop. pub button: PointerButton, /// The entity that was dropped onto the `target` entity. pub dropped: Entity, @@ -339,7 +339,7 @@ impl PointerState { pub struct PickingEventWriters<'w> { cancel_events: EventWriter<'w, Pointer>, click_events: EventWriter<'w, Pointer>, - down_events: EventWriter<'w, Pointer>, + pressed_events: EventWriter<'w, Pointer>, drag_drop_events: EventWriter<'w, Pointer>, drag_end_events: EventWriter<'w, Pointer>, drag_enter_events: EventWriter<'w, Pointer>, @@ -350,7 +350,7 @@ pub struct PickingEventWriters<'w> { move_events: EventWriter<'w, Pointer>, out_events: EventWriter<'w, Pointer>, over_events: EventWriter<'w, Pointer>, - up_events: EventWriter<'w, Pointer>, + released_events: EventWriter<'w, Pointer>, } /// Dispatches interaction events to the target entities. @@ -360,7 +360,7 @@ pub struct PickingEventWriters<'w> { /// + [`DragEnter`] → [`Over`]. /// + Any number of any of the following: /// + For each movement: [`DragStart`] → [`Drag`] → [`DragOver`] → [`Move`]. -/// + For each button press: [`Down`] or [`Click`] → [`Up`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`]. +/// + For each button press: [`Pressed`] or [`Click`] → [`Released`] → [`DragDrop`] → [`DragEnd`] → [`DragLeave`]. /// + For each pointer cancellation: [`Cancel`]. /// /// Additionally, across multiple frames, the following are also strictly @@ -368,7 +368,7 @@ pub struct PickingEventWriters<'w> { /// + When a pointer moves over the target: /// [`Over`], [`Move`], [`Out`]. /// + When a pointer presses buttons on the target: -/// [`Down`], [`Click`], [`Up`]. +/// [`Pressed`], [`Click`], [`Released`]. /// + When a pointer drags the target: /// [`DragStart`], [`Drag`], [`DragEnd`]. /// + When a pointer drags something over the target: @@ -390,7 +390,7 @@ pub struct PickingEventWriters<'w> { /// In the context of UI, this is especially problematic. Additional hierarchy-aware /// events will be added in a future release. /// -/// Both [`Click`] and [`Up`] target the entity hovered in the *previous frame*, +/// Both [`Click`] and [`Released`] target the entity hovered in the *previous frame*, /// rather than the current frame. This is because touch pointers hover nothing /// on the frame they are released. The end effect is that these two events can /// be received sequentally after an [`Out`] event (but always on the same frame @@ -545,31 +545,31 @@ pub fn pointer_events( // The sequence of events emitted depends on if this is a press or a release match direction { - PressDirection::Down => { - // If it's a press, emit a Down event and mark the hovered entities as pressed + PressDirection::Pressed => { + // If it's a press, emit a Pressed event and mark the hovered entities as pressed for (hovered_entity, hit) in hover_map .get(&pointer_id) .iter() .flat_map(|h| h.iter().map(|(entity, data)| (*entity, data.clone()))) { - let down_event = Pointer::new( + let pressed_event = Pointer::new( pointer_id, location.clone(), hovered_entity, - Down { + Pressed { button, hit: hit.clone(), }, ); - commands.trigger_targets(down_event.clone(), hovered_entity); - event_writers.down_events.send(down_event); + commands.trigger_targets(pressed_event.clone(), hovered_entity); + event_writers.pressed_events.send(pressed_event); // Also insert the press into the state state .pressing .insert(hovered_entity, (location.clone(), now, hit)); } } - PressDirection::Up => { + PressDirection::Released => { // Emit Click and Up events on all the previously hovered entities. for (hovered_entity, hit) in previous_hover_map .get(&pointer_id) @@ -592,18 +592,18 @@ pub fn pointer_events( commands.trigger_targets(click_event.clone(), hovered_entity); event_writers.click_events.send(click_event); } - // Always send the Up event - let up_event = Pointer::new( + // Always send the Released event + let released_event = Pointer::new( pointer_id, location.clone(), hovered_entity, - Up { + Released { button, hit: hit.clone(), }, ); - commands.trigger_targets(up_event.clone(), hovered_entity); - event_writers.up_events.send(up_event); + commands.trigger_targets(released_event.clone(), hovered_entity); + event_writers.released_events.send(released_event); } // Then emit the drop events. diff --git a/crates/bevy_picking/src/focus.rs b/crates/bevy_picking/src/focus.rs index d07c0c095f4bc..8c3246d96ea9a 100644 --- a/crates/bevy_picking/src/focus.rs +++ b/crates/bevy_picking/src/focus.rs @@ -226,7 +226,7 @@ pub fn update_interactions( // need to be able to insert the interaction component on entities if they do not exist. To do // so we need to know the final aggregated interaction state to avoid the scenario where we set // an entity to `Pressed`, then overwrite that with a lower precedent like `Hovered`. - let mut new_interaction_state = HashMap::::new(); + let mut new_interaction_state = HashMap::::default(); for (pointer, pointer_press, mut pointer_interaction) in &mut pointers { if let Some(pointers_hovered_entities) = hover_map.get(pointer) { // Insert a sorted list of hit entities into the pointer's interaction component. diff --git a/crates/bevy_picking/src/input.rs b/crates/bevy_picking/src/input.rs index 7c32ffac26f81..321ed6b5e6da2 100644 --- a/crates/bevy_picking/src/input.rs +++ b/crates/bevy_picking/src/input.rs @@ -152,8 +152,8 @@ pub fn mouse_pick_events( MouseButton::Other(_) | MouseButton::Back | MouseButton::Forward => continue, }; let direction = match input.state { - ButtonState::Pressed => PressDirection::Down, - ButtonState::Released => PressDirection::Up, + ButtonState::Pressed => PressDirection::Pressed, + ButtonState::Released => PressDirection::Released, }; pointer_events.send(PointerInput::new( PointerId::Mouse, @@ -198,7 +198,7 @@ pub fn touch_pick_events( pointer, location, PointerAction::Pressed { - direction: PressDirection::Down, + direction: PressDirection::Pressed, button: PointerButton::Primary, }, )); @@ -226,7 +226,7 @@ pub fn touch_pick_events( pointer, location, PointerAction::Pressed { - direction: PressDirection::Up, + direction: PressDirection::Released, button: PointerButton::Primary, }, )); diff --git a/crates/bevy_picking/src/lib.rs b/crates/bevy_picking/src/lib.rs index 3ff6f88d5b874..9a04220ba26cf 100644 --- a/crates/bevy_picking/src/lib.rs +++ b/crates/bevy_picking/src/lib.rs @@ -172,7 +172,7 @@ pub mod prelude { #[cfg(feature = "bevy_mesh_picking_backend")] #[doc(hidden)] pub use crate::mesh_picking::{ - ray_cast::{MeshRayCast, RayCastBackfaces, RayCastSettings, RayCastVisibility}, + ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastBackfaces, RayCastVisibility}, MeshPickingPlugin, MeshPickingSettings, RayCastPickable, }; #[doc(hidden)] @@ -400,7 +400,7 @@ impl Plugin for InteractionPlugin { .init_resource::() .add_event::>() .add_event::>() - .add_event::>() + .add_event::>() .add_event::>() .add_event::>() .add_event::>() @@ -411,7 +411,7 @@ impl Plugin for InteractionPlugin { .add_event::>() .add_event::>() .add_event::>() - .add_event::>() + .add_event::>() .add_systems( PreUpdate, (update_focus, pointer_events, update_interactions) diff --git a/crates/bevy_picking/src/mesh_picking/mod.rs b/crates/bevy_picking/src/mesh_picking/mod.rs index 62060540be692..a848097a6854f 100644 --- a/crates/bevy_picking/src/mesh_picking/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/mod.rs @@ -19,7 +19,7 @@ use bevy_app::prelude::*; use bevy_ecs::prelude::*; use bevy_reflect::prelude::*; use bevy_render::{prelude::*, view::RenderLayers}; -use ray_cast::{MeshRayCast, RayCastSettings, RayCastVisibility, SimplifiedMesh}; +use ray_cast::{MeshRayCast, MeshRayCastSettings, RayCastVisibility, SimplifiedMesh}; /// Runtime settings for the [`MeshPickingPlugin`]. #[derive(Resource, Reflect)] @@ -89,7 +89,7 @@ pub fn update_hits( let cam_layers = cam_layers.to_owned().unwrap_or_default(); - let settings = RayCastSettings { + let settings = MeshRayCastSettings { visibility: backend_settings.ray_cast_visibility, filter: &|entity| { let marker_requirement = diff --git a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs index e6c405bbb3677..2ba76f79606f6 100644 --- a/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs +++ b/crates/bevy_picking/src/mesh_picking/ray_cast/mod.rs @@ -34,7 +34,7 @@ pub enum RayCastVisibility { /// Settings for a ray cast. #[derive(Clone)] -pub struct RayCastSettings<'a> { +pub struct MeshRayCastSettings<'a> { /// Determines how ray casting should consider [`Visibility`]. pub visibility: RayCastVisibility, /// A predicate that is applied for every entity that ray casts are performed against. @@ -45,7 +45,7 @@ pub struct RayCastSettings<'a> { pub early_exit_test: &'a dyn Fn(Entity) -> bool, } -impl<'a> RayCastSettings<'a> { +impl<'a> MeshRayCastSettings<'a> { /// Set the filter to apply to the ray cast. pub fn with_filter(mut self, filter: &'a impl Fn(Entity) -> bool) -> Self { self.filter = filter; @@ -75,7 +75,7 @@ impl<'a> RayCastSettings<'a> { } } -impl<'a> Default for RayCastSettings<'a> { +impl<'a> Default for MeshRayCastSettings<'a> { fn default() -> Self { Self { visibility: RayCastVisibility::VisibleInView, @@ -128,13 +128,13 @@ type MeshFilter = Or<(With, With, With)>; /// # use bevy_picking::prelude::*; /// fn ray_cast_system(mut ray_cast: MeshRayCast) { /// let ray = Ray3d::new(Vec3::ZERO, Dir3::X); -/// let hits = ray_cast.cast_ray(ray, &RayCastSettings::default()); +/// let hits = ray_cast.cast_ray(ray, &MeshRayCastSettings::default()); /// } /// ``` /// /// ## Configuration /// -/// You can specify the behavior of the ray cast using [`RayCastSettings`]. This allows you to filter out +/// You can specify the behavior of the ray cast using [`MeshRayCastSettings`]. This allows you to filter out /// entities, configure early-out behavior, and set whether the [`Visibility`] of an entity should be /// considered. /// @@ -156,7 +156,7 @@ type MeshFilter = Or<(With, With, With)>; /// // Ignore the visibility of entities. This allows ray casting hidden entities. /// let visibility = RayCastVisibility::Any; /// -/// let settings = RayCastSettings::default() +/// let settings = MeshRayCastSettings::default() /// .with_filter(&filter) /// .with_early_exit_test(&early_exit_test) /// .with_visibility(visibility); @@ -205,7 +205,11 @@ pub struct MeshRayCast<'w, 's> { impl<'w, 's> MeshRayCast<'w, 's> { /// Casts the `ray` into the world and returns a sorted list of intersections, nearest first. - pub fn cast_ray(&mut self, ray: Ray3d, settings: &RayCastSettings) -> &[(Entity, RayMeshHit)] { + pub fn cast_ray( + &mut self, + ray: Ray3d, + settings: &MeshRayCastSettings, + ) -> &[(Entity, RayMeshHit)] { let ray_cull = info_span!("ray culling"); let ray_cull_guard = ray_cull.enter(); diff --git a/crates/bevy_picking/src/pointer.rs b/crates/bevy_picking/src/pointer.rs index fd7ec7b1eacb9..d8a65d9588a02 100644 --- a/crates/bevy_picking/src/pointer.rs +++ b/crates/bevy_picking/src/pointer.rs @@ -146,9 +146,9 @@ impl PointerPress { #[derive(Debug, Clone, Copy, PartialEq, Eq, Reflect)] pub enum PressDirection { /// The pointer button was just pressed - Down, + Pressed, /// The pointer button was just released - Up, + Released, } /// The button that was just pressed or released @@ -245,7 +245,7 @@ impl Location { pub enum PointerAction { /// A button has been pressed on the pointer. Pressed { - /// The press direction, either down or up. + /// The press state, either pressed or released. direction: PressDirection, /// The button that was pressed. button: PointerButton, @@ -286,7 +286,7 @@ impl PointerInput { #[inline] pub fn button_just_pressed(&self, target_button: PointerButton) -> bool { if let PointerAction::Pressed { direction, button } = self.action { - direction == PressDirection::Down && button == target_button + direction == PressDirection::Pressed && button == target_button } else { false } @@ -296,7 +296,7 @@ impl PointerInput { #[inline] pub fn button_just_released(&self, target_button: PointerButton) -> bool { if let PointerAction::Pressed { direction, button } = self.action { - direction == PressDirection::Up && button == target_button + direction == PressDirection::Released && button == target_button } else { false } @@ -314,11 +314,11 @@ impl PointerInput { .iter_mut() .for_each(|(pointer_id, _, mut pointer)| { if *pointer_id == event.pointer_id { - let is_down = direction == PressDirection::Down; + let is_pressed = direction == PressDirection::Pressed; match button { - PointerButton::Primary => pointer.primary = is_down, - PointerButton::Secondary => pointer.secondary = is_down, - PointerButton::Middle => pointer.middle = is_down, + PointerButton::Primary => pointer.primary = is_pressed, + PointerButton::Secondary => pointer.secondary = is_pressed, + PointerButton::Middle => pointer.middle = is_pressed, } } }); diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 010b5a7298855..5879c1cac7de8 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -45,6 +45,9 @@ bevy_utils = { path = "../bevy_utils", version = "0.15.0-dev", default-features ] } bevy_ptr = { path = "../bevy_ptr", version = "0.15.0-dev" } +# used by bevy-utils, but it also needs reflect impls +foldhash = { version = "0.1.3", default-features = false } + # other erased-serde = { version = "0.4", default-features = false, features = [ "alloc", diff --git a/crates/bevy_reflect/derive/src/serialization.rs b/crates/bevy_reflect/derive/src/serialization.rs index 3cfe0adc74451..75df7b7b930b2 100644 --- a/crates/bevy_reflect/derive/src/serialization.rs +++ b/crates/bevy_reflect/derive/src/serialization.rs @@ -24,7 +24,7 @@ impl SerializationDataDef { fields: &[StructField<'_>], bevy_reflect_path: &Path, ) -> Result, syn::Error> { - let mut skipped = HashMap::default(); + let mut skipped = >::default(); for field in fields { match field.attrs.ignore { diff --git a/crates/bevy_reflect/src/func/args/arg.rs b/crates/bevy_reflect/src/func/args/arg.rs index d614f073f2cc8..60698a3d7e0a7 100644 --- a/crates/bevy_reflect/src/func/args/arg.rs +++ b/crates/bevy_reflect/src/func/args/arg.rs @@ -183,6 +183,14 @@ impl<'a> Arg<'a> { } } } + + /// Returns `true` if the argument is of type `T`. + pub fn is(&self) -> bool { + self.value + .try_as_reflect() + .map(::is::) + .unwrap_or_default() + } } /// Represents an argument that can be passed to a [`DynamicFunction`] or [`DynamicFunctionMut`]. diff --git a/crates/bevy_reflect/src/func/args/count.rs b/crates/bevy_reflect/src/func/args/count.rs new file mode 100644 index 0000000000000..d5f410f88dfaf --- /dev/null +++ b/crates/bevy_reflect/src/func/args/count.rs @@ -0,0 +1,311 @@ +use crate::func::args::ArgCountOutOfBoundsError; +use core::fmt::{Debug, Formatter}; + +/// A container for zero or more argument counts for a function. +/// +/// For most functions, this will contain a single count, +/// however, overloaded functions may contain more. +/// +/// # Maximum Argument Count +/// +/// The maximum number of arguments that can be represented by this struct is 63, +/// as given by [`ArgCount::MAX_COUNT`]. +/// The reason for this is that all counts are stored internally as a single `u64` +/// with each bit representing a specific count based on its bit index. +/// +/// This allows for a smaller memory footprint and faster lookups compared to a +/// `HashSet` or `Vec` of possible counts. +/// It's also more appropriate for representing the argument counts of a function +/// given that most functions will not have more than a few arguments. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] +pub struct ArgCount { + /// The bits representing the argument counts. + /// + /// Each bit represents a specific count based on its bit index. + bits: u64, + /// The total number of argument counts. + len: u8, +} + +impl ArgCount { + /// The maximum number of arguments that can be represented by this struct. + pub const MAX_COUNT: usize = u64::BITS as usize - 1; + + /// Create a new [`ArgCount`] with the given count. + /// + /// # Errors + /// + /// Returns an error if the count is greater than [`Self::MAX_COUNT`]. + pub fn new(count: usize) -> Result { + Ok(Self { + bits: 1 << Self::try_to_u8(count)?, + len: 1, + }) + } + + /// Adds the given count to this [`ArgCount`]. + /// + /// # Panics + /// + /// Panics if the count is greater than [`Self::MAX_COUNT`]. + pub fn add(&mut self, count: usize) { + self.try_add(count).unwrap(); + } + + /// Attempts to add the given count to this [`ArgCount`]. + /// + /// # Errors + /// + /// Returns an error if the count is greater than [`Self::MAX_COUNT`]. + pub fn try_add(&mut self, count: usize) -> Result<(), ArgCountOutOfBoundsError> { + let count = Self::try_to_u8(count)?; + + if !self.contains_unchecked(count) { + self.len += 1; + self.bits |= 1 << count; + } + + Ok(()) + } + + /// Removes the given count from this [`ArgCount`]. + pub fn remove(&mut self, count: usize) { + self.try_remove(count).unwrap(); + } + + /// Attempts to remove the given count from this [`ArgCount`]. + /// + /// # Errors + /// + /// Returns an error if the count is greater than [`Self::MAX_COUNT`]. + pub fn try_remove(&mut self, count: usize) -> Result<(), ArgCountOutOfBoundsError> { + let count = Self::try_to_u8(count)?; + + if self.contains_unchecked(count) { + self.len -= 1; + self.bits &= !(1 << count); + } + + Ok(()) + } + + /// Checks if this [`ArgCount`] contains the given count. + pub fn contains(&self, count: usize) -> bool { + count < usize::BITS as usize && (self.bits >> count) & 1 == 1 + } + + /// Returns the total number of argument counts that this [`ArgCount`] contains. + pub fn len(&self) -> usize { + self.len as usize + } + + /// Returns true if this [`ArgCount`] contains no argument counts. + pub fn is_empty(&self) -> bool { + self.len == 0 + } + + /// Returns an iterator over the argument counts in this [`ArgCount`]. + pub fn iter(&self) -> ArgCountIter { + ArgCountIter { + count: *self, + index: 0, + found: 0, + } + } + + /// Checks if this [`ArgCount`] contains the given count without any bounds checking. + /// + /// # Panics + /// + /// Panics if the count is greater than [`Self::MAX_COUNT`]. + fn contains_unchecked(&self, count: u8) -> bool { + (self.bits >> count) & 1 == 1 + } + + /// Attempts to convert the given count to a `u8` within the bounds of the [maximum count]. + /// + /// [maximum count]: Self::MAX_COUNT + fn try_to_u8(count: usize) -> Result { + if count > Self::MAX_COUNT { + Err(ArgCountOutOfBoundsError(count)) + } else { + Ok(count as u8) + } + } +} + +/// Defaults this [`ArgCount`] to empty. +/// +/// This means that it contains no argument counts, including zero. +impl Default for ArgCount { + fn default() -> Self { + Self { bits: 0, len: 0 } + } +} + +impl Debug for ArgCount { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_set().entries(self.iter()).finish() + } +} + +/// An iterator for the argument counts in an [`ArgCount`]. +pub struct ArgCountIter { + count: ArgCount, + index: u8, + found: u8, +} + +impl Iterator for ArgCountIter { + type Item = usize; + + fn next(&mut self) -> Option { + loop { + if self.index as usize > ArgCount::MAX_COUNT { + return None; + } + + if self.found == self.count.len { + // All counts have been found + return None; + } + + if self.count.contains_unchecked(self.index) { + self.index += 1; + self.found += 1; + return Some(self.index as usize - 1); + } + + self.index += 1; + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.count.len(), Some(self.count.len())) + } +} + +impl ExactSizeIterator for ArgCountIter {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_default_to_empty() { + let count = ArgCount::default(); + + assert_eq!(count.len(), 0); + assert!(count.is_empty()); + + assert!(!count.contains(0)); + } + + #[test] + fn should_construct_with_count() { + let count = ArgCount::new(3).unwrap(); + + assert_eq!(count.len(), 1); + assert!(!count.is_empty()); + + assert!(count.contains(3)); + } + + #[test] + fn should_add_count() { + let mut count = ArgCount::default(); + count.add(3); + + assert_eq!(count.len(), 1); + + assert!(count.contains(3)); + } + + #[test] + fn should_add_multiple_counts() { + let mut count = ArgCount::default(); + count.add(3); + count.add(5); + count.add(7); + + assert_eq!(count.len(), 3); + + assert!(!count.contains(0)); + assert!(!count.contains(1)); + assert!(!count.contains(2)); + + assert!(count.contains(3)); + assert!(count.contains(5)); + assert!(count.contains(7)); + } + + #[test] + fn should_add_idempotently() { + let mut count = ArgCount::default(); + count.add(3); + count.add(3); + + assert_eq!(count.len(), 1); + assert!(count.contains(3)); + } + + #[test] + fn should_remove_count() { + let mut count = ArgCount::default(); + count.add(3); + + assert_eq!(count.len(), 1); + assert!(count.contains(3)); + + count.remove(3); + + assert_eq!(count.len(), 0); + assert!(!count.contains(3)); + } + + #[test] + fn should_allow_removeting_nonexistent_count() { + let mut count = ArgCount::default(); + + assert_eq!(count.len(), 0); + assert!(!count.contains(3)); + + count.remove(3); + + assert_eq!(count.len(), 0); + assert!(!count.contains(3)); + } + + #[test] + fn should_iterate_over_counts() { + let mut count = ArgCount::default(); + count.add(3); + count.add(5); + count.add(7); + + let mut iter = count.iter(); + + assert_eq!(iter.len(), 3); + + assert_eq!(iter.next(), Some(3)); + assert_eq!(iter.next(), Some(5)); + assert_eq!(iter.next(), Some(7)); + assert_eq!(iter.next(), None); + } + + #[test] + fn should_return_error_for_out_of_bounds_count() { + let count = ArgCount::new(64); + assert_eq!(count, Err(ArgCountOutOfBoundsError(64))); + + let mut count = ArgCount::default(); + assert_eq!(count.try_add(64), Err(ArgCountOutOfBoundsError(64))); + assert_eq!(count.try_remove(64), Err(ArgCountOutOfBoundsError(64))); + } + + #[test] + fn should_return_false_for_out_of_bounds_contains() { + let count = ArgCount::default(); + assert!(!count.contains(64)); + } +} diff --git a/crates/bevy_reflect/src/func/args/error.rs b/crates/bevy_reflect/src/func/args/error.rs index 9d66c7039354b..65c4caa6e8449 100644 --- a/crates/bevy_reflect/src/func/args/error.rs +++ b/crates/bevy_reflect/src/func/args/error.rs @@ -32,3 +32,8 @@ pub enum ArgError { #[error("expected an argument but received none")] EmptyArgList, } + +/// The given argument count is out of bounds. +#[derive(Debug, Error, PartialEq)] +#[error("argument count out of bounds: {0}")] +pub struct ArgCountOutOfBoundsError(pub usize); diff --git a/crates/bevy_reflect/src/func/args/list.rs b/crates/bevy_reflect/src/func/args/list.rs index 6ed7eace98c2e..145414424f4b1 100644 --- a/crates/bevy_reflect/src/func/args/list.rs +++ b/crates/bevy_reflect/src/func/args/list.rs @@ -5,7 +5,10 @@ use crate::{ }, PartialReflect, Reflect, TypePath, }; -use alloc::{boxed::Box, collections::VecDeque}; +use alloc::{ + boxed::Box, + collections::vec_deque::{Iter, VecDeque}, +}; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, vec}; @@ -286,6 +289,11 @@ impl<'a> ArgList<'a> { self.pop_arg()?.take_mut() } + /// Returns an iterator over the arguments in the list. + pub fn iter(&self) -> Iter<'_, Arg<'a>> { + self.list.iter() + } + /// Returns the number of arguments in the list. pub fn len(&self) -> usize { self.list.len() diff --git a/crates/bevy_reflect/src/func/args/mod.rs b/crates/bevy_reflect/src/func/args/mod.rs index da0ea00bb1abd..3b167bd2f0b32 100644 --- a/crates/bevy_reflect/src/func/args/mod.rs +++ b/crates/bevy_reflect/src/func/args/mod.rs @@ -4,6 +4,7 @@ //! [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut pub use arg::*; +pub use count::*; pub use error::*; pub use from_arg::*; pub use info::*; @@ -11,6 +12,7 @@ pub use list::*; pub use ownership::*; mod arg; +mod count; mod error; mod from_arg; mod info; diff --git a/crates/bevy_reflect/src/func/dynamic_function.rs b/crates/bevy_reflect/src/func/dynamic_function.rs index 36a2f22a8030c..e135d23f57cd9 100644 --- a/crates/bevy_reflect/src/func/dynamic_function.rs +++ b/crates/bevy_reflect/src/func/dynamic_function.rs @@ -2,8 +2,11 @@ use crate::{ self as bevy_reflect, __macro_exports::RegisterForReflection, func::{ - args::ArgList, info::FunctionInfo, DynamicFunctionMut, Function, FunctionError, - FunctionResult, IntoFunction, IntoFunctionMut, + args::{ArgCount, ArgList}, + dynamic_function_internal::DynamicFunctionInternal, + info::FunctionInfo, + DynamicFunctionMut, Function, FunctionOverloadError, FunctionResult, IntoFunction, + IntoFunctionMut, }, serde::Serializable, ApplyError, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, @@ -16,6 +19,16 @@ use core::fmt::{Debug, Formatter}; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, vec}; +/// An [`Arc`] containing a callback to a reflected function. +/// +/// The `Arc` is used to both ensure that it is `Send + Sync` +/// and to allow for the callback to be cloned. +/// +/// Note that cloning is okay since we only ever need an immutable reference +/// to call a `dyn Fn` function. +/// If we were to contain a `dyn FnMut` instead, cloning would be a lot more complicated. +type ArcFn<'env> = Arc Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>; + /// A dynamic representation of a function. /// /// This type can be used to represent any callable that satisfies [`Fn`] @@ -35,7 +48,7 @@ use alloc::{boxed::Box, format, vec}; /// Most of the time, a [`DynamicFunction`] can be created using the [`IntoFunction`] trait: /// /// ``` -/// # use bevy_reflect::func::{ArgList, DynamicFunction, FunctionInfo, IntoFunction}; +/// # use bevy_reflect::func::{ArgList, DynamicFunction, IntoFunction}; /// # /// fn add(a: i32, b: i32) -> i32 { /// a + b @@ -54,9 +67,9 @@ use alloc::{boxed::Box, format, vec}; /// /// [`ReflectFn`]: crate::func::ReflectFn /// [module-level documentation]: crate::func +#[derive(Clone)] pub struct DynamicFunction<'env> { - pub(super) info: FunctionInfo, - pub(super) func: Arc Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>, + pub(super) internal: DynamicFunctionInternal>, } impl<'env> DynamicFunction<'env> { @@ -65,17 +78,26 @@ impl<'env> DynamicFunction<'env> { /// The given function can be used to call out to any other callable, /// including functions, closures, or methods. /// - /// It's important that the function signature matches the provided [`FunctionInfo`] + /// It's important that the function signature matches the provided [`FunctionInfo`]. /// as this will be used to validate arguments when [calling] the function. + /// This is also required in order for [function overloading] to work correctly. + /// + /// # Panics + /// + /// This function may panic for any of the following reasons: + /// - No [`SignatureInfo`] is provided. + /// - A provided [`SignatureInfo`] has more arguments than [`ArgCount::MAX_COUNT`]. + /// - The conversion to [`FunctionInfo`] fails. /// - /// [calling]: DynamicFunction::call + /// [calling]: crate::func::dynamic_function::DynamicFunction::call + /// [`SignatureInfo`]: crate::func::SignatureInfo + /// [function overloading]: Self::with_overload pub fn new Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>( func: F, - info: FunctionInfo, + info: impl TryInto, ) -> Self { Self { - info, - func: Arc::new(func), + internal: DynamicFunctionInternal::new(Arc::new(func), info.try_into().unwrap()), } } @@ -88,10 +110,140 @@ impl<'env> DynamicFunction<'env> { /// /// [`DynamicFunctions`]: DynamicFunction pub fn with_name(mut self, name: impl Into>) -> Self { - self.info = self.info.with_name(name); + self.internal = self.internal.with_name(name); self } + /// Add an overload to this function. + /// + /// Overloads allow a single [`DynamicFunction`] to represent multiple functions of different signatures. + /// + /// This can be used to handle multiple monomorphizations of a generic function + /// or to allow functions with a variable number of arguments. + /// + /// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`. + /// For example, if the existing function had the signature `(i32, i32) -> i32`, + /// and the new function, `F`, also had the signature `(i32, i32) -> i32`, + /// the one from `F` would replace the one from the existing function. + /// + /// Overloaded functions retain the [name] of the original function. + /// + /// # Panics + /// + /// Panics if the function, `F`, contains a signature already found in this function. + /// + /// For a non-panicking version, see [`try_with_overload`]. + /// + /// # Examples + /// + /// ``` + /// # use std::ops::Add; + /// # use bevy_reflect::func::{ArgList, IntoFunction}; + /// # + /// fn add>(a: T, b: T) -> T { + /// a + b + /// } + /// + /// // Currently, the only generic type `func` supports is `i32`: + /// let mut func = add::.into_function(); + /// + /// // However, we can add an overload to handle `f32` as well: + /// func = func.with_overload(add::); + /// + /// // Test `i32`: + /// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.try_take::().unwrap(), 100); + /// + /// // Test `f32`: + /// let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.try_take::().unwrap(), 100.0); + ///``` + /// + /// ``` + /// # use bevy_reflect::func::{ArgList, IntoFunction}; + /// # + /// fn add_2(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// fn add_3(a: i32, b: i32, c: i32) -> i32 { + /// a + b + c + /// } + /// + /// // Currently, `func` only supports two arguments. + /// let mut func = add_2.into_function(); + /// + /// // However, we can add an overload to handle three arguments as well. + /// func = func.with_overload(add_3); + /// + /// // Test two arguments: + /// let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.try_take::().unwrap(), 100); + /// + /// // Test three arguments: + /// let args = ArgList::default() + /// .push_owned(25_i32) + /// .push_owned(75_i32) + /// .push_owned(100_i32); + /// let result = func.call(args).unwrap().unwrap_owned(); + /// assert_eq!(result.try_take::().unwrap(), 200); + /// ``` + /// + ///```should_panic + /// # use bevy_reflect::func::IntoFunction; + /// + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// fn sub(a: i32, b: i32) -> i32 { + /// a - b + /// } + /// + /// let mut func = add.into_function(); + /// + /// // This will panic because the function already has an argument signature for `(i32, i32)`: + /// func = func.with_overload(sub); + /// ``` + /// + /// [argument signature]: crate::func::signature::ArgumentSignature + /// [name]: Self::name + /// [`try_with_overload`]: Self::try_with_overload + pub fn with_overload<'a, F: IntoFunction<'a, Marker>, Marker>( + self, + function: F, + ) -> DynamicFunction<'a> + where + 'env: 'a, + { + self.try_with_overload(function).unwrap_or_else(|(_, err)| { + panic!("{}", err); + }) + } + + /// Attempt to add an overload to this function. + /// + /// If the function, `F`, contains a signature already found in this function, + /// an error will be returned along with the original function. + /// + /// For a panicking version, see [`with_overload`]. + /// + /// [`with_overload`]: Self::with_overload + pub fn try_with_overload, Marker>( + mut self, + function: F, + ) -> Result, FunctionOverloadError)> { + let function = function.into_function(); + + match self.internal.merge(function.internal) { + Ok(_) => Ok(self), + Err(err) => Err((Box::new(self), err)), + } + } + /// Call the function with the given arguments. /// /// # Example @@ -116,25 +268,17 @@ impl<'env> DynamicFunction<'env> { /// /// The function itself may also return any errors it needs to. pub fn call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { - let expected_arg_count = self.info.arg_count(); - let received_arg_count = args.len(); - - if expected_arg_count != received_arg_count { - Err(FunctionError::ArgCountMismatch { - expected: expected_arg_count, - received: received_arg_count, - }) - } else { - (self.func)(args) - } + self.internal.validate_args(&args)?; + let func = self.internal.get(&args)?; + func(args) } /// Returns the function info. pub fn info(&self) -> &FunctionInfo { - &self.info + self.internal.info() } - /// The [name] of the function. + /// The name of the function. /// /// For [`DynamicFunctions`] created using [`IntoFunction`], /// the default name will always be the full path to the function as returned by [`core::any::type_name`], @@ -143,17 +287,62 @@ impl<'env> DynamicFunction<'env> { /// /// This can be overridden using [`with_name`]. /// - /// [name]: FunctionInfo::name + /// If the function was [overloaded], it will retain its original name if it had one. + /// /// [`DynamicFunctions`]: DynamicFunction /// [`with_name`]: Self::with_name + /// [overloaded]: Self::with_overload pub fn name(&self) -> Option<&Cow<'static, str>> { - self.info.name() + self.internal.name() + } + + /// Returns `true` if the function is [overloaded]. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::IntoFunction; + /// let add = (|a: i32, b: i32| a + b).into_function(); + /// assert!(!add.is_overloaded()); + /// + /// let add = add.with_overload(|a: f32, b: f32| a + b); + /// assert!(add.is_overloaded()); + /// ``` + /// + /// [overloaded]: Self::with_overload + pub fn is_overloaded(&self) -> bool { + self.internal.is_overloaded() + } + /// Returns the number of arguments the function expects. + /// + /// For [overloaded] functions that can have a variable number of arguments, + /// this will contain the full set of counts for all signatures. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::IntoFunction; + /// let add = (|a: i32, b: i32| a + b).into_function(); + /// assert!(add.arg_count().contains(2)); + /// + /// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c); + /// assert!(add.arg_count().contains(2)); + /// assert!(add.arg_count().contains(3)); + /// ``` + /// + /// [overloaded]: Self::with_overload + pub fn arg_count(&self) -> ArgCount { + self.internal.arg_count() } } impl Function for DynamicFunction<'static> { + fn name(&self) -> Option<&Cow<'static, str>> { + self.internal.name() + } + fn info(&self) -> &FunctionInfo { - self.info() + self.internal.info() } fn reflect_call<'a>(&self, args: ArgList<'a>) -> FunctionResult<'a> { @@ -258,32 +447,14 @@ impl_type_path!((in bevy_reflect) DynamicFunction<'env>); /// This takes the format: `DynamicFunction(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// /// Names for arguments and the function itself are optional and will default to `_` if not provided. +/// +/// If the function is [overloaded], the output will include the signatures of all overloads as a set. +/// For example, `DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`. +/// +/// [overloaded]: DynamicFunction::with_overload impl<'env> Debug for DynamicFunction<'env> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); - write!(f, "DynamicFunction(fn {name}(")?; - - for (index, arg) in self.info.args().iter().enumerate() { - let name = arg.name().unwrap_or("_"); - let ty = arg.type_path(); - write!(f, "{name}: {ty}")?; - - if index + 1 < self.info.args().len() { - write!(f, ", ")?; - } - } - - let ret = self.info.return_info().type_path(); - write!(f, ") -> {ret})") - } -} - -impl<'env> Clone for DynamicFunction<'env> { - fn clone(&self) -> Self { - Self { - info: self.info.clone(), - func: Arc::clone(&self.func), - } + write!(f, "DynamicFunction({:?})", &self.internal) } } @@ -304,15 +475,20 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunction<'env> { #[cfg(test)] mod tests { use super::*; - use crate::func::IntoReturn; + use crate::func::signature::ArgumentSignature; + use crate::func::{FunctionError, IntoReturn, SignatureInfo}; + use crate::Type; + use bevy_utils::HashSet; + use core::ops::Add; #[test] fn should_overwrite_function_name() { let c = 23; - let func = (|a: i32, b: i32| a + b + c) - .into_function() - .with_name("my_function"); - assert_eq!(func.info().name().unwrap(), "my_function"); + let func = (|a: i32, b: i32| a + b + c).into_function(); + assert!(func.name().is_none()); + + let func = func.with_name("my_function"); + assert_eq!(func.name().unwrap(), "my_function"); } #[test] @@ -332,13 +508,40 @@ mod tests { let args = ArgList::default().push_owned(25_i32); let error = func.call(args).unwrap_err(); - assert!(matches!( + + assert_eq!( error, FunctionError::ArgCountMismatch { - expected: 2, + expected: ArgCount::new(2).unwrap(), received: 1 } - )); + ); + } + + #[test] + fn should_return_error_on_arg_count_mismatch_overloaded() { + let func = (|a: i32, b: i32| a + b) + .into_function() + .with_overload(|a: i32, b: i32, c: i32| a + b + c); + + let args = ArgList::default() + .push_owned(1_i32) + .push_owned(2_i32) + .push_owned(3_i32) + .push_owned(4_i32); + + let error = func.call(args).unwrap_err(); + + let mut expected_count = ArgCount::new(2).unwrap(); + expected_count.add(3); + + assert_eq!( + error, + FunctionError::ArgCountMismatch { + expected: expected_count, + received: 4 + } + ); } #[test] @@ -400,7 +603,7 @@ mod tests { }, // The `FunctionInfo` doesn't really matter for this test // so we can just give it dummy information. - FunctionInfo::anonymous() + SignatureInfo::anonymous() .with_arg::("curr") .with_arg::<()>("this"), ); @@ -409,4 +612,192 @@ mod tests { let value = factorial.call(args).unwrap().unwrap_owned(); assert_eq!(value.try_take::().unwrap(), 120); } + + #[test] + fn should_allow_creating_manual_generic_dynamic_function() { + let func = DynamicFunction::new( + |mut args| { + let a = args.take_arg()?; + let b = args.take_arg()?; + + if a.is::() { + let a = a.take::()?; + let b = b.take::()?; + Ok((a + b).into_return()) + } else { + let a = a.take::()?; + let b = b.take::()?; + Ok((a + b).into_return()) + } + }, + vec![ + SignatureInfo::named("add::") + .with_arg::("a") + .with_arg::("b") + .with_return::(), + SignatureInfo::named("add::") + .with_arg::("a") + .with_arg::("b") + .with_return::(), + ], + ); + + assert_eq!(func.name().unwrap(), "add::"); + let func = func.with_name("add"); + assert_eq!(func.name().unwrap(), "add"); + + let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + + let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100.0); + } + + #[test] + #[should_panic(expected = "called `Result::unwrap()` on an `Err` value: MissingSignature")] + fn should_panic_on_missing_function_info() { + let _ = DynamicFunction::new(|_| Ok(().into_return()), Vec::new()); + } + + #[test] + fn should_allow_function_overloading() { + fn add>(a: T, b: T) -> T { + a + b + } + + let func = add::.into_function().with_overload(add::); + + let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + + let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100.0); + } + + #[test] + fn should_allow_variable_arguments_via_overloading() { + fn add_2(a: i32, b: i32) -> i32 { + a + b + } + + fn add_3(a: i32, b: i32, c: i32) -> i32 { + a + b + c + } + + let func = add_2.into_function().with_overload(add_3); + + let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + + let args = ArgList::default() + .push_owned(25_i32) + .push_owned(75_i32) + .push_owned(100_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 200); + } + + #[test] + fn should_allow_function_overloading_with_manual_overload() { + let manual = DynamicFunction::new( + |mut args| { + let a = args.take_arg()?; + let b = args.take_arg()?; + + if a.is::() { + let a = a.take::()?; + let b = b.take::()?; + Ok((a + b).into_return()) + } else { + let a = a.take::()?; + let b = b.take::()?; + Ok((a + b).into_return()) + } + }, + vec![ + SignatureInfo::named("add::") + .with_arg::("a") + .with_arg::("b") + .with_return::(), + SignatureInfo::named("add::") + .with_arg::("a") + .with_arg::("b") + .with_return::(), + ], + ); + + let func = manual.with_overload(|a: u32, b: u32| a + b); + + let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + + let args = ArgList::default().push_owned(25_u32).push_owned(75_u32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + } + + #[test] + fn should_return_error_on_unknown_overload() { + fn add>(a: T, b: T) -> T { + a + b + } + + let func = add::.into_function().with_overload(add::); + + let args = ArgList::default().push_owned(25_u32).push_owned(75_u32); + let result = func.call(args); + assert_eq!( + result.unwrap_err(), + FunctionError::NoOverload { + expected: [ + ArgumentSignature::from_iter(vec![Type::of::(), Type::of::()]), + ArgumentSignature::from_iter(vec![Type::of::(), Type::of::()]) + ] + .into_iter() + .collect::>(), + received: ArgumentSignature::from_iter(vec![Type::of::(), Type::of::()]), + } + ); + } + + #[test] + fn should_debug_dynamic_function() { + fn greet(name: &String) -> String { + format!("Hello, {}!", name) + } + + let function = greet.into_function(); + let debug = format!("{:?}", function); + assert_eq!(debug, "DynamicFunction(fn bevy_reflect::func::dynamic_function::tests::should_debug_dynamic_function::greet(_: &alloc::string::String) -> alloc::string::String)"); + } + + #[test] + fn should_debug_anonymous_dynamic_function() { + let function = (|a: i32, b: i32| a + b).into_function(); + let debug = format!("{:?}", function); + assert_eq!(debug, "DynamicFunction(fn _(_: i32, _: i32) -> i32)"); + } + + #[test] + fn should_debug_overloaded_dynamic_function() { + fn add>(a: T, b: T) -> T { + a + b + } + + let func = add:: + .into_function() + .with_overload(add::) + .with_name("add"); + let debug = format!("{:?}", func); + assert_eq!( + debug, + "DynamicFunction(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})" + ); + } } diff --git a/crates/bevy_reflect/src/func/dynamic_function_internal.rs b/crates/bevy_reflect/src/func/dynamic_function_internal.rs new file mode 100644 index 0000000000000..427de1263d14a --- /dev/null +++ b/crates/bevy_reflect/src/func/dynamic_function_internal.rs @@ -0,0 +1,378 @@ +use crate::func::args::ArgCount; +use crate::func::signature::{ArgListSignature, ArgumentSignature}; +use crate::func::{ArgList, FunctionError, FunctionInfo, FunctionOverloadError}; +use alloc::borrow::Cow; +use bevy_utils::HashMap; +use core::fmt::{Debug, Formatter}; + +/// An internal structure for storing a function and its corresponding [function information]. +/// +/// This is used to facilitate the sharing of functionality between [`DynamicFunction`] +/// and [`DynamicFunctionMut`]. +/// +/// [function information]: FunctionInfo +/// [`DynamicFunction`]: crate::func::DynamicFunction +/// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut +#[derive(Clone)] +pub(super) struct DynamicFunctionInternal { + functions: Vec, + info: FunctionInfo, + arg_map: HashMap, +} + +impl DynamicFunctionInternal { + /// Create a new instance of [`DynamicFunctionInternal`] with the given function + /// and its corresponding information. + pub fn new(func: F, info: FunctionInfo) -> Self { + let arg_map = info + .signatures() + .iter() + .map(|sig| (ArgumentSignature::from(sig), 0)) + .collect(); + + Self { + functions: vec![func], + info, + arg_map, + } + } + pub fn with_name(mut self, name: impl Into>) -> Self { + self.info = self.info.with_name(Some(name.into())); + self + } + + /// The name of the function. + pub fn name(&self) -> Option<&Cow<'static, str>> { + self.info.name() + } + + /// Returns `true` if the function is overloaded. + pub fn is_overloaded(&self) -> bool { + self.info.is_overloaded() + } + + /// Get an immutable reference to the function. + /// + /// If the function is not overloaded, it will always be returned regardless of the arguments. + /// Otherwise, the function will be selected based on the arguments provided. + /// + /// If no overload matches the provided arguments, returns [`FunctionError::NoOverload`]. + pub fn get(&self, args: &ArgList) -> Result<&F, FunctionError> { + if !self.info.is_overloaded() { + return Ok(&self.functions[0]); + } + + let signature = ArgListSignature::from(args); + self.arg_map + .get(&signature) + .map(|index| &self.functions[*index]) + .ok_or_else(|| FunctionError::NoOverload { + expected: self.arg_map.keys().cloned().collect(), + received: ArgumentSignature::from(args), + }) + } + + /// Get a mutable reference to the function. + /// + /// If the function is not overloaded, it will always be returned regardless of the arguments. + /// Otherwise, the function will be selected based on the arguments provided. + /// + /// If no overload matches the provided arguments, returns [`FunctionError::NoOverload`]. + pub fn get_mut(&mut self, args: &ArgList) -> Result<&mut F, FunctionError> { + if !self.info.is_overloaded() { + return Ok(&mut self.functions[0]); + } + + let signature = ArgListSignature::from(args); + self.arg_map + .get(&signature) + .map(|index| &mut self.functions[*index]) + .ok_or_else(|| FunctionError::NoOverload { + expected: self.arg_map.keys().cloned().collect(), + received: ArgumentSignature::from(args), + }) + } + + /// Returns the function information contained in the map. + #[inline] + pub fn info(&self) -> &FunctionInfo { + &self.info + } + + /// Returns the number of arguments the function expects. + /// + /// For overloaded functions that can have a variable number of arguments, + /// this will contain the full set of counts for all signatures. + pub fn arg_count(&self) -> ArgCount { + self.info.arg_count() + } + + /// Helper method for validating that a given set of arguments are _potentially_ valid for this function. + /// + /// Currently, this validates: + /// - The number of arguments is within the expected range + pub fn validate_args(&self, args: &ArgList) -> Result<(), FunctionError> { + let expected_arg_count = self.arg_count(); + let received_arg_count = args.len(); + + if !expected_arg_count.contains(received_arg_count) { + Err(FunctionError::ArgCountMismatch { + expected: expected_arg_count, + received: received_arg_count, + }) + } else { + Ok(()) + } + } + + /// Merge another [`DynamicFunctionInternal`] into this one. + /// + /// If `other` contains any functions with the same signature as this one, + /// an error will be returned along with the original, unchanged instance. + /// + /// Therefore, this method should always return an overloaded function if the merge is successful. + /// + /// Additionally, if the merge succeeds, it should be guaranteed that the order + /// of the functions in the map will be preserved. + /// For example, merging `[func_a, func_b]` (self) with `[func_c, func_d]` (other) should result in + /// `[func_a, func_b, func_c, func_d]`. + /// And merging `[func_c, func_d]` (self) with `[func_a, func_b]` (other) should result in + /// `[func_c, func_d, func_a, func_b]`. + pub fn merge(&mut self, mut other: Self) -> Result<(), FunctionOverloadError> { + // Keep a separate map of the new indices to avoid mutating the existing one + // until we can be sure the merge will be successful. + let mut new_signatures = >::default(); + + for (sig, index) in other.arg_map { + if self.arg_map.contains_key(&sig) { + return Err(FunctionOverloadError::DuplicateSignature(sig)); + } + + new_signatures.insert(sig, self.functions.len() + index); + } + + self.arg_map.reserve(new_signatures.len()); + for (sig, index) in new_signatures { + self.arg_map.insert(sig, index); + } + + self.functions.append(&mut other.functions); + self.info.extend_unchecked(other.info); + + Ok(()) + } + + /// Maps the internally stored function(s) from type `F` to type `G`. + pub fn map_functions(self, f: fn(F) -> G) -> DynamicFunctionInternal { + DynamicFunctionInternal { + functions: self.functions.into_iter().map(f).collect(), + info: self.info, + arg_map: self.arg_map, + } + } +} + +impl Debug for DynamicFunctionInternal { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + self.info + .pretty_printer() + .include_fn_token() + .include_name() + .fmt(f) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::{FunctionInfo, SignatureInfo}; + use crate::Type; + + #[test] + fn should_merge_single_into_single() { + let mut func_a = DynamicFunctionInternal::new( + 'a', + FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")), + ); + + let func_b = DynamicFunctionInternal::new( + 'b', + FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")), + ); + + func_a.merge(func_b).unwrap(); + + assert_eq!(func_a.functions, vec!['a', 'b']); + assert_eq!(func_a.info.signatures().len(), 2); + assert_eq!( + func_a.arg_map, + HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + ]) + ); + } + + #[test] + fn should_merge_single_into_overloaded() { + let mut func_a = DynamicFunctionInternal::new( + 'a', + FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")), + ); + + let func_b = DynamicFunctionInternal { + functions: vec!['b', 'c'], + info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")) + .with_overload(SignatureInfo::anonymous().with_arg::("arg0")) + .unwrap(), + arg_map: HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + ]), + }; + + func_a.merge(func_b).unwrap(); + + assert_eq!(func_a.functions, vec!['a', 'b', 'c']); + assert_eq!(func_a.info.signatures().len(), 3); + assert_eq!( + func_a.arg_map, + HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + (ArgumentSignature::from_iter([Type::of::()]), 2), + ]) + ); + } + + #[test] + fn should_merge_overloaed_into_single() { + let mut func_a = DynamicFunctionInternal { + functions: vec!['a', 'b'], + info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")) + .with_overload(SignatureInfo::anonymous().with_arg::("arg0")) + .unwrap(), + arg_map: HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + ]), + }; + + let func_b = DynamicFunctionInternal::new( + 'c', + FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")), + ); + + func_a.merge(func_b).unwrap(); + + assert_eq!(func_a.functions, vec!['a', 'b', 'c']); + assert_eq!(func_a.info.signatures().len(), 3); + assert_eq!( + func_a.arg_map, + HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + (ArgumentSignature::from_iter([Type::of::()]), 2), + ]) + ); + } + + #[test] + fn should_merge_overloaded_into_overloaded() { + let mut func_a = DynamicFunctionInternal { + functions: vec!['a', 'b'], + info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")) + .with_overload(SignatureInfo::anonymous().with_arg::("arg0")) + .unwrap(), + arg_map: HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + ]), + }; + + let func_b = DynamicFunctionInternal { + functions: vec!['c', 'd'], + info: FunctionInfo::new(SignatureInfo::anonymous().with_arg::("arg0")) + .with_overload(SignatureInfo::anonymous().with_arg::("arg0")) + .unwrap(), + arg_map: HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + ]), + }; + + func_a.merge(func_b).unwrap(); + + assert_eq!(func_a.functions, vec!['a', 'b', 'c', 'd']); + assert_eq!(func_a.info.signatures().len(), 4); + assert_eq!( + func_a.arg_map, + HashMap::from_iter([ + (ArgumentSignature::from_iter([Type::of::()]), 0), + (ArgumentSignature::from_iter([Type::of::()]), 1), + (ArgumentSignature::from_iter([Type::of::()]), 2), + (ArgumentSignature::from_iter([Type::of::()]), 3), + ]) + ); + } + + #[test] + fn should_return_error_on_duplicate_signature() { + let mut func_a = DynamicFunctionInternal::new( + 'a', + FunctionInfo::new( + SignatureInfo::anonymous() + .with_arg::("arg0") + .with_arg::("arg1"), + ), + ); + + let func_b = DynamicFunctionInternal { + functions: vec!['b', 'c'], + info: FunctionInfo::new( + SignatureInfo::anonymous() + .with_arg::("arg0") + .with_arg::("arg1"), + ) + .with_overload( + SignatureInfo::anonymous() + .with_arg::("arg0") + .with_arg::("arg1"), + ) + .unwrap(), + arg_map: HashMap::from_iter([ + ( + ArgumentSignature::from_iter([Type::of::(), Type::of::()]), + 0, + ), + ( + ArgumentSignature::from_iter([Type::of::(), Type::of::()]), + 1, + ), + ]), + }; + + let FunctionOverloadError::DuplicateSignature(duplicate) = + func_a.merge(func_b).unwrap_err() + else { + panic!("Expected `FunctionOverloadError::DuplicateSignature`"); + }; + + assert_eq!( + duplicate, + ArgumentSignature::from_iter([Type::of::(), Type::of::()]) + ); + + // Assert the original remains unchanged: + assert!(!func_a.is_overloaded()); + assert_eq!(func_a.functions, vec!['a']); + assert_eq!(func_a.info.signatures().len(), 1); + assert_eq!( + func_a.arg_map, + HashMap::from_iter([( + ArgumentSignature::from_iter([Type::of::(), Type::of::()]), + 0 + ),]) + ); + } +} diff --git a/crates/bevy_reflect/src/func/dynamic_function_mut.rs b/crates/bevy_reflect/src/func/dynamic_function_mut.rs index 46341ee553124..bcbf6a96337ab 100644 --- a/crates/bevy_reflect/src/func/dynamic_function_mut.rs +++ b/crates/bevy_reflect/src/func/dynamic_function_mut.rs @@ -1,14 +1,18 @@ -use alloc::{borrow::Cow, boxed::Box}; +use alloc::{borrow::Cow, boxed::Box, sync::Arc}; use core::fmt::{Debug, Formatter}; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, vec}; use crate::func::{ - args::ArgList, info::FunctionInfo, DynamicFunction, FunctionError, FunctionResult, - IntoFunctionMut, + args::{ArgCount, ArgList}, + dynamic_function_internal::DynamicFunctionInternal, + DynamicFunction, FunctionInfo, FunctionOverloadError, FunctionResult, IntoFunctionMut, }; +/// A [`Box`] containing a callback to a reflected function. +type BoxFnMut<'env> = Box FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>; + /// A dynamic representation of a function. /// /// This type can be used to represent any callable that satisfies [`FnMut`] @@ -66,8 +70,7 @@ use crate::func::{ /// [`ReflectFnMut`]: crate::func::ReflectFnMut /// [module-level documentation]: crate::func pub struct DynamicFunctionMut<'env> { - info: FunctionInfo, - func: Box FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>, + internal: DynamicFunctionInternal>, } impl<'env> DynamicFunctionMut<'env> { @@ -76,17 +79,26 @@ impl<'env> DynamicFunctionMut<'env> { /// The given function can be used to call out to any other callable, /// including functions, closures, or methods. /// - /// It's important that the function signature matches the provided [`FunctionInfo`] + /// It's important that the function signature matches the provided [`FunctionInfo`]. /// as this will be used to validate arguments when [calling] the function. + /// This is also required in order for [function overloading] to work correctly. + /// + /// # Panics + /// + /// This function may panic for any of the following reasons: + /// - No [`SignatureInfo`] is provided. + /// - A provided [`SignatureInfo`] has more arguments than [`ArgCount::MAX_COUNT`]. + /// - The conversion to [`FunctionInfo`] fails. /// - /// [calling]: DynamicFunctionMut::call + /// [calling]: crate::func::dynamic_function_mut::DynamicFunctionMut::call + /// [`SignatureInfo`]: crate::func::SignatureInfo + /// [function overloading]: Self::with_overload pub fn new FnMut(ArgList<'a>) -> FunctionResult<'a> + 'env>( func: F, - info: FunctionInfo, + info: impl TryInto, ) -> Self { Self { - info, - func: Box::new(func), + internal: DynamicFunctionInternal::new(Box::new(func), info.try_into().unwrap()), } } @@ -99,10 +111,99 @@ impl<'env> DynamicFunctionMut<'env> { /// /// [`DynamicFunctionMuts`]: DynamicFunctionMut pub fn with_name(mut self, name: impl Into>) -> Self { - self.info = self.info.with_name(name); + self.internal = self.internal.with_name(name); self } + /// Add an overload to this function. + /// + /// Overloads allow a single [`DynamicFunctionMut`] to represent multiple functions of different signatures. + /// + /// This can be used to handle multiple monomorphizations of a generic function + /// or to allow functions with a variable number of arguments. + /// + /// Any functions with the same [argument signature] will be overwritten by the one from the new function, `F`. + /// For example, if the existing function had the signature `(i32, i32) -> i32`, + /// and the new function, `F`, also had the signature `(i32, i32) -> i32`, + /// the one from `F` would replace the one from the existing function. + /// + /// Overloaded functions retain the [name] of the original function. + /// + /// Note that it may be impossible to overload closures that mutably borrow from their environment + /// due to Rust's borrowing rules. + /// However, it's still possible to overload functions that do not capture their environment mutably, + /// or those that maintain mutually exclusive mutable references to their environment. + /// + /// # Panics + /// + /// Panics if the function, `F`, contains a signature already found in this function. + /// + /// For a non-panicking version, see [`try_with_overload`]. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::IntoFunctionMut; + /// let mut total_i32 = 0; + /// let mut add_i32 = |a: i32| total_i32 += a; + /// + /// let mut total_f32 = 0.0; + /// let mut add_f32 = |a: f32| total_f32 += a; + /// + /// // Currently, the only generic type `func` supports is `i32`. + /// let mut func = add_i32.into_function_mut(); + /// + /// // However, we can add an overload to handle `f32` as well: + /// func = func.with_overload(add_f32); + /// + /// // Test `i32`: + /// let args = bevy_reflect::func::ArgList::new().push_owned(123_i32); + /// func.call(args).unwrap(); + /// + /// // Test `f32`: + /// let args = bevy_reflect::func::ArgList::new().push_owned(1.23_f32); + /// func.call(args).unwrap(); + /// + /// drop(func); + /// assert_eq!(total_i32, 123); + /// assert_eq!(total_f32, 1.23); + /// ``` + /// + /// [argument signature]: crate::func::signature::ArgumentSignature + /// [name]: Self::name + /// [`try_with_overload`]: Self::try_with_overload + pub fn with_overload<'a, F: IntoFunctionMut<'a, Marker>, Marker>( + self, + function: F, + ) -> DynamicFunctionMut<'a> + where + 'env: 'a, + { + self.try_with_overload(function).unwrap_or_else(|(_, err)| { + panic!("{}", err); + }) + } + + /// Attempt to add an overload to this function. + /// + /// If the function, `F`, contains a signature already found in this function, + /// an error will be returned along with the original function. + /// + /// For a panicking version, see [`with_overload`]. + /// + /// [`with_overload`]: Self::with_overload + pub fn try_with_overload, Marker>( + mut self, + function: F, + ) -> Result, FunctionOverloadError)> { + let function = function.into_function_mut(); + + match self.internal.merge(function.internal) { + Ok(_) => Ok(self), + Err(err) => Err((Box::new(self), err)), + } + } + /// Call the function with the given arguments. /// /// Variables that are captured mutably by this function @@ -135,17 +236,9 @@ impl<'env> DynamicFunctionMut<'env> { /// /// [`call_once`]: DynamicFunctionMut::call_once pub fn call<'a>(&mut self, args: ArgList<'a>) -> FunctionResult<'a> { - let expected_arg_count = self.info.arg_count(); - let received_arg_count = args.len(); - - if expected_arg_count != received_arg_count { - Err(FunctionError::ArgCountMismatch { - expected: expected_arg_count, - received: received_arg_count, - }) - } else { - (self.func)(args) - } + self.internal.validate_args(&args)?; + let func = self.internal.get_mut(&args)?; + func(args) } /// Call the function with the given arguments and consume it. @@ -177,25 +270,15 @@ impl<'env> DynamicFunctionMut<'env> { /// /// The function itself may also return any errors it needs to. pub fn call_once(mut self, args: ArgList) -> FunctionResult { - let expected_arg_count = self.info.arg_count(); - let received_arg_count = args.len(); - - if expected_arg_count != received_arg_count { - Err(FunctionError::ArgCountMismatch { - expected: expected_arg_count, - received: received_arg_count, - }) - } else { - (self.func)(args) - } + self.call(args) } /// Returns the function info. pub fn info(&self) -> &FunctionInfo { - &self.info + self.internal.info() } - /// The [name] of the function. + /// The name of the function. /// /// For [`DynamicFunctionMuts`] created using [`IntoFunctionMut`], /// the default name will always be the full path to the function as returned by [`core::any::type_name`], @@ -204,11 +287,52 @@ impl<'env> DynamicFunctionMut<'env> { /// /// This can be overridden using [`with_name`]. /// - /// [name]: FunctionInfo::name /// [`DynamicFunctionMuts`]: DynamicFunctionMut /// [`with_name`]: Self::with_name pub fn name(&self) -> Option<&Cow<'static, str>> { - self.info.name() + self.internal.name() + } + + /// Returns `true` if the function is [overloaded]. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::IntoFunctionMut; + /// let mut total_i32 = 0; + /// let increment = (|value: i32| total_i32 += value).into_function_mut(); + /// assert!(!increment.is_overloaded()); + /// + /// let mut total_f32 = 0.0; + /// let increment = increment.with_overload(|value: f32| total_f32 += value); + /// assert!(increment.is_overloaded()); + /// ``` + /// + /// [overloaded]: Self::with_overload + pub fn is_overloaded(&self) -> bool { + self.internal.is_overloaded() + } + + /// Returns the number of arguments the function expects. + /// + /// For [overloaded] functions that can have a variable number of arguments, + /// this will contain the full set of counts for all signatures. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::IntoFunctionMut; + /// let add = (|a: i32, b: i32| a + b).into_function_mut(); + /// assert!(add.arg_count().contains(2)); + /// + /// let add = add.with_overload(|a: f32, b: f32, c: f32| a + b + c); + /// assert!(add.arg_count().contains(2)); + /// assert!(add.arg_count().contains(3)); + /// ``` + /// + /// [overloaded]: Self::with_overload + pub fn arg_count(&self) -> ArgCount { + self.internal.arg_count() } } @@ -217,23 +341,14 @@ impl<'env> DynamicFunctionMut<'env> { /// This takes the format: `DynamicFunctionMut(fn {name}({arg1}: {type1}, {arg2}: {type2}, ...) -> {return_type})`. /// /// Names for arguments and the function itself are optional and will default to `_` if not provided. +/// +/// If the function is [overloaded], the output will include the signatures of all overloads as a set. +/// For example, `DynamicFunctionMut(fn add{(_: i32, _: i32) -> i32, (_: f32, _: f32) -> f32})`. +/// +/// [overloaded]: DynamicFunctionMut::with_overload impl<'env> Debug for DynamicFunctionMut<'env> { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - let name = self.info.name().unwrap_or(&Cow::Borrowed("_")); - write!(f, "DynamicFunctionMut(fn {name}(")?; - - for (index, arg) in self.info.args().iter().enumerate() { - let name = arg.name().unwrap_or("_"); - let ty = arg.type_path(); - write!(f, "{name}: {ty}")?; - - if index + 1 < self.info.args().len() { - write!(f, ", ")?; - } - } - - let ret = self.info.return_info().type_path(); - write!(f, ") -> {ret})") + write!(f, "DynamicFunctionMut({:?})", &self.internal) } } @@ -241,12 +356,20 @@ impl<'env> From> for DynamicFunctionMut<'env> { #[inline] fn from(function: DynamicFunction<'env>) -> Self { Self { - info: function.info, - func: Box::new(move |args| (function.func)(args)), + internal: function.internal.map_functions(arc_to_box), } } } +/// Helper function from converting an [`Arc`] function to a [`Box`] function. +/// +/// This is needed to help the compiler infer the correct types. +fn arc_to_box<'env>( + f: Arc Fn(ArgList<'a>) -> FunctionResult<'a> + Send + Sync + 'env>, +) -> BoxFnMut<'env> { + Box::new(move |args| f(args)) +} + impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> { #[inline] fn into_function_mut(self) -> DynamicFunctionMut<'env> { @@ -257,14 +380,17 @@ impl<'env> IntoFunctionMut<'env, ()> for DynamicFunctionMut<'env> { #[cfg(test)] mod tests { use super::*; + use crate::func::{FunctionError, IntoReturn, SignatureInfo}; + use core::ops::Add; #[test] fn should_overwrite_function_name() { let mut total = 0; - let func = (|a: i32, b: i32| total = a + b) - .into_function_mut() - .with_name("my_function"); - assert_eq!(func.info().name().unwrap(), "my_function"); + let func = (|a: i32, b: i32| total = a + b).into_function_mut(); + assert!(func.name().is_none()); + + let func = func.with_name("my_function"); + assert_eq!(func.name().unwrap(), "my_function"); } #[test] @@ -285,22 +411,79 @@ mod tests { let args = ArgList::default().push_owned(25_i32); let error = func.call(args).unwrap_err(); - assert!(matches!( + assert_eq!( error, FunctionError::ArgCountMismatch { - expected: 2, + expected: ArgCount::new(2).unwrap(), received: 1 } - )); + ); let args = ArgList::default().push_owned(25_i32); let error = func.call_once(args).unwrap_err(); - assert!(matches!( + assert_eq!( error, FunctionError::ArgCountMismatch { - expected: 2, + expected: ArgCount::new(2).unwrap(), received: 1 } - )); + ); + } + + #[test] + fn should_allow_creating_manual_generic_dynamic_function_mut() { + let mut total = 0_i32; + let func = DynamicFunctionMut::new( + |mut args| { + let value = args.take_arg()?; + + if value.is::() { + let value = value.take::()?; + total += value; + } else { + let value = value.take::()?; + total += value as i32; + } + + Ok(().into_return()) + }, + vec![ + SignatureInfo::named("add::").with_arg::("value"), + SignatureInfo::named("add::").with_arg::("value"), + ], + ); + + assert_eq!(func.name().unwrap(), "add::"); + let mut func = func.with_name("add"); + assert_eq!(func.name().unwrap(), "add"); + + let args = ArgList::default().push_owned(25_i32); + func.call(args).unwrap(); + let args = ArgList::default().push_owned(75_i16); + func.call(args).unwrap(); + + drop(func); + assert_eq!(total, 100); + } + + // Closures that mutably borrow from their environment cannot realistically + // be overloaded since that would break Rust's borrowing rules. + // However, we still need to verify overloaded functions work since a + // `DynamicFunctionMut` can also be made from a non-mutably borrowing closure/function. + #[test] + fn should_allow_function_overloading() { + fn add>(a: T, b: T) -> T { + a + b + } + + let mut func = add::.into_function_mut().with_overload(add::); + + let args = ArgList::default().push_owned(25_i32).push_owned(75_i32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100); + + let args = ArgList::default().push_owned(25.0_f32).push_owned(75.0_f32); + let result = func.call(args).unwrap().unwrap_owned(); + assert_eq!(result.try_take::().unwrap(), 100.0); } } diff --git a/crates/bevy_reflect/src/func/error.rs b/crates/bevy_reflect/src/func/error.rs index 234cbb9e6a779..ad6796be19f1e 100644 --- a/crates/bevy_reflect/src/func/error.rs +++ b/crates/bevy_reflect/src/func/error.rs @@ -1,5 +1,10 @@ -use crate::func::{args::ArgError, Return}; +use crate::func::signature::ArgumentSignature; +use crate::func::{ + args::{ArgCount, ArgError}, + Return, +}; use alloc::borrow::Cow; +use bevy_utils::HashSet; use thiserror::Error; #[cfg(not(feature = "std"))] @@ -15,8 +20,14 @@ pub enum FunctionError { #[error(transparent)] ArgError(#[from] ArgError), /// The number of arguments provided does not match the expected number. - #[error("expected {expected} arguments but received {received}")] - ArgCountMismatch { expected: usize, received: usize }, + #[error("received {received} arguments but expected one of {expected:?}")] + ArgCountMismatch { expected: ArgCount, received: usize }, + /// No overload was found for the given set of arguments. + #[error("no overload found for arguments with signature `{received:?}`, expected one of `{expected:?}`")] + NoOverload { + expected: HashSet, + received: ArgumentSignature, + }, } /// The result of calling a [`DynamicFunction`] or [`DynamicFunctionMut`]. @@ -28,6 +39,25 @@ pub enum FunctionError { /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut pub type FunctionResult<'a> = Result, FunctionError>; +/// An error that occurs when attempting to add a function overload. +#[derive(Debug, Error, PartialEq)] +pub enum FunctionOverloadError { + /// A [`SignatureInfo`] was expected, but none was found. + /// + /// [`SignatureInfo`]: crate::func::info::SignatureInfo + #[error("expected at least one `SignatureInfo` but found none")] + MissingSignature, + /// An error that occurs when attempting to add a function overload with a duplicate signature. + #[error("could not add function overload: duplicate found for signature `{0:?}`")] + DuplicateSignature(ArgumentSignature), + #[error( + "argument signature `{:?}` has too many arguments (max {})", + 0, + ArgCount::MAX_COUNT + )] + TooManyArguments(ArgumentSignature), +} + /// An error that occurs when registering a function into a [`FunctionRegistry`]. /// /// [`FunctionRegistry`]: crate::func::FunctionRegistry diff --git a/crates/bevy_reflect/src/func/function.rs b/crates/bevy_reflect/src/func/function.rs index face9f6466601..0d8e94ca95aff 100644 --- a/crates/bevy_reflect/src/func/function.rs +++ b/crates/bevy_reflect/src/func/function.rs @@ -1,5 +1,8 @@ use crate::{ - func::{ArgList, DynamicFunction, FunctionInfo, FunctionResult}, + func::{ + args::{ArgCount, ArgList}, + DynamicFunction, FunctionInfo, FunctionResult, + }, PartialReflect, }; use alloc::borrow::Cow; @@ -45,12 +48,15 @@ pub trait Function: PartialReflect + Debug { /// /// [`DynamicFunctions`]: crate::func::DynamicFunction /// [`IntoFunction`]: crate::func::IntoFunction - fn name(&self) -> Option<&Cow<'static, str>> { - self.info().name() - } + fn name(&self) -> Option<&Cow<'static, str>>; - /// The number of arguments this function accepts. - fn arg_count(&self) -> usize { + /// Returns the number of arguments the function expects. + /// + /// For [overloaded] functions that can have a variable number of arguments, + /// this will contain the full set of counts for all signatures. + /// + /// [overloaded]: crate::func#overloading-functions + fn arg_count(&self) -> ArgCount { self.info().arg_count() } diff --git a/crates/bevy_reflect/src/func/info.rs b/crates/bevy_reflect/src/func/info.rs index d8325c582d8a4..797b97e7e1bac 100644 --- a/crates/bevy_reflect/src/func/info.rs +++ b/crates/bevy_reflect/src/func/info.rs @@ -1,62 +1,274 @@ use alloc::{borrow::Cow, vec}; +use core::fmt::{Debug, Formatter}; #[cfg(not(feature = "std"))] use alloc::{boxed::Box, format, vec}; -use variadics_please::all_tuples; - use crate::{ - func::args::{ArgInfo, GetOwnership, Ownership}, + func::args::{ArgCount, ArgCountOutOfBoundsError, ArgInfo, GetOwnership, Ownership}, + func::signature::ArgumentSignature, + func::FunctionOverloadError, type_info::impl_type_methods, Type, TypePath, }; +use variadics_please::all_tuples; + /// Type information for a [`DynamicFunction`] or [`DynamicFunctionMut`]. /// /// This information can be retrieved directly from certain functions and closures /// using the [`TypedFunction`] trait, and manually constructed otherwise. /// +/// It is compromised of one or more [`SignatureInfo`] structs, +/// allowing it to represent functions with multiple sets of arguments (i.e. "overloaded functions"). +/// /// [`DynamicFunction`]: crate::func::DynamicFunction /// [`DynamicFunctionMut`]: crate::func::DynamicFunctionMut #[derive(Debug, Clone)] pub struct FunctionInfo { name: Option>, - args: Vec, - return_info: ReturnInfo, + arg_count: ArgCount, + signatures: Box<[SignatureInfo]>, } impl FunctionInfo { - /// Create a new [`FunctionInfo`] for a function with the given name. + /// Create a new [`FunctionInfo`] for a function with the given signature. + /// + /// # Panics + /// + /// Panics if the given signature has more than the maximum number of arguments + /// as specified by [`ArgCount::MAX_COUNT`]. + pub fn new(signature: SignatureInfo) -> Self { + Self { + name: signature.name.clone(), + arg_count: ArgCount::new(signature.arg_count()).unwrap(), + signatures: vec![signature].into(), + } + } + + /// Create a new [`FunctionInfo`] from a set of signatures. + /// + /// Returns an error if the given iterator is empty or contains duplicate signatures. + pub fn try_from_iter( + signatures: impl IntoIterator, + ) -> Result { + let mut iter = signatures.into_iter(); + + let base = iter.next().ok_or(FunctionOverloadError::MissingSignature)?; + + if base.arg_count() > ArgCount::MAX_COUNT { + return Err(FunctionOverloadError::TooManyArguments( + ArgumentSignature::from(&base), + )); + } + + let mut info = Self::new(base); + + for signature in iter { + if signature.arg_count() > ArgCount::MAX_COUNT { + return Err(FunctionOverloadError::TooManyArguments( + ArgumentSignature::from(&signature), + )); + } + + info = info.with_overload(signature).map_err(|sig| { + FunctionOverloadError::DuplicateSignature(ArgumentSignature::from(&sig)) + })?; + } + + Ok(info) + } + + /// The base signature for this function. + /// + /// All functions—including overloaded functions—are guaranteed to have at least one signature. + /// The first signature used to define the [`FunctionInfo`] is considered the base signature. + pub fn base(&self) -> &SignatureInfo { + &self.signatures[0] + } + + /// Whether this function is overloaded. + /// + /// This is determined by the existence of multiple signatures. + pub fn is_overloaded(&self) -> bool { + self.signatures.len() > 1 + } + + /// Set the name of the function. + pub fn with_name(mut self, name: Option>>) -> Self { + self.name = name.map(Into::into); + self + } + + /// The name of the function. + /// + /// For [`DynamicFunctions`] created using [`IntoFunction`] or [`DynamicFunctionMuts`] created using [`IntoFunctionMut`], + /// the default name will always be the full path to the function as returned by [`std::any::type_name`], + /// unless the function is a closure, anonymous function, or function pointer, + /// in which case the name will be `None`. + /// + /// For overloaded functions, this will be the name of the base signature, + /// unless manually overwritten using [`Self::with_name`]. + /// + /// [`DynamicFunctions`]: crate::func::DynamicFunction + /// [`IntoFunction`]: crate::func::IntoFunction + /// [`DynamicFunctionMuts`]: crate::func::DynamicFunctionMut + /// [`IntoFunctionMut`]: crate::func::IntoFunctionMut + pub fn name(&self) -> Option<&Cow<'static, str>> { + self.name.as_ref() + } + + /// Add a signature to this function. + /// + /// If a signature with the same [`ArgumentSignature`] already exists, + /// an error is returned with the given signature. + /// + /// # Panics + /// + /// Panics if the given signature has more than the maximum number of arguments + /// as specified by [`ArgCount::MAX_COUNT`]. + pub fn with_overload(mut self, signature: SignatureInfo) -> Result { + let is_duplicate = self.signatures.iter().any(|s| { + s.arg_count() == signature.arg_count() + && ArgumentSignature::from(s) == ArgumentSignature::from(&signature) + }); + + if is_duplicate { + return Err(signature); + } + + self.arg_count.add(signature.arg_count()); + self.signatures = IntoIterator::into_iter(self.signatures) + .chain(Some(signature)) + .collect(); + Ok(self) + } + + /// Returns the number of arguments the function expects. + /// + /// For [overloaded] functions that can have a variable number of arguments, + /// this will contain the full set of counts for all signatures. + /// + /// [overloaded]: crate::func#overloading-functions + pub fn arg_count(&self) -> ArgCount { + self.arg_count + } + + /// The signatures of the function. + /// + /// This is guaranteed to always contain at least one signature. + /// Overloaded functions will contain two or more. + pub fn signatures(&self) -> &[SignatureInfo] { + &self.signatures + } + + /// Returns a wrapper around this info that implements [`Debug`] for pretty-printing the function. + /// + /// This can be useful for more readable debugging and logging. + /// + /// # Example + /// + /// ``` + /// # use bevy_reflect::func::{FunctionInfo, TypedFunction}; + /// # + /// fn add(a: i32, b: i32) -> i32 { + /// a + b + /// } + /// + /// let info = add.get_function_info(); + /// + /// let pretty = info.pretty_printer(); + /// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); + /// ``` + pub fn pretty_printer(&self) -> PrettyPrintFunctionInfo { + PrettyPrintFunctionInfo::new(self) + } + + /// Extend this [`FunctionInfo`] with another without checking for duplicates. + /// + /// # Panics + /// + /// Panics if the given signature has more than the maximum number of arguments + /// as specified by [`ArgCount::MAX_COUNT`]. + pub(super) fn extend_unchecked(&mut self, other: FunctionInfo) { + if self.name.is_none() { + self.name = other.name; + } + + let signatures = core::mem::take(&mut self.signatures); + self.signatures = IntoIterator::into_iter(signatures) + .chain(IntoIterator::into_iter(other.signatures)) + .collect(); + self.arg_count = self + .signatures + .iter() + .fold(ArgCount::default(), |mut count, sig| { + count.add(sig.arg_count()); + count + }); + } +} + +impl TryFrom for FunctionInfo { + type Error = ArgCountOutOfBoundsError; + + fn try_from(signature: SignatureInfo) -> Result { + let count = signature.arg_count(); + if count > ArgCount::MAX_COUNT { + return Err(ArgCountOutOfBoundsError(count)); + } + + Ok(Self::new(signature)) + } +} + +impl TryFrom> for FunctionInfo { + type Error = FunctionOverloadError; + + fn try_from(signatures: Vec) -> Result { + Self::try_from_iter(signatures) + } +} + +impl TryFrom<[SignatureInfo; N]> for FunctionInfo { + type Error = FunctionOverloadError; + + fn try_from(signatures: [SignatureInfo; N]) -> Result { + Self::try_from_iter(signatures) + } +} + +#[derive(Debug, Clone)] +pub struct SignatureInfo { + name: Option>, + args: Box<[ArgInfo]>, + return_info: ReturnInfo, +} + +impl SignatureInfo { + /// Create a new [`SignatureInfo`] for a function with the given name. pub fn named(name: impl Into>) -> Self { Self { name: Some(name.into()), - args: Vec::new(), + args: Box::new([]), return_info: ReturnInfo::new::<()>(), } } - /// Create a new [`FunctionInfo`] with no name. + /// Create a new [`SignatureInfo`] with no name. /// /// For the purposes of debugging and [registration], - /// it's recommended to use [`FunctionInfo::named`] instead. + /// it's recommended to use [`Self::named`] instead. /// /// [registration]: crate::func::FunctionRegistry pub fn anonymous() -> Self { Self { name: None, - args: Vec::new(), + args: Box::new([]), return_info: ReturnInfo::new::<()>(), } } - /// Create a new [`FunctionInfo`] from the given function. - pub fn from(function: &F) -> Self - where - F: TypedFunction, - { - function.get_function_info() - } - /// Set the name of the function. pub fn with_name(mut self, name: impl Into>) -> Self { self.name = Some(name.into()); @@ -72,7 +284,9 @@ impl FunctionInfo { name: impl Into>, ) -> Self { let index = self.args.len(); - self.args.push(ArgInfo::new::(index).with_name(name)); + self.args = IntoIterator::into_iter(self.args) + .chain(Some(ArgInfo::new::(index).with_name(name))) + .collect(); self } @@ -83,7 +297,7 @@ impl FunctionInfo { /// It's preferable to use [`Self::with_arg`] to add arguments to the function /// as it will automatically set the index of the argument. pub fn with_args(mut self, args: Vec) -> Self { - self.args = args; + self.args = IntoIterator::into_iter(self.args).chain(args).collect(); self } @@ -167,6 +381,161 @@ impl ReturnInfo { } } +/// A wrapper around [`FunctionInfo`] that implements [`Debug`] for pretty-printing function information. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{FunctionInfo, PrettyPrintFunctionInfo, TypedFunction}; +/// # +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let info = add.get_function_info(); +/// +/// let pretty = PrettyPrintFunctionInfo::new(&info); +/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); +/// ``` +pub struct PrettyPrintFunctionInfo<'a> { + info: &'a FunctionInfo, + include_fn_token: bool, + include_name: bool, +} + +impl<'a> PrettyPrintFunctionInfo<'a> { + /// Create a new pretty-printer for the given [`FunctionInfo`]. + pub fn new(info: &'a FunctionInfo) -> Self { + Self { + info, + include_fn_token: false, + include_name: false, + } + } + + /// Include the function name in the pretty-printed output. + pub fn include_name(mut self) -> Self { + self.include_name = true; + self + } + + /// Include the `fn` token in the pretty-printed output. + pub fn include_fn_token(mut self) -> Self { + self.include_fn_token = true; + self + } +} + +impl<'a> Debug for PrettyPrintFunctionInfo<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + if self.include_fn_token { + write!(f, "fn")?; + + if self.include_name { + write!(f, " ")?; + } + } + + match (self.include_name, self.info.name()) { + (true, Some(name)) => write!(f, "{}", name)?, + (true, None) => write!(f, "_")?, + _ => {} + } + + if self.info.is_overloaded() { + // `{(arg0: i32, arg1: i32) -> (), (arg0: f32, arg1: f32) -> ()}` + let mut set = f.debug_set(); + for signature in self.info.signatures() { + set.entry(&PrettyPrintSignatureInfo::new(signature)); + } + set.finish() + } else { + // `(arg0: i32, arg1: i32) -> ()` + PrettyPrintSignatureInfo::new(self.info.base()).fmt(f) + } + } +} + +/// A wrapper around [`SignatureInfo`] that implements [`Debug`] for pretty-printing function signature information. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::func::{FunctionInfo, PrettyPrintSignatureInfo, TypedFunction}; +/// # +/// fn add(a: i32, b: i32) -> i32 { +/// a + b +/// } +/// +/// let info = add.get_function_info(); +/// +/// let pretty = PrettyPrintSignatureInfo::new(info.base()); +/// assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); +/// ``` +pub struct PrettyPrintSignatureInfo<'a> { + info: &'a SignatureInfo, + include_fn_token: bool, + include_name: bool, +} + +impl<'a> PrettyPrintSignatureInfo<'a> { + /// Create a new pretty-printer for the given [`SignatureInfo`]. + pub fn new(info: &'a SignatureInfo) -> Self { + Self { + info, + include_fn_token: false, + include_name: false, + } + } + + /// Include the function name in the pretty-printed output. + pub fn include_name(mut self) -> Self { + self.include_name = true; + self + } + + /// Include the `fn` token in the pretty-printed output. + pub fn include_fn_token(mut self) -> Self { + self.include_fn_token = true; + self + } +} + +impl<'a> Debug for PrettyPrintSignatureInfo<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + if self.include_fn_token { + write!(f, "fn")?; + + if self.include_name { + write!(f, " ")?; + } + } + + match (self.include_name, self.info.name()) { + (true, Some(name)) => write!(f, "{}", name)?, + (true, None) => write!(f, "_")?, + _ => {} + } + + write!(f, "(")?; + + // We manually write the args instead of using `DebugTuple` to avoid trailing commas + // and (when used with `{:#?}`) unnecessary newlines + for (index, arg) in self.info.args().iter().enumerate() { + if index > 0 { + write!(f, ", ")?; + } + + let name = arg.name().unwrap_or("_"); + let ty = arg.type_path(); + write!(f, "{name}: {ty}")?; + } + + let ret = self.info.return_info().type_path(); + write!(f, ") -> {ret}") + } +} + /// A static accessor to compile-time type information for functions. /// /// This is the equivalent of [`Typed`], but for function. @@ -194,7 +563,7 @@ impl ReturnInfo { /// # Example /// /// ``` -/// # use bevy_reflect::func::{ArgList, FunctionInfo, ReflectFnMut, TypedFunction}; +/// # use bevy_reflect::func::{ArgList, ReflectFnMut, TypedFunction}; /// # /// fn print(value: String) { /// println!("{}", value); @@ -202,9 +571,9 @@ impl ReturnInfo { /// /// let info = print.get_function_info(); /// assert!(info.name().unwrap().ends_with("print")); -/// assert_eq!(info.arg_count(), 1); -/// assert_eq!(info.args()[0].type_path(), "alloc::string::String"); -/// assert_eq!(info.return_info().type_path(), "()"); +/// assert!(info.arg_count().contains(1)); +/// assert_eq!(info.base().args()[0].type_path(), "alloc::string::String"); +/// assert_eq!(info.base().return_info().type_path(), "()"); /// ``` /// /// # Trait Parameters @@ -243,18 +612,20 @@ macro_rules! impl_typed_function { Function: FnMut($($Arg),*) -> ReturnType, { fn function_info() -> FunctionInfo { - create_info::() - .with_args({ - #[allow(unused_mut)] - let mut _index = 0; - vec![ - $(ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info(ReturnInfo::new::()) + FunctionInfo::new( + create_info::() + .with_args({ + #[allow(unused_mut)] + let mut _index = 0; + vec![ + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::()) + ) } } @@ -266,20 +637,22 @@ macro_rules! impl_typed_function { for<'a> &'a ReturnType: TypePath + GetOwnership, Function: for<'a> FnMut(&'a Receiver, $($Arg),*) -> &'a ReturnType, { - fn function_info() -> $crate::func::FunctionInfo { - create_info::() - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - ArgInfo::new::<&Receiver>(0), - $($crate::func::args::ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info(ReturnInfo::new::<&ReturnType>()) + fn function_info() -> FunctionInfo { + FunctionInfo::new( + create_info::() + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&Receiver>(0), + $($crate::func::args::ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&ReturnType>()) + ) } } @@ -292,19 +665,21 @@ macro_rules! impl_typed_function { Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a mut ReturnType, { fn function_info() -> FunctionInfo { - create_info::() - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - ArgInfo::new::<&mut Receiver>(0), - $(ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info(ReturnInfo::new::<&mut ReturnType>()) + FunctionInfo::new( + create_info::() + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&mut Receiver>(0), + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&mut ReturnType>()) + ) } } @@ -317,19 +692,21 @@ macro_rules! impl_typed_function { Function: for<'a> FnMut(&'a mut Receiver, $($Arg),*) -> &'a ReturnType, { fn function_info() -> FunctionInfo { - create_info::() - .with_args({ - #[allow(unused_mut)] - let mut _index = 1; - vec![ - ArgInfo::new::<&mut Receiver>(0), - $(ArgInfo::new::<$Arg>({ - _index += 1; - _index - 1 - }),)* - ] - }) - .with_return_info(ReturnInfo::new::<&ReturnType>()) + FunctionInfo::new( + create_info::() + .with_args({ + #[allow(unused_mut)] + let mut _index = 1; + vec![ + ArgInfo::new::<&mut Receiver>(0), + $(ArgInfo::new::<$Arg>({ + _index += 1; + _index - 1 + }),)* + ] + }) + .with_return_info(ReturnInfo::new::<&ReturnType>()) + ) } } }; @@ -355,13 +732,13 @@ all_tuples!(impl_typed_function, 0, 15, Arg, arg); /// | Function pointer | `fn() -> String` | `None` | /// /// [`type_name`]: core::any::type_name -fn create_info() -> FunctionInfo { +fn create_info() -> SignatureInfo { let name = core::any::type_name::(); if name.ends_with("{{closure}}") || name.starts_with("fn(") { - FunctionInfo::anonymous() + SignatureInfo::anonymous() } else { - FunctionInfo::named(name) + SignatureInfo::named(name) } } @@ -386,10 +763,10 @@ mod tests { info.name().unwrap(), "bevy_reflect::func::info::tests::should_create_function_info::add" ); - assert_eq!(info.arg_count(), 2); - assert_eq!(info.args()[0].type_path(), "i32"); - assert_eq!(info.args()[1].type_path(), "i32"); - assert_eq!(info.return_info().type_path(), "i32"); + assert_eq!(info.base().arg_count(), 2); + assert_eq!(info.base().args()[0].type_path(), "i32"); + assert_eq!(info.base().args()[1].type_path(), "i32"); + assert_eq!(info.base().return_info().type_path(), "i32"); } #[test] @@ -405,10 +782,10 @@ mod tests { let info = add.get_function_info(); assert!(info.name().is_none()); - assert_eq!(info.arg_count(), 2); - assert_eq!(info.args()[0].type_path(), "i32"); - assert_eq!(info.args()[1].type_path(), "i32"); - assert_eq!(info.return_info().type_path(), "i32"); + assert_eq!(info.base().arg_count(), 2); + assert_eq!(info.base().args()[0].type_path(), "i32"); + assert_eq!(info.base().args()[1].type_path(), "i32"); + assert_eq!(info.base().return_info().type_path(), "i32"); } #[test] @@ -423,10 +800,10 @@ mod tests { let info = add.get_function_info(); assert!(info.name().is_none()); - assert_eq!(info.arg_count(), 2); - assert_eq!(info.args()[0].type_path(), "i32"); - assert_eq!(info.args()[1].type_path(), "i32"); - assert_eq!(info.return_info().type_path(), "i32"); + assert_eq!(info.base().arg_count(), 2); + assert_eq!(info.base().args()[0].type_path(), "i32"); + assert_eq!(info.base().args()[1].type_path(), "i32"); + assert_eq!(info.base().return_info().type_path(), "i32"); } #[test] @@ -442,9 +819,30 @@ mod tests { let info = add.get_function_info(); assert!(info.name().is_none()); - assert_eq!(info.arg_count(), 2); - assert_eq!(info.args()[0].type_path(), "i32"); - assert_eq!(info.args()[1].type_path(), "i32"); - assert_eq!(info.return_info().type_path(), "()"); + assert_eq!(info.base().arg_count(), 2); + assert_eq!(info.base().args()[0].type_path(), "i32"); + assert_eq!(info.base().args()[1].type_path(), "i32"); + assert_eq!(info.base().return_info().type_path(), "()"); + } + + #[test] + fn should_pretty_print_info() { + // fn add(a: i32, b: i32) -> i32 { + // a + b + // } + // + // let info = add.get_function_info().with_name("add"); + // + // let pretty = info.pretty_printer(); + // assert_eq!(format!("{:?}", pretty), "(_: i32, _: i32) -> i32"); + // + // let pretty = info.pretty_printer().include_fn_token(); + // assert_eq!(format!("{:?}", pretty), "fn(_: i32, _: i32) -> i32"); + // + // let pretty = info.pretty_printer().include_name(); + // assert_eq!(format!("{:?}", pretty), "add(_: i32, _: i32) -> i32"); + // + // let pretty = info.pretty_printer().include_fn_token().include_name(); + // assert_eq!(format!("{:?}", pretty), "fn add(_: i32, _: i32) -> i32"); } } diff --git a/crates/bevy_reflect/src/func/into_function.rs b/crates/bevy_reflect/src/func/into_function.rs index 78a92b05f5953..e913045f8cc2a 100644 --- a/crates/bevy_reflect/src/func/into_function.rs +++ b/crates/bevy_reflect/src/func/into_function.rs @@ -66,6 +66,6 @@ mod tests { fn should_default_closure_name_to_none() { let c = 23; let func = (|a: i32, b: i32| a + b + c).into_function(); - assert_eq!(func.info().name(), None); + assert!(func.name().is_none()); } } diff --git a/crates/bevy_reflect/src/func/into_function_mut.rs b/crates/bevy_reflect/src/func/into_function_mut.rs index a33b840bfc971..8f7f1b0a6dd1a 100644 --- a/crates/bevy_reflect/src/func/into_function_mut.rs +++ b/crates/bevy_reflect/src/func/into_function_mut.rs @@ -81,6 +81,6 @@ mod tests { fn should_default_closure_name_to_none() { let mut total = 0; let func = (|a: i32, b: i32| total = a + b).into_function_mut(); - assert_eq!(func.info().name(), None); + assert!(func.name().is_none()); } } diff --git a/crates/bevy_reflect/src/func/mod.rs b/crates/bevy_reflect/src/func/mod.rs index 2811f5c22663a..f990e135f017d 100644 --- a/crates/bevy_reflect/src/func/mod.rs +++ b/crates/bevy_reflect/src/func/mod.rs @@ -94,6 +94,32 @@ //! For other functions that don't conform to one of the above signatures, //! [`DynamicFunction`] and [`DynamicFunctionMut`] can instead be created manually. //! +//! # Generic Functions +//! +//! In Rust, generic functions are [monomophized] by the compiler, +//! which means that a separate copy of the function is generated for each concrete set of type parameters. +//! +//! When converting a generic function to a [`DynamicFunction`] or [`DynamicFunctionMut`], +//! the function must be manually monomorphized with concrete types. +//! In other words, you cannot write `add.into_function()`. +//! Instead, you will need to write `add::.into_function()`. +//! +//! This means that reflected functions cannot be generic themselves. +//! To get around this limitation, you can consider [overloading] your function with multiple concrete types. +//! +//! # Overloading Functions +//! +//! Both [`DynamicFunction`] and [`DynamicFunctionMut`] support [function overloading]. +//! +//! Function overloading allows one function to handle multiple types of arguments. +//! This is useful for simulating generic functions by having an overload for each known concrete type. +//! Additionally, it can also simulate [variadic functions]: functions that can be called with a variable number of arguments. +//! +//! Internally, this works by storing multiple functions in a map, +//! where each function is associated with a specific argument signature. +//! +//! To learn more, see the docs on [`DynamicFunction::with_overload`]. +//! //! # Function Registration //! //! This module also provides a [`FunctionRegistry`] that can be used to register functions and closures @@ -127,6 +153,10 @@ //! [`Reflect`]: crate::Reflect //! [lack of variadic generics]: https://poignardazur.github.io/2024/05/25/report-on-rustnl-variadics/ //! [coherence issues]: https://doc.rust-lang.org/rustc/lints/listing/warn-by-default.html#coherence-leak-check +//! [monomophized]: https://en.wikipedia.org/wiki/Monomorphization +//! [overloading]: #overloading-functions +//! [function overloading]: https://en.wikipedia.org/wiki/Function_overloading +//! [variadic functions]: https://en.wikipedia.org/wiki/Variadic_function pub use args::{ArgError, ArgList, ArgValue}; pub use dynamic_function::*; @@ -143,6 +173,7 @@ pub use return_type::*; pub mod args; mod dynamic_function; +mod dynamic_function_internal; mod dynamic_function_mut; mod error; mod function; @@ -154,18 +185,19 @@ mod reflect_fn; mod reflect_fn_mut; mod registry; mod return_type; +pub mod signature; #[cfg(test)] mod tests { use alloc::borrow::Cow; + use super::*; + use crate::func::args::ArgCount; use crate::{ func::args::{ArgError, ArgList, Ownership}, TypePath, }; - use super::*; - #[test] fn should_error_on_missing_args() { fn foo(_: i32) {} @@ -176,7 +208,7 @@ mod tests { assert_eq!( result.unwrap_err(), FunctionError::ArgCountMismatch { - expected: 1, + expected: ArgCount::new(1).unwrap(), received: 0 } ); @@ -192,7 +224,7 @@ mod tests { assert_eq!( result.unwrap_err(), FunctionError::ArgCountMismatch { - expected: 0, + expected: ArgCount::new(0).unwrap(), received: 1 } ); diff --git a/crates/bevy_reflect/src/func/reflect_fn.rs b/crates/bevy_reflect/src/func/reflect_fn.rs index 486fa452aa0ef..38a18141fcf43 100644 --- a/crates/bevy_reflect/src/func/reflect_fn.rs +++ b/crates/bevy_reflect/src/func/reflect_fn.rs @@ -5,8 +5,9 @@ use alloc::{boxed::Box, format, vec}; use crate::{ func::{ - args::FromArg, macros::count_tokens, ArgList, FunctionError, FunctionResult, IntoReturn, - ReflectFnMut, + args::{ArgCount, FromArg}, + macros::count_tokens, + ArgList, FunctionError, FunctionResult, IntoReturn, ReflectFnMut, }, Reflect, TypePath, }; @@ -96,7 +97,7 @@ macro_rules! impl_reflect_fn { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -125,7 +126,7 @@ macro_rules! impl_reflect_fn { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -155,7 +156,7 @@ macro_rules! impl_reflect_fn { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -185,7 +186,7 @@ macro_rules! impl_reflect_fn { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } diff --git a/crates/bevy_reflect/src/func/reflect_fn_mut.rs b/crates/bevy_reflect/src/func/reflect_fn_mut.rs index 6a8b9a6d73264..760e657037c5b 100644 --- a/crates/bevy_reflect/src/func/reflect_fn_mut.rs +++ b/crates/bevy_reflect/src/func/reflect_fn_mut.rs @@ -5,7 +5,9 @@ use alloc::{boxed::Box, format, vec}; use crate::{ func::{ - args::FromArg, macros::count_tokens, ArgList, FunctionError, FunctionResult, IntoReturn, + args::{ArgCount, FromArg}, + macros::count_tokens, + ArgList, FunctionError, FunctionResult, IntoReturn, }, Reflect, TypePath, }; @@ -102,7 +104,7 @@ macro_rules! impl_reflect_fn_mut { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -131,7 +133,7 @@ macro_rules! impl_reflect_fn_mut { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -161,7 +163,7 @@ macro_rules! impl_reflect_fn_mut { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } @@ -191,7 +193,7 @@ macro_rules! impl_reflect_fn_mut { if args.len() != COUNT { return Err(FunctionError::ArgCountMismatch { - expected: COUNT, + expected: ArgCount::new(COUNT).unwrap(), received: args.len(), }); } diff --git a/crates/bevy_reflect/src/func/signature.rs b/crates/bevy_reflect/src/func/signature.rs new file mode 100644 index 0000000000000..965ff401e00b8 --- /dev/null +++ b/crates/bevy_reflect/src/func/signature.rs @@ -0,0 +1,234 @@ +//! Function signature types. +//! +//! Function signatures differ from [`FunctionInfo`] and [`SignatureInfo`] in that they +//! are only concerned about the types and order of the arguments and return type of a function. +//! +//! The names of arguments do not matter, +//! nor does any other information about the function such as its name or other attributes. +//! +//! This makes signatures useful for comparing or hashing functions strictly based on their +//! arguments and return type. +//! +//! [`FunctionInfo`]: crate::func::info::FunctionInfo + +use crate::func::args::ArgInfo; +use crate::func::{ArgList, SignatureInfo}; +use crate::Type; +use bevy_utils::hashbrown::Equivalent; +use core::borrow::Borrow; +use core::fmt::{Debug, Formatter}; +use core::hash::{Hash, Hasher}; +use core::ops::{Deref, DerefMut}; + +/// The signature of a function. +/// +/// This can be used as a way to compare or hash functions based on their arguments and return type. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct Signature { + args: ArgumentSignature, + ret: Type, +} + +impl Signature { + /// Create a new function signature with the given argument signature and return type. + pub fn new(args: ArgumentSignature, ret: Type) -> Self { + Self { args, ret } + } + + /// Get the argument signature of the function. + pub fn args(&self) -> &ArgumentSignature { + &self.args + } + + /// Get the return type of the function. + pub fn return_type(&self) -> &Type { + &self.ret + } +} + +impl Debug for Signature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!(f, "{:?} -> {:?}", self.args, self.ret) + } +} + +impl> From for Signature { + fn from(info: T) -> Self { + let info = info.borrow(); + Self::new(ArgumentSignature::from(info), *info.return_info().ty()) + } +} + +/// A wrapper around a borrowed [`ArgList`] that can be used as an +/// [equivalent] of an [`ArgumentSignature`]. +/// +/// [equivalent]: Equivalent +pub(super) struct ArgListSignature<'a, 'b>(&'a ArgList<'b>); + +impl Equivalent for ArgListSignature<'_, '_> { + fn equivalent(&self, key: &ArgumentSignature) -> bool { + self.len() == key.len() && self.iter().eq(key.iter()) + } +} + +impl<'a, 'b> ArgListSignature<'a, 'b> { + pub fn iter(&self) -> impl ExactSizeIterator { + self.0.iter().map(|arg| { + arg.value() + .get_represented_type_info() + .unwrap_or_else(|| { + panic!("no `TypeInfo` found for argument: {:?}", arg); + }) + .ty() + }) + } + + pub fn len(&self) -> usize { + self.0.len() + } +} + +impl Eq for ArgListSignature<'_, '_> {} +impl PartialEq for ArgListSignature<'_, '_> { + fn eq(&self, other: &Self) -> bool { + self.len() == other.len() && self.iter().eq(other.iter()) + } +} + +impl Hash for ArgListSignature<'_, '_> { + fn hash(&self, state: &mut H) { + self.0.iter().for_each(|arg| { + arg.value() + .get_represented_type_info() + .unwrap_or_else(|| { + panic!("no `TypeInfo` found for argument: {:?}", arg); + }) + .ty() + .hash(state); + }); + } +} + +impl<'a, 'b> From<&'a ArgList<'b>> for ArgListSignature<'a, 'b> { + fn from(args: &'a ArgList<'b>) -> Self { + Self(args) + } +} + +/// The argument-portion of a function signature. +/// +/// For example, given a function signature `(a: i32, b: f32) -> u32`, +/// the argument signature would be `(i32, f32)`. +/// +/// This can be used as a way to compare or hash functions based on their arguments. +#[derive(Clone)] +pub struct ArgumentSignature(Box<[Type]>); + +impl Debug for ArgumentSignature { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut tuple = f.debug_tuple(""); + for ty in self.0.iter() { + tuple.field(ty); + } + tuple.finish() + } +} + +impl Deref for ArgumentSignature { + type Target = [Type]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for ArgumentSignature { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl Eq for ArgumentSignature {} + +impl PartialEq for ArgumentSignature { + fn eq(&self, other: &Self) -> bool { + self.0.len() == other.0.len() && self.0.iter().eq(other.0.iter()) + } +} + +impl Hash for ArgumentSignature { + fn hash(&self, state: &mut H) { + self.0.iter().for_each(|ty| ty.hash(state)); + } +} + +impl FromIterator for ArgumentSignature { + fn from_iter>(iter: T) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl> From for ArgumentSignature { + fn from(info: T) -> Self { + Self( + info.borrow() + .args() + .iter() + .map(ArgInfo::ty) + .copied() + .collect(), + ) + } +} + +impl From<&ArgList<'_>> for ArgumentSignature { + fn from(args: &ArgList) -> Self { + Self( + args.iter() + .map(|arg| { + arg.value() + .get_represented_type_info() + .unwrap_or_else(|| { + panic!("no `TypeInfo` found for argument: {:?}", arg); + }) + .ty() + }) + .copied() + .collect(), + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::func::TypedFunction; + + #[test] + fn should_generate_signature_from_function_info() { + fn add(a: i32, b: f32) -> u32 { + (a as f32 + b).round() as u32 + } + + let info = add.get_function_info(); + let signature = Signature::from(info.base()); + + assert_eq!(signature.args().0.len(), 2); + assert_eq!(signature.args().0[0], Type::of::()); + assert_eq!(signature.args().0[1], Type::of::()); + assert_eq!(*signature.return_type(), Type::of::()); + } + + #[test] + fn should_debug_signature() { + let signature = Signature::new( + ArgumentSignature::from_iter(vec![Type::of::<&mut String>(), Type::of::()]), + Type::of::<()>(), + ); + + assert_eq!( + format!("{:?}", signature), + "(&mut alloc::string::String, i32) -> ()" + ); + } +} diff --git a/crates/bevy_reflect/src/impls/foldhash.rs b/crates/bevy_reflect/src/impls/foldhash.rs new file mode 100644 index 0000000000000..a4f1df44efefc --- /dev/null +++ b/crates/bevy_reflect/src/impls/foldhash.rs @@ -0,0 +1,8 @@ +use crate::{self as bevy_reflect, impl_type_path}; + +impl_type_path!(::foldhash::fast::FoldHasher); +impl_type_path!(::foldhash::fast::FixedState); +impl_type_path!(::foldhash::fast::RandomState); +impl_type_path!(::foldhash::quality::FoldHasher); +impl_type_path!(::foldhash::quality::FixedState); +impl_type_path!(::foldhash::quality::RandomState); diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index baeb2999e3632..19fa4eb2b4c18 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -13,7 +13,13 @@ use crate::{ ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, ReflectSerialize, Set, SetInfo, TypeInfo, TypeParamInfo, TypePath, TypeRegistration, TypeRegistry, Typed, }; -use alloc::{borrow::Cow, borrow::ToOwned, boxed::Box, collections::VecDeque, format, vec::Vec}; +use alloc::{ + borrow::{Cow, ToOwned}, + boxed::Box, + collections::VecDeque, + format, + vec::Vec, +}; use bevy_reflect_derive::{impl_reflect, impl_reflect_opaque}; use core::{ any::Any, @@ -832,6 +838,7 @@ macro_rules! impl_reflect_for_hashmap { #[cfg(feature = "std")] impl_reflect_for_hashmap!(::std::collections::HashMap); +impl_type_path!(::core::hash::BuildHasherDefault); #[cfg(feature = "std")] impl_type_path!(::std::collections::hash_map::RandomState); #[cfg(feature = "std")] @@ -846,7 +853,6 @@ crate::func::macros::impl_function_traits!(::std::collections::HashMap; ); impl_reflect_for_hashmap!(bevy_utils::hashbrown::HashMap); -impl_type_path!(::bevy_utils::hashbrown::hash_map::DefaultHashBuilder); impl_type_path!(::bevy_utils::hashbrown::HashMap); #[cfg(feature = "functions")] crate::func::macros::impl_function_traits!(::bevy_utils::hashbrown::HashMap; @@ -1060,7 +1066,7 @@ macro_rules! impl_reflect_for_hashset { } impl_type_path!(::bevy_utils::NoOpHash); -impl_type_path!(::bevy_utils::FixedState); +impl_type_path!(::bevy_utils::FixedHasher); #[cfg(feature = "std")] impl_reflect_for_hashset!(::std::collections::HashSet); @@ -2342,10 +2348,10 @@ mod tests { #[test] fn should_partial_eq_hash_map() { - let mut a = HashMap::new(); + let mut a = >::default(); a.insert(0usize, 1.23_f64); let b = a.clone(); - let mut c = HashMap::new(); + let mut c = >::default(); c.insert(0usize, 3.21_f64); let a: &dyn PartialReflect = &a; diff --git a/crates/bevy_reflect/src/kind.rs b/crates/bevy_reflect/src/kind.rs index 1120ae5d1f7bb..d5d16715c09c4 100644 --- a/crates/bevy_reflect/src/kind.rs +++ b/crates/bevy_reflect/src/kind.rs @@ -297,7 +297,7 @@ mod tests { #[test] fn should_cast_mut() { - let mut value: HashSet = HashSet::new(); + let mut value: HashSet = HashSet::default(); let result = value.reflect_mut().as_set(); assert!(result.is_ok()); diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 07a3c43c12a6e..46253e13d144e 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -578,6 +578,7 @@ mod type_path; mod type_registry; mod impls { + mod foldhash; mod std; #[cfg(feature = "glam")] @@ -1158,11 +1159,11 @@ mod tests { #[derive(Reflect, Eq, PartialEq, Debug)] struct Baz(String); - let mut hash_map = HashMap::default(); + let mut hash_map = >::default(); hash_map.insert(1, 1); hash_map.insert(2, 2); - let mut hash_map_baz = HashMap::default(); + let mut hash_map_baz = >::default(); hash_map_baz.insert(1, Bar { x: 0 }); let mut foo = Foo { @@ -1227,12 +1228,12 @@ mod tests { foo.apply(&foo_patch); - let mut hash_map = HashMap::default(); + let mut hash_map = >::default(); hash_map.insert(1, 1); hash_map.insert(2, 3); hash_map.insert(3, 4); - let mut hash_map_baz = HashMap::default(); + let mut hash_map_baz = >::default(); hash_map_baz.insert(1, Bar { x: 7 }); let expected_foo = Foo { @@ -1251,7 +1252,7 @@ mod tests { let new_foo = Foo::from_reflect(&foo_patch) .expect("error while creating a concrete type from a dynamic type"); - let mut hash_map = HashMap::default(); + let mut hash_map = >::default(); hash_map.insert(2, 3); hash_map.insert(3, 4); @@ -1408,7 +1409,7 @@ mod tests { x: u32, } - let mut hash_map = HashMap::default(); + let mut hash_map = >::default(); hash_map.insert(1, 1); hash_map.insert(2, 2); let foo = Foo { @@ -1497,7 +1498,8 @@ mod tests { assert!(fields[0].reflect_partial_eq(&123_i32).unwrap_or_default()); assert!(fields[1].reflect_partial_eq(&321_i32).unwrap_or_default()); - let mut map_value: Box = Box::new(HashMap::from([(123_i32, 321_i32)])); + let mut map_value: Box = + Box::new([(123_i32, 321_i32)].into_iter().collect::>()); let fields = map_value.drain(); assert!(fields[0].0.reflect_partial_eq(&123_i32).unwrap_or_default()); assert!(fields[0].1.reflect_partial_eq(&321_i32).unwrap_or_default()); @@ -1861,7 +1863,7 @@ mod tests { assert_eq!(usize::type_path(), info.key_ty().path()); assert_eq!(f32::type_path(), info.value_ty().path()); - let value: &dyn Reflect = &MyMap::new(); + let value: &dyn Reflect = &MyMap::default(); let info = value.reflect_type_info(); assert!(info.is::()); @@ -2160,7 +2162,7 @@ mod tests { } } - let mut map = HashMap::new(); + let mut map = >::default(); map.insert(123, 1.23); let test = Test { @@ -2474,7 +2476,7 @@ bevy_reflect::tests::Test { // test reflected value value: u32, } - let mut map = HashMap::new(); + let mut map = >::default(); map.insert(9, 10); let mut test_struct: DynamicStruct = TestStruct { tuple: (0, 1), diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index bafcaf528faec..e5205e90afa38 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -3,11 +3,10 @@ use core::fmt::{Debug, Formatter}; use bevy_reflect_derive::impl_type_path; use bevy_utils::hashbrown::HashTable; -use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, type_info::impl_type_methods, ApplyError, Generics, MaybeTyped, - PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, - TypePath, + self as bevy_reflect, generics::impl_generic_info_methods, type_info::impl_type_methods, + ApplyError, Generics, MaybeTyped, PartialReflect, Reflect, ReflectKind, ReflectMut, + ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, }; use alloc::{boxed::Box, format, vec::Vec}; @@ -31,7 +30,7 @@ use alloc::{boxed::Box, format, vec::Vec}; /// /// ``` /// use bevy_reflect::{PartialReflect, Reflect, Map}; -/// use bevy_utils::HashMap; +/// use std::collections::HashMap; /// /// /// let foo: &mut dyn Map = &mut HashMap::::new(); @@ -569,7 +568,7 @@ pub fn map_partial_eq(a: &M, b: &dyn PartialReflect) -> Option< /// /// # Example /// ``` -/// # use bevy_utils::HashMap; +/// # use std::collections::HashMap; /// use bevy_reflect::Reflect; /// /// let mut my_map = HashMap::new(); diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index b1ada1835e961..e55897166e926 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -26,18 +26,20 @@ mod tuples; mod tests { use bincode::Options; use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; - use serde::de::IgnoredAny; - use serde::Deserializer; + use serde::{de::IgnoredAny, Deserializer}; use serde::{de::DeserializeSeed, Deserialize}; use bevy_utils::{HashMap, HashSet}; - use crate::serde::ReflectDeserializerProcessor; - use crate::{self as bevy_reflect, TypeRegistration}; use crate::{ - serde::{ReflectDeserializer, ReflectSerializer, TypedReflectDeserializer}, - DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistry, + self as bevy_reflect, + serde::{ + ReflectDeserializer, ReflectDeserializerProcessor, ReflectSerializer, + TypedReflectDeserializer, + }, + DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistration, + TypeRegistry, }; #[derive(Reflect, Debug, PartialEq)] @@ -148,10 +150,10 @@ mod tests { } fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); + let mut map = >::default(); map.insert(64, 32); - let mut set = HashSet::new(); + let mut set = >::default(); set.insert(64); MyStruct { diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index ba12b7fb4fc39..53afacde37430 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -26,8 +26,7 @@ mod tests { PartialReflect, Reflect, ReflectSerialize, Struct, TypeRegistry, }; use bevy_utils::{HashMap, HashSet}; - use core::any::TypeId; - use core::{f32::consts::PI, ops::RangeInclusive}; + use core::{any::TypeId, f32::consts::PI, ops::RangeInclusive}; use ron::{extensions::Extensions, ser::PrettyConfig}; use serde::{Serialize, Serializer}; @@ -128,10 +127,10 @@ mod tests { } fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); + let mut map = >::default(); map.insert(64, 32); - let mut set = HashSet::new(); + let mut set = >::default(); set.insert(64); MyStruct { diff --git a/crates/bevy_reflect/src/set.rs b/crates/bevy_reflect/src/set.rs index be66c53318438..0d46d9f9df771 100644 --- a/crates/bevy_reflect/src/set.rs +++ b/crates/bevy_reflect/src/set.rs @@ -4,11 +4,10 @@ use core::fmt::{Debug, Formatter}; use bevy_reflect_derive::impl_type_path; use bevy_utils::hashbrown::{hash_table::OccupiedEntry as HashTableOccupiedEntry, HashTable}; -use crate::generics::impl_generic_info_methods; use crate::{ - self as bevy_reflect, hash_error, type_info::impl_type_methods, ApplyError, Generics, - PartialReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, - TypePath, + self as bevy_reflect, generics::impl_generic_info_methods, hash_error, + type_info::impl_type_methods, ApplyError, Generics, PartialReflect, Reflect, ReflectKind, + ReflectMut, ReflectOwned, ReflectRef, Type, TypeInfo, TypePath, }; /// A trait used to power [set-like] operations via [reflection]. @@ -31,7 +30,7 @@ use crate::{ /// /// ``` /// use bevy_reflect::{PartialReflect, Set}; -/// use bevy_utils::HashSet; +/// use std::collections::HashSet; /// /// /// let foo: &mut dyn Set = &mut HashSet::::new(); @@ -432,7 +431,7 @@ pub fn set_partial_eq(a: &M, b: &dyn PartialReflect) -> Option { /// /// # Example /// ``` -/// # use bevy_utils::HashSet; +/// # use std::collections::HashSet; /// use bevy_reflect::Reflect; /// /// let mut my_set = HashSet::new(); diff --git a/crates/bevy_reflect/src/utility.rs b/crates/bevy_reflect/src/utility.rs index 5395b2b28ae64..f106baf622135 100644 --- a/crates/bevy_reflect/src/utility.rs +++ b/crates/bevy_reflect/src/utility.rs @@ -2,7 +2,7 @@ use crate::TypeInfo; use alloc::boxed::Box; -use bevy_utils::{FixedState, NoOpHash, TypeIdMap}; +use bevy_utils::{DefaultHasher, FixedHasher, NoOpHash, TypeIdMap}; use core::{ any::{Any, TypeId}, hash::BuildHasher, @@ -315,6 +315,6 @@ impl Default for GenericTypeCell { /// /// [`Reflect::reflect_hash`]: crate::Reflect #[inline] -pub fn reflect_hasher() -> bevy_utils::AHasher { - FixedState.build_hasher() +pub fn reflect_hasher() -> DefaultHasher { + FixedHasher.build_hasher() } diff --git a/crates/bevy_remote/src/builtin_methods.rs b/crates/bevy_remote/src/builtin_methods.rs index a3c38e8c3d379..7cf59c825b194 100644 --- a/crates/bevy_remote/src/builtin_methods.rs +++ b/crates/bevy_remote/src/builtin_methods.rs @@ -58,7 +58,7 @@ pub const BRP_LIST_AND_WATCH_METHOD: &str = "bevy/list+watch"; /// ID. /// /// The server responds with a [`BrpGetResponse`]. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpGetParams { /// The ID of the entity from which components are to be requested. pub entity: Entity, @@ -83,7 +83,7 @@ pub struct BrpGetParams { /// and component values that match. /// /// The server responds with a [`BrpQueryResponse`]. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpQueryParams { /// The components to select. pub data: BrpQuery, @@ -98,7 +98,7 @@ pub struct BrpQueryParams { /// with its ID. /// /// The server responds with a [`BrpSpawnResponse`]. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpSpawnParams { /// A map from each component's full path to its serialized value. /// @@ -115,7 +115,7 @@ pub struct BrpSpawnParams { /// `bevy/destroy`: Given an ID, despawns the entity with that ID. /// /// The server responds with an okay. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpDestroyParams { /// The ID of the entity to despawn. pub entity: Entity, @@ -124,7 +124,7 @@ pub struct BrpDestroyParams { /// `bevy/remove`: Deletes one or more components from an entity. /// /// The server responds with a null. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpRemoveParams { /// The ID of the entity from which components are to be removed. pub entity: Entity, @@ -143,7 +143,7 @@ pub struct BrpRemoveParams { /// `bevy/insert`: Adds one or more components to an entity. /// /// The server responds with a null. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpInsertParams { /// The ID of the entity that components are to be added to. pub entity: Entity, @@ -163,7 +163,7 @@ pub struct BrpInsertParams { /// `bevy/reparent`: Assign a new parent to one or more entities. /// /// The server responds with a null. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpReparentParams { /// The IDs of the entities that are to become the new children of the /// `parent`. @@ -181,14 +181,14 @@ pub struct BrpReparentParams { /// system (no params provided), or those on an entity (params provided). /// /// The server responds with a [`BrpListResponse`] -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpListParams { /// The entity to query. pub entity: Entity, } /// Describes the data that is to be fetched in a query. -#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] pub struct BrpQuery { /// The [full path] of the type name of each component that is to be /// fetched. @@ -214,7 +214,7 @@ pub struct BrpQuery { /// Additional constraints that can be placed on a query to include or exclude /// certain entities. -#[derive(Debug, Serialize, Deserialize, Clone, Default)] +#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq)] pub struct BrpQueryFilter { /// The [full path] of the type name of each component that must not be /// present on the entity for it to be included in the results. @@ -234,14 +234,14 @@ pub struct BrpQueryFilter { /// A response from the world to the client that specifies a single entity. /// /// This is sent in response to `bevy/spawn`. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpSpawnResponse { /// The ID of the entity in question. pub entity: Entity, } /// The response to a `bevy/get` request. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] pub enum BrpGetResponse { /// The non-strict response that reports errors separately without failing the entire request. @@ -257,7 +257,7 @@ pub enum BrpGetResponse { } /// A single response from a `bevy/get+watch` request. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] #[serde(untagged)] pub enum BrpGetWatchingResponse { /// The non-strict response that reports errors separately without failing the entire request. @@ -285,7 +285,7 @@ pub enum BrpGetWatchingResponse { pub type BrpListResponse = Vec; /// A single response from a `bevy/list+watch` request. -#[derive(Debug, Default, Serialize, Deserialize, Clone)] +#[derive(Debug, Default, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpListWatchingResponse { added: Vec, removed: Vec, @@ -295,7 +295,7 @@ pub struct BrpListWatchingResponse { pub type BrpQueryResponse = Vec; /// One query match result: a single entity paired with the requested components. -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)] pub struct BrpQueryRow { /// The ID of the entity that matched. pub entity: Entity, @@ -364,7 +364,7 @@ pub fn process_remote_get_watching_request( let mut changed = Vec::new(); let mut removed = Vec::new(); - let mut errors = HashMap::new(); + let mut errors = >::default(); 'component_loop: for component_path in components { let Ok(type_registration) = @@ -847,7 +847,7 @@ fn build_components_map<'a>( paths_and_reflect_components: impl Iterator, type_registry: &TypeRegistry, ) -> AnyhowResult> { - let mut serialized_components_map = HashMap::new(); + let mut serialized_components_map = >::default(); for (type_path, reflect_component) in paths_and_reflect_components { let Some(reflected) = reflect_component.reflect(entity_ref.clone()) else { @@ -873,7 +873,7 @@ fn build_has_map<'a>( entity_ref: FilteredEntityRef, paths_and_reflect_components: impl Iterator, ) -> HashMap { - let mut has_map = HashMap::new(); + let mut has_map = >::default(); for (type_path, reflect_component) in paths_and_reflect_components { let has = reflect_component.contains(entity_ref.clone()); @@ -923,7 +923,7 @@ fn deserialize_components( let reflected: Box = TypedReflectDeserializer::new(component_type, type_registry) .deserialize(&component) - .unwrap(); + .map_err(|err| anyhow!("{component_path} is invalid: {err}"))?; reflect_components.push(reflected); } @@ -969,3 +969,40 @@ fn get_component_type_registration<'r>( .get_with_type_path(component_path) .ok_or_else(|| anyhow!("Unknown component type: `{}`", component_path)) } + +#[cfg(test)] +mod tests { + /// A generic function that tests serialization and deserialization of any type + /// implementing Serialize and Deserialize traits. + fn test_serialize_deserialize(value: T) + where + T: Serialize + for<'a> Deserialize<'a> + PartialEq + core::fmt::Debug, + { + // Serialize the value to JSON string + let serialized = serde_json::to_string(&value).expect("Failed to serialize"); + + // Deserialize the JSON string back into the original type + let deserialized: T = serde_json::from_str(&serialized).expect("Failed to deserialize"); + + // Assert that the deserialized value is the same as the original + assert_eq!( + &value, &deserialized, + "Deserialized value does not match original" + ); + } + use super::*; + + #[test] + fn serialization_tests() { + test_serialize_deserialize(BrpQueryRow { + components: Default::default(), + entity: Entity::from_raw(0), + has: Default::default(), + }); + test_serialize_deserialize(BrpListWatchingResponse::default()); + test_serialize_deserialize(BrpQuery::default()); + test_serialize_deserialize(BrpListParams { + entity: Entity::from_raw(0), + }); + } +} diff --git a/crates/bevy_remote/src/http.rs b/crates/bevy_remote/src/http.rs index 321f6aa6bf497..04c99ea21010f 100644 --- a/crates/bevy_remote/src/http.rs +++ b/crates/bevy_remote/src/http.rs @@ -17,23 +17,25 @@ use async_io::Async; use bevy_app::{App, Plugin, Startup}; use bevy_ecs::system::{Res, Resource}; use bevy_tasks::{futures_lite::StreamExt, IoTaskPool}; -use core::net::{IpAddr, Ipv4Addr}; use core::{ convert::Infallible, + net::{IpAddr, Ipv4Addr}, pin::Pin, task::{Context, Poll}, }; use http_body_util::{BodyExt as _, Full}; -use hyper::header::{HeaderName, HeaderValue}; use hyper::{ body::{Body, Bytes, Frame, Incoming}, + header::{HeaderName, HeaderValue}, server::conn::http1, service, Request, Response, }; use serde_json::Value; use smol_hyper::rt::{FuturesIo, SmolTimer}; -use std::collections::HashMap; -use std::net::{TcpListener, TcpStream}; +use std::{ + collections::HashMap, + net::{TcpListener, TcpStream}, +}; /// The default port that Bevy will listen on. /// @@ -57,7 +59,7 @@ impl Headers { /// Create a new instance of `Headers`. pub fn new() -> Self { Self { - headers: HashMap::new(), + headers: HashMap::default(), } } diff --git a/crates/bevy_remote/src/lib.rs b/crates/bevy_remote/src/lib.rs index 576ee7dbf18bc..103afb0327174 100644 --- a/crates/bevy_remote/src/lib.rs +++ b/crates/bevy_remote/src/lib.rs @@ -800,7 +800,7 @@ fn process_remote_requests(world: &mut World) { match handler { RemoteMethodSystemId::Instant(id) => { - let result = match world.run_system_with_input(id, message.params) { + let result = match world.run_system_with(id, message.params) { Ok(result) => result, Err(error) => { let _ = message.sender.force_send(Err(BrpError { @@ -850,7 +850,7 @@ fn process_single_ongoing_watching_request( system_id: &RemoteWatchingMethodSystemId, ) -> BrpResult> { world - .run_system_with_input(*system_id, message.params.clone()) + .run_system_with(*system_id, message.params.clone()) .map_err(|error| BrpError { code: error_codes::INTERNAL_ERROR, message: format!("Failed to run method handler: {error}"), diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 0fceb795bd7ee..a31db478fba8f 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -443,7 +443,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { if render_device.features().contains( #render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY | #render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY - ) && render_device.limits().max_storage_buffers_per_shader_stage > 0 { + ) && render_device.limits().max_storage_buffers_per_shader_stage > 0 && + !force_no_bindless { ( #render_path::render_resource::BufferBindingType::Storage { read_only: true }, #render_path::render_resource::BufferUsages::STORAGE, @@ -571,7 +572,8 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { let #actual_bindless_slot_count = if render_device.features().contains( #render_path::settings::WgpuFeatures::BUFFER_BINDING_ARRAY | #render_path::settings::WgpuFeatures::TEXTURE_BINDING_ARRAY - ) && render_device.limits().max_storage_buffers_per_shader_stage > 0 { + ) && render_device.limits().max_storage_buffers_per_shader_stage > 0 && + !force_no_bindless { ::core::num::NonZeroU32::new(#bindless_count) } else { None @@ -607,6 +609,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { layout: &#render_path::render_resource::BindGroupLayout, render_device: &#render_path::renderer::RenderDevice, (images, fallback_image, storage_buffers): &mut #ecs_path::system::SystemParamItem<'_, '_, Self::Param>, + force_no_bindless: bool, ) -> Result<#render_path::render_resource::UnpreparedBindGroup, #render_path::render_resource::AsBindGroupError> { #uniform_binding_type_declarations @@ -618,7 +621,10 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { }) } - fn bind_group_layout_entries(render_device: &#render_path::renderer::RenderDevice) -> Vec<#render_path::render_resource::BindGroupLayoutEntry> { + fn bind_group_layout_entries( + render_device: &#render_path::renderer::RenderDevice, + force_no_bindless: bool + ) -> Vec<#render_path::render_resource::BindGroupLayoutEntry> { #actual_bindless_slot_count_declaration #uniform_binding_type_declarations diff --git a/crates/bevy_render/src/camera/camera.rs b/crates/bevy_render/src/camera/camera.rs index f0603157fb4fe..3c375d935cd72 100644 --- a/crates/bevy_render/src/camera/camera.rs +++ b/crates/bevy_render/src/camera/camera.rs @@ -6,7 +6,7 @@ use crate::{ render_asset::RenderAssets, render_graph::{InternedRenderSubGraph, RenderSubGraph}, render_resource::TextureView, - sync_world::{RenderEntity, SyncToRenderWorld, TemporaryRenderEntity}, + sync_world::{RenderEntity, SyncToRenderWorld}, texture::GpuImage, view::{ ColorGrading, ExtractedView, ExtractedWindows, GpuCulling, Msaa, RenderLayers, @@ -887,7 +887,7 @@ pub fn camera_system>( ) { let primary_window = primary_window.iter().next(); - let mut changed_window_ids = HashSet::new(); + let mut changed_window_ids = >::default(); changed_window_ids.extend(window_created_events.read().map(|event| event.window)); changed_window_ids.extend(window_resized_events.read().map(|event| event.window)); let scale_factor_changed_window_ids: HashSet<_> = window_scale_factor_changed_events @@ -926,7 +926,9 @@ pub fn camera_system>( // This can happen when the window is moved between monitors with different DPIs. // Without this, the viewport will take a smaller portion of the window moved to // a higher DPI monitor. - if normalized_target.is_changed(&scale_factor_changed_window_ids, &HashSet::new()) { + if normalized_target + .is_changed(&scale_factor_changed_window_ids, &HashSet::default()) + { if let (Some(new_scale_factor), Some(old_scale_factor)) = ( new_computed_target_info .as_ref() @@ -1098,9 +1100,7 @@ pub fn extract_cameras( .get(*entity) .cloned() .map(|entity| entity.id()) - .unwrap_or_else(|_e| { - commands.spawn(TemporaryRenderEntity).id() - }); + .unwrap_or(Entity::PLACEHOLDER); (render_entity, (*entity).into()) }) .collect(); @@ -1201,8 +1201,8 @@ pub fn sort_cameras( ord => ord, }); let mut previous_order_target = None; - let mut ambiguities = HashSet::new(); - let mut target_counts = HashMap::new(); + let mut ambiguities = >::default(); + let mut target_counts = >::default(); for sorted_camera in &mut sorted_cameras.0 { let new_order_target = (sorted_camera.order, sorted_camera.target.clone()); if let Some(previous_order_target) = previous_order_target { diff --git a/crates/bevy_render/src/camera/camera_driver_node.rs b/crates/bevy_render/src/camera/camera_driver_node.rs index f01d781478783..274b12bca84fa 100644 --- a/crates/bevy_render/src/camera/camera_driver_node.rs +++ b/crates/bevy_render/src/camera/camera_driver_node.rs @@ -32,7 +32,7 @@ impl Node for CameraDriverNode { ) -> Result<(), NodeRunError> { let sorted_cameras = world.resource::(); let windows = world.resource::(); - let mut camera_windows = HashSet::new(); + let mut camera_windows = >::default(); for sorted_camera in &sorted_cameras.0 { let Ok(camera) = self.cameras.get_manual(world, sorted_camera.entity) else { continue; diff --git a/crates/bevy_render/src/mesh/allocator.rs b/crates/bevy_render/src/mesh/allocator.rs index 4e96078464ed3..6e7afaa4d7cde 100644 --- a/crates/bevy_render/src/mesh/allocator.rs +++ b/crates/bevy_render/src/mesh/allocator.rs @@ -15,11 +15,7 @@ use bevy_ecs::{ system::{Res, ResMut, Resource}, world::{FromWorld, World}, }; -use bevy_utils::{ - default, - hashbrown::{HashMap, HashSet}, - tracing::error, -}; +use bevy_utils::{default, tracing::error, HashMap, HashSet}; use offset_allocator::{Allocation, Allocator}; use wgpu::{ BufferDescriptor, BufferSize, BufferUsages, CommandEncoderDescriptor, DownlevelFlags, @@ -329,10 +325,10 @@ impl FromWorld for MeshAllocator { .contains(DownlevelFlags::BASE_VERTEX); Self { - slabs: HashMap::new(), - slab_layouts: HashMap::new(), - mesh_id_to_vertex_slab: HashMap::new(), - mesh_id_to_index_slab: HashMap::new(), + slabs: HashMap::default(), + slab_layouts: HashMap::default(), + mesh_id_to_vertex_slab: HashMap::default(), + mesh_id_to_index_slab: HashMap::default(), next_slab_id: default(), general_vertex_slabs_supported, } @@ -600,7 +596,7 @@ impl MeshAllocator { } fn free_meshes(&mut self, extracted_meshes: &ExtractedAssets) { - let mut empty_slabs = HashSet::new(); + let mut empty_slabs = >::default(); for mesh_id in &extracted_meshes.removed { if let Some(slab_id) = self.mesh_id_to_vertex_slab.remove(mesh_id) { self.free_allocation_in_slab(mesh_id, slab_id, &mut empty_slabs); @@ -881,8 +877,8 @@ impl GeneralSlab { let mut new_slab = GeneralSlab { allocator: Allocator::new(slab_slot_capacity), buffer: None, - resident_allocations: HashMap::new(), - pending_allocations: HashMap::new(), + resident_allocations: HashMap::default(), + pending_allocations: HashMap::default(), element_layout: layout, slot_capacity: slab_slot_capacity, }; diff --git a/crates/bevy_render/src/render_asset.rs b/crates/bevy_render/src/render_asset.rs index 047c2604b2321..2757dceb9fa81 100644 --- a/crates/bevy_render/src/render_asset.rs +++ b/crates/bevy_render/src/render_asset.rs @@ -233,8 +233,8 @@ pub(crate) fn extract_render_asset( |world, mut cached_state: Mut>| { let (mut events, mut assets) = cached_state.state.get_mut(world); - let mut changed_assets = HashSet::default(); - let mut removed = HashSet::default(); + let mut changed_assets = >::default(); + let mut removed = >::default(); for event in events.read() { #[allow(clippy::match_same_arms)] @@ -254,7 +254,7 @@ pub(crate) fn extract_render_asset( } let mut extracted_assets = Vec::new(); - let mut added = HashSet::new(); + let mut added = >::default(); for id in changed_assets.drain() { if let Some(asset) = assets.get(id) { let asset_usage = A::asset_usage(asset); diff --git a/crates/bevy_render/src/render_resource/bind_group.rs b/crates/bevy_render/src/render_resource/bind_group.rs index 99f1812319f74..1a42eb00415b9 100644 --- a/crates/bevy_render/src/render_resource/bind_group.rs +++ b/crates/bevy_render/src/render_resource/bind_group.rs @@ -352,7 +352,7 @@ pub trait AsBindGroup { param: &mut SystemParamItem<'_, '_, Self::Param>, ) -> Result, AsBindGroupError> { let UnpreparedBindGroup { bindings, data } = - Self::unprepared_bind_group(self, layout, render_device, param)?; + Self::unprepared_bind_group(self, layout, render_device, param, false)?; let entries = bindings .iter() @@ -372,30 +372,45 @@ pub trait AsBindGroup { } /// Returns a vec of (binding index, `OwnedBindingResource`). - /// In cases where `OwnedBindingResource` is not available (as for bindless texture arrays currently), - /// an implementor may return `AsBindGroupError::CreateBindGroupDirectly` - /// from this function and instead define `as_bind_group` directly. This may - /// prevent certain features, such as bindless mode, from working correctly. + /// + /// In cases where `OwnedBindingResource` is not available (as for bindless + /// texture arrays currently), an implementor may return + /// `AsBindGroupError::CreateBindGroupDirectly` from this function and + /// instead define `as_bind_group` directly. This may prevent certain + /// features, such as bindless mode, from working correctly. + /// + /// Set `force_no_bindless` to true to require that bindless textures *not* + /// be used. `ExtendedMaterial` uses this in order to ensure that the base + /// material doesn't use bindless mode if the extension doesn't. fn unprepared_bind_group( &self, layout: &BindGroupLayout, render_device: &RenderDevice, param: &mut SystemParamItem<'_, '_, Self::Param>, + force_no_bindless: bool, ) -> Result, AsBindGroupError>; - /// Creates the bind group layout matching all bind groups returned by [`AsBindGroup::as_bind_group`] + /// Creates the bind group layout matching all bind groups returned by + /// [`AsBindGroup::as_bind_group`] fn bind_group_layout(render_device: &RenderDevice) -> BindGroupLayout where Self: Sized, { render_device.create_bind_group_layout( Self::label(), - &Self::bind_group_layout_entries(render_device), + &Self::bind_group_layout_entries(render_device, false), ) } /// Returns a vec of bind group layout entries - fn bind_group_layout_entries(render_device: &RenderDevice) -> Vec + /// + /// Set `force_no_bindless` to true to require that bindless textures *not* + /// be used. `ExtendedMaterial` uses this in order to ensure that the base + /// material doesn't use bindless mode if the extension doesn't. + fn bind_group_layout_entries( + render_device: &RenderDevice, + force_no_bindless: bool, + ) -> Vec where Self: Sized; } diff --git a/crates/bevy_render/src/render_resource/pipeline_specializer.rs b/crates/bevy_render/src/render_resource/pipeline_specializer.rs index 55c13b3ab154c..3ee7a78ed7793 100644 --- a/crates/bevy_render/src/render_resource/pipeline_specializer.rs +++ b/crates/bevy_render/src/render_resource/pipeline_specializer.rs @@ -10,7 +10,7 @@ use bevy_utils::{ default, hashbrown::hash_map::{RawEntryMut, VacantEntry}, tracing::error, - Entry, HashMap, + Entry, FixedHasher, HashMap, }; use core::{fmt::Debug, hash::Hash}; use thiserror::Error; @@ -132,7 +132,11 @@ impl SpecializedMeshPipelines { specialize_pipeline: &S, key: S::Key, layout: &MeshVertexBufferLayoutRef, - entry: VacantEntry<(MeshVertexBufferLayoutRef, S::Key), CachedRenderPipelineId>, + entry: VacantEntry< + (MeshVertexBufferLayoutRef, S::Key), + CachedRenderPipelineId, + FixedHasher, + >, ) -> Result where S: SpecializedMeshPipeline, diff --git a/crates/bevy_render/src/view/mod.rs b/crates/bevy_render/src/view/mod.rs index 22af7ffb44b21..a626a17d1d592 100644 --- a/crates/bevy_render/src/view/mod.rs +++ b/crates/bevy_render/src/view/mod.rs @@ -923,7 +923,7 @@ pub fn prepare_view_targets( )>, view_target_attachments: Res, ) { - let mut textures = HashMap::default(); + let mut textures = >::default(); for (entity, camera, view, texture_usage, msaa) in cameras.iter() { let (Some(target_size), Some(target)) = (camera.physical_target_size, &camera.target) else { diff --git a/crates/bevy_scene/src/scene_filter.rs b/crates/bevy_scene/src/scene_filter.rs index eb86984903c79..062b218165c0c 100644 --- a/crates/bevy_scene/src/scene_filter.rs +++ b/crates/bevy_scene/src/scene_filter.rs @@ -47,7 +47,7 @@ impl SceneFilter { /// /// [`Denylist`]: SceneFilter::Denylist pub fn allow_all() -> Self { - Self::Denylist(HashSet::new()) + Self::Denylist(HashSet::default()) } /// Creates a filter where all types are denied. @@ -56,7 +56,7 @@ impl SceneFilter { /// /// [`Allowlist`]: SceneFilter::Allowlist pub fn deny_all() -> Self { - Self::Allowlist(HashSet::new()) + Self::Allowlist(HashSet::default()) } /// Allow the given type, `T`. @@ -88,7 +88,7 @@ impl SceneFilter { pub fn allow_by_id(mut self, type_id: TypeId) -> Self { match &mut self { Self::Unset => { - self = Self::Allowlist(HashSet::from([type_id])); + self = Self::Allowlist([type_id].into_iter().collect()); } Self::Allowlist(list) => { list.insert(type_id); @@ -128,7 +128,7 @@ impl SceneFilter { #[must_use] pub fn deny_by_id(mut self, type_id: TypeId) -> Self { match &mut self { - Self::Unset => self = Self::Denylist(HashSet::from([type_id])), + Self::Unset => self = Self::Denylist([type_id].into_iter().collect()), Self::Allowlist(list) => { list.remove(&type_id); } @@ -222,7 +222,7 @@ impl IntoIterator for SceneFilter { fn into_iter(self) -> Self::IntoIter { match self { - Self::Unset => HashSet::new().into_iter(), + Self::Unset => Default::default(), Self::Allowlist(list) | Self::Denylist(list) => list.into_iter(), } } diff --git a/crates/bevy_scene/src/scene_spawner.rs b/crates/bevy_scene/src/scene_spawner.rs index 2f660c9903750..32d829b8c726a 100644 --- a/crates/bevy_scene/src/scene_spawner.rs +++ b/crates/bevy_scene/src/scene_spawner.rs @@ -319,7 +319,7 @@ impl SceneSpawner { let spawned = self .spawned_dynamic_scenes .entry(handle.id()) - .or_insert_with(HashSet::new); + .or_insert_with(HashSet::default); spawned.insert(instance_id); // Scenes with parents need more setup before they are ready. @@ -426,7 +426,7 @@ impl SceneSpawner { pub fn scene_spawner_system(world: &mut World) { world.resource_scope(|world, mut scene_spawner: Mut| { // remove any loading instances where parent is deleted - let mut dead_instances = HashSet::default(); + let mut dead_instances = >::default(); scene_spawner .scenes_with_parent .retain(|(instance, parent)| { diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index e1498ccc1c978..0c74b8e7ee894 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -476,7 +476,7 @@ impl<'a, 'de> Visitor<'de> for SceneMapVisitor<'a> { where A: MapAccess<'de>, { - let mut added = HashSet::new(); + let mut added = >::default(); let mut entries = Vec::new(); while let Some(registration) = map.next_key_seed(TypeRegistrationDeserializer::new(self.registry))? diff --git a/crates/bevy_sprite/src/texture_atlas_builder.rs b/crates/bevy_sprite/src/texture_atlas_builder.rs index 92d62b619d85d..695e00f437e0d 100644 --- a/crates/bevy_sprite/src/texture_atlas_builder.rs +++ b/crates/bevy_sprite/src/texture_atlas_builder.rs @@ -271,7 +271,7 @@ impl<'a> TextureAtlasBuilder<'a> { let rect_placements = rect_placements.ok_or(TextureAtlasBuilderError::NotEnoughSpace)?; let mut texture_rects = Vec::with_capacity(rect_placements.packed_locations().len()); - let mut texture_ids = HashMap::default(); + let mut texture_ids = >::default(); // We iterate through the textures to place to respect the insertion order for the texture indices for (index, (image_id, texture)) in self.textures_to_place.iter().enumerate() { let (_, packed_location) = rect_placements.packed_locations().get(&index).unwrap(); diff --git a/crates/bevy_text/src/font_atlas_set.rs b/crates/bevy_text/src/font_atlas_set.rs index 5547ea5fdbd83..60374daf49bfc 100644 --- a/crates/bevy_text/src/font_atlas_set.rs +++ b/crates/bevy_text/src/font_atlas_set.rs @@ -200,11 +200,12 @@ impl FontAtlasSet { }) } - /// Returns the number of font atlases in this set + /// Returns the number of font atlases in this set. pub fn len(&self) -> usize { self.font_atlases.len() } - /// Returns the number of font atlases in this set + + /// Returns `true` if the set has no font atlases. pub fn is_empty(&self) -> bool { self.font_atlases.len() == 0 } diff --git a/crates/bevy_ui/src/layout/mod.rs b/crates/bevy_ui/src/layout/mod.rs index 15e15059852c0..315340244eda3 100644 --- a/crates/bevy_ui/src/layout/mod.rs +++ b/crates/bevy_ui/src/layout/mod.rs @@ -710,7 +710,7 @@ mod tests { ui_child_entities.len() ); - let child_node_map = HashMap::from_iter( + let child_node_map = >::from_iter( ui_child_entities .iter() .map(|child_entity| (*child_entity, ui_surface.entity_to_taffy[child_entity])), @@ -944,7 +944,7 @@ mod tests { new_pos: Vec2, expected_camera_entity: &Entity, ) { - world.run_system_once_with(new_pos, move_ui_node).unwrap(); + world.run_system_once_with(move_ui_node, new_pos).unwrap(); ui_schedule.run(world); let (ui_node_entity, TargetCamera(target_camera_entity)) = world .query_filtered::<(Entity, &TargetCamera), With>() @@ -1234,11 +1234,11 @@ mod tests { } let _ = world.run_system_once_with( + test_system, TestSystemParam { camera_entity, root_node_entity, }, - test_system, ); let ui_surface = world.resource::(); diff --git a/crates/bevy_ui/src/picking_backend.rs b/crates/bevy_ui/src/picking_backend.rs index fb43cd08c4088..279acdb880241 100644 --- a/crates/bevy_ui/src/picking_backend.rs +++ b/crates/bevy_ui/src/picking_backend.rs @@ -29,7 +29,7 @@ use bevy_ecs::{prelude::*, query::QueryData}; use bevy_math::{Rect, Vec2}; use bevy_render::prelude::*; use bevy_transform::prelude::*; -use bevy_utils::hashbrown::HashMap; +use bevy_utils::HashMap; use bevy_window::PrimaryWindow; use bevy_picking::backend::prelude::*; @@ -70,7 +70,7 @@ pub fn ui_picking( mut output: EventWriter, ) { // For each camera, the pointer and its position - let mut pointer_pos_by_camera = HashMap::>::new(); + let mut pointer_pos_by_camera = HashMap::>::default(); for (pointer_id, pointer_location) in pointers.iter().filter_map(|(pointer, pointer_location)| { @@ -107,7 +107,7 @@ pub fn ui_picking( } // The list of node entities hovered for each (camera, pointer) combo - let mut hit_nodes = HashMap::<(Entity, PointerId), Vec>::new(); + let mut hit_nodes = HashMap::<(Entity, PointerId), Vec>::default(); // prepare an iterator that contains all the nodes that have the cursor in their rect, // from the top node to the bottom one. this will also reset the interaction to `None` diff --git a/crates/bevy_ui/src/render/box_shadow.rs b/crates/bevy_ui/src/render/box_shadow.rs index 365a06fcaf2d8..ce741ce137ec7 100644 --- a/crates/bevy_ui/src/render/box_shadow.rs +++ b/crates/bevy_ui/src/render/box_shadow.rs @@ -262,7 +262,7 @@ pub fn extract_shadows( continue; }; - // Skip invisible images + // Skip if no visible shadows if !view_visibility.get() || box_shadow.is_empty() || uinode.is_empty() { continue; } diff --git a/crates/bevy_ui/src/stack.rs b/crates/bevy_ui/src/stack.rs index 83ccc9a23c0d5..a8283e0938ea7 100644 --- a/crates/bevy_ui/src/stack.rs +++ b/crates/bevy_ui/src/stack.rs @@ -62,7 +62,7 @@ pub fn ui_stack_system( maybe_zindex.map(|zindex| zindex.0).unwrap_or(0), ), )); - visited_root_nodes.insert_unique_unchecked(id); + visited_root_nodes.insert(id); } for (id, global_zindex, maybe_zindex) in zindex_global_node_query.iter() { diff --git a/crates/bevy_ui/src/update.rs b/crates/bevy_ui/src/update.rs index 049c797ea103c..9d8883d81ddbe 100644 --- a/crates/bevy_ui/src/update.rs +++ b/crates/bevy_ui/src/update.rs @@ -146,7 +146,7 @@ pub fn update_target_camera_system( ) { // Track updated entities to prevent redundant updates, as `Commands` changes are deferred, // and updates done for changed_children_query can overlap with itself or with root_node_query - let mut updated_entities = HashSet::new(); + let mut updated_entities = >::default(); // Assuming that TargetCamera is manually set on the root node only, // update root nodes first, since it implies the biggest change diff --git a/crates/bevy_utils/Cargo.toml b/crates/bevy_utils/Cargo.toml index 91bf2710e0aee..8121bbce4f16c 100644 --- a/crates/bevy_utils/Cargo.toml +++ b/crates/bevy_utils/Cargo.toml @@ -10,22 +10,18 @@ keywords = ["bevy"] [features] default = ["std", "serde"] -std = [ - "alloc", - "tracing/std", - "ahash/std", - "dep:thread_local", - "ahash/runtime-rng", -] -alloc = ["hashbrown/default"] +std = ["alloc", "tracing/std", "foldhash/std", "dep:thread_local"] +alloc = ["hashbrown"] +detailed_trace = [] serde = ["hashbrown/serde"] [dependencies] -ahash = { version = "0.8.7", default-features = false, features = [ - "compile-time-rng", -] } +foldhash = { version = "0.1.3", default-features = false } tracing = { version = "0.1", default-features = false } -hashbrown = { version = "0.14.2", default-features = false } +hashbrown = { version = "0.15.1", features = [ + "equivalent", + "raw-entry", +], optional = true, default-features = false } thread_local = { version = "1.0", optional = true } [dev-dependencies] diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index 00329f652b02f..19d122ad849f3 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -35,8 +35,28 @@ mod once; mod parallel_queue; mod time; -pub use ahash::{AHasher, RandomState}; +/// For when you want a deterministic hasher. +/// +/// Seed was randomly generated with a fair dice roll. Guaranteed to be random: +/// +const FIXED_HASHER: FixedState = + FixedState::with_seed(0b1001010111101110000001001100010000000011001001101011001001111000); + +/// Deterministic hasher based upon a random but fixed state. +#[derive(Copy, Clone, Default, Debug)] +pub struct FixedHasher; +impl BuildHasher for FixedHasher { + type Hasher = DefaultHasher; + + #[inline] + fn build_hasher(&self) -> Self::Hasher { + FIXED_HASHER.build_hasher() + } +} + pub use default::default; +pub use foldhash::fast::{FixedState, FoldHasher as DefaultHasher, RandomState}; +#[cfg(feature = "alloc")] pub use hashbrown; #[cfg(feature = "std")] pub use parallel_queue::*; @@ -46,15 +66,15 @@ pub use tracing; #[cfg(feature = "alloc")] use alloc::boxed::Box; +#[cfg(feature = "alloc")] +use core::any::TypeId; use core::{ - any::TypeId, fmt::Debug, - hash::{BuildHasher, BuildHasherDefault, Hash, Hasher}, + hash::{BuildHasher, Hash, Hasher}, marker::PhantomData, mem::ManuallyDrop, ops::Deref, }; -use hashbrown::hash_map::RawEntryMut; #[cfg(not(target_arch = "wasm32"))] mod conditional_send { @@ -83,70 +103,60 @@ impl ConditionalSendFuture for T {} pub type BoxedFuture<'a, T> = core::pin::Pin + 'a>>; /// A shortcut alias for [`hashbrown::hash_map::Entry`]. -pub type Entry<'a, K, V, S = BuildHasherDefault> = hashbrown::hash_map::Entry<'a, K, V, S>; - -/// A hasher builder that will create a fixed hasher. -#[derive(Debug, Clone, Default)] -pub struct FixedState; - -impl BuildHasher for FixedState { - type Hasher = AHasher; - - #[inline] - fn build_hasher(&self) -> AHasher { - RandomState::with_seeds( - 0b10010101111011100000010011000100, - 0b00000011001001101011001001111000, - 0b11001111011010110111100010110101, - 0b00000100001111100011010011010101, - ) - .build_hasher() - } -} +#[cfg(feature = "alloc")] +pub type Entry<'a, K, V, S = FixedHasher> = hashbrown::hash_map::Entry<'a, K, V, S>; -/// A [`HashMap`][hashbrown::HashMap] implementing aHash, a high +/// A [`HashMap`][hashbrown::HashMap] implementing a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// aHash is designed for performance and is NOT cryptographically secure. +/// The hashing algorithm is designed for performance +/// and is NOT cryptographically secure. /// /// Within the same execution of the program iteration order of different /// `HashMap`s only depends on the order of insertions and deletions, /// but it will not be stable between multiple executions of the program. -pub type HashMap = hashbrown::HashMap>; +#[cfg(feature = "alloc")] +pub type HashMap = hashbrown::HashMap; -/// A stable hash map implementing aHash, a high speed keyed hashing algorithm +/// A stable hash map implementing a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashMap`] the iteration order stability extends between executions /// using the same Bevy version on the same device. /// -/// aHash is designed for performance and is NOT cryptographically secure. +/// The hashing algorithm is designed for performance +/// and is NOT cryptographically secure. #[deprecated( - note = "Will be required to use the hash library of your choice. Alias for: hashbrown::HashMap" + note = "Will be required to use the hash library of your choice. Alias for: hashbrown::HashMap" )] -pub type StableHashMap = hashbrown::HashMap; +#[cfg(feature = "alloc")] +pub type StableHashMap = hashbrown::HashMap; -/// A [`HashSet`][hashbrown::HashSet] implementing aHash, a high +/// A [`HashSet`][hashbrown::HashSet] implementing a high /// speed keyed hashing algorithm intended for use in in-memory hashmaps. /// -/// aHash is designed for performance and is NOT cryptographically secure. +/// The hashing algorithm is designed for performance +/// and is NOT cryptographically secure. /// /// Within the same execution of the program iteration order of different /// `HashSet`s only depends on the order of insertions and deletions, /// but it will not be stable between multiple executions of the program. -pub type HashSet = hashbrown::HashSet>; +#[cfg(feature = "alloc")] +pub type HashSet = hashbrown::HashSet; -/// A stable hash set implementing aHash, a high speed keyed hashing algorithm +/// A stable hash set using a high speed keyed hashing algorithm /// intended for use in in-memory hashmaps. /// /// Unlike [`HashMap`] the iteration order stability extends between executions /// using the same Bevy version on the same device. /// -/// aHash is designed for performance and is NOT cryptographically secure. +/// The hashing algorithm is designed for performance +/// and is NOT cryptographically secure. #[deprecated( - note = "Will be required to use the hash library of your choice. Alias for: hashbrown::HashSet" + note = "Will be required to use the hash library of your choice. Alias for: hashbrown::HashSet" )] -pub type StableHashSet = hashbrown::HashSet; +#[cfg(feature = "alloc")] +pub type StableHashSet = hashbrown::HashSet; /// A pre-hashed value of a specific type. Pre-hashing enables memoization of hashes that are expensive to compute. /// @@ -154,10 +164,10 @@ pub type StableHashSet = hashbrown::HashSet; /// See [`PassHash`] and [`PassHasher`] for a "pass through" [`BuildHasher`] and [`Hasher`] implementation /// designed to work with [`Hashed`] /// See [`PreHashMap`] for a hashmap pre-configured to use [`Hashed`] keys. -pub struct Hashed { +pub struct Hashed { hash: u64, value: V, - marker: PhantomData, + marker: PhantomData, } impl Hashed { @@ -263,9 +273,11 @@ impl Hasher for PassHasher { /// A [`HashMap`] pre-configured to use [`Hashed`] keys and [`PassHash`] passthrough hashing. /// Iteration order only depends on the order of insertions and deletions. +#[cfg(feature = "alloc")] pub type PreHashMap = hashbrown::HashMap, V, PassHash>; /// Extension methods intended to add functionality to [`PreHashMap`]. +#[cfg(feature = "alloc")] pub trait PreHashMapExt { /// Tries to get or insert the value for the given `key` using the pre-computed hash first. /// If the [`PreHashMap`] does not already contain the `key`, it will clone it and insert @@ -273,9 +285,11 @@ pub trait PreHashMapExt { fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V; } +#[cfg(feature = "alloc")] impl PreHashMapExt for PreHashMap { #[inline] fn get_or_insert_with V>(&mut self, key: &Hashed, func: F) -> &mut V { + use hashbrown::hash_map::RawEntryMut; let entry = self .raw_entry_mut() .from_key_hashed_nocheck(key.hash(), key); @@ -291,6 +305,7 @@ impl PreHashMapExt for PreHashMap = hashbrown::HashMap; /// [`BuildHasher`] for types that already contain a high-quality hash. @@ -448,8 +463,8 @@ mod tests { fn stable_hash_within_same_program_execution() { use alloc::vec::Vec; - let mut map_1 = HashMap::new(); - let mut map_2 = HashMap::new(); + let mut map_1 = >::default(); + let mut map_2 = >::default(); for i in 1..10 { map_1.insert(i, i); map_2.insert(i, i); diff --git a/examples/3d/mesh_ray_cast.rs b/examples/3d/mesh_ray_cast.rs index 3c691ccc164dc..85985f75a900a 100644 --- a/examples/3d/mesh_ray_cast.rs +++ b/examples/3d/mesh_ray_cast.rs @@ -51,7 +51,10 @@ fn bounce_ray(mut ray: Ray3d, ray_cast: &mut MeshRayCast, gizmos: &mut Gizmos, c for i in 0..MAX_BOUNCES { // Cast the ray and get the first hit - let Some((_, hit)) = ray_cast.cast_ray(ray, &RayCastSettings::default()).first() else { + let Some((_, hit)) = ray_cast + .cast_ray(ray, &MeshRayCastSettings::default()) + .first() + else { break; }; diff --git a/examples/3d/tonemapping.rs b/examples/3d/tonemapping.rs index f802154cb390e..875198e7fed3f 100644 --- a/examples/3d/tonemapping.rs +++ b/examples/3d/tonemapping.rs @@ -572,7 +572,7 @@ impl PerMethodSettings { impl Default for PerMethodSettings { fn default() -> Self { - let mut settings = HashMap::new(); + let mut settings = >::default(); for method in [ Tonemapping::None, diff --git a/examples/README.md b/examples/README.md index af1df35e07631..bf4e04d0f5010 100644 --- a/examples/README.md +++ b/examples/README.md @@ -463,6 +463,7 @@ Example | Description [Many Animated Sprites](../examples/stress_tests/many_animated_sprites.rs) | Displays many animated sprites in a grid arrangement with slight offsets to their animation timers. Used for performance testing. [Many Buttons](../examples/stress_tests/many_buttons.rs) | Test rendering of many UI elements [Many Cameras & Lights](../examples/stress_tests/many_cameras_lights.rs) | Test rendering of many cameras and lights +[Many Components (and Entities and Systems)](../examples/stress_tests/many_components.rs) | Test large ECS systems [Many Cubes](../examples/stress_tests/many_cubes.rs) | Simple benchmark to test per-entity draw overhead. Run with the `sphere` argument to test frustum culling [Many Foxes](../examples/stress_tests/many_foxes.rs) | Loads an animated fox model and spawns lots of them. Good for testing skinned mesh performance. Takes an unsigned integer argument for the number of foxes to spawn. Defaults to 1000 [Many Gizmos](../examples/stress_tests/many_gizmos.rs) | Test rendering of many gizmos diff --git a/examples/animation/animation_masks.rs b/examples/animation/animation_masks.rs index 734b4c1c06346..7a27d6acd0347 100644 --- a/examples/animation/animation_masks.rs +++ b/examples/animation/animation_masks.rs @@ -4,8 +4,8 @@ use bevy::{ animation::{AnimationTarget, AnimationTargetId}, color::palettes::css::{LIGHT_GRAY, WHITE}, prelude::*, - utils::hashbrown::HashSet, }; +use std::collections::HashSet; // IDs of the mask groups we define for the running fox model. // diff --git a/examples/diagnostics/log_diagnostics.rs b/examples/diagnostics/log_diagnostics.rs index e312ad671d12e..d0934c5e3968b 100644 --- a/examples/diagnostics/log_diagnostics.rs +++ b/examples/diagnostics/log_diagnostics.rs @@ -15,10 +15,15 @@ fn main() { LogDiagnosticsPlugin::default(), // Any plugin can register diagnostics. Uncomment this to add an entity count diagnostics: // bevy::diagnostic::EntityCountDiagnosticsPlugin::default(), + // Uncomment this to add an asset count diagnostics: // bevy::asset::diagnostic::AssetCountDiagnosticsPlugin::::default(), + // Uncomment this to add system info diagnostics: // bevy::diagnostic::SystemInformationDiagnosticsPlugin::default() + + // Uncomment this to add rendering diagnostics: + // bevy::render::diagnostic::RenderDiagnosticsPlugin::default(), )) .run(); } diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index 1e9b8c57a32ad..7de684d350d14 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -3,7 +3,7 @@ //! This example show how you can create components dynamically, spawn entities with those components //! as well as query for entities with those components. -use std::{alloc::Layout, io::Write, ptr::NonNull}; +use std::{alloc::Layout, collections::HashMap, io::Write, ptr::NonNull}; use bevy::{ ecs::{ @@ -13,7 +13,6 @@ use bevy::{ }, prelude::*, ptr::{Aligned, OwningPtr}, - utils::HashMap, }; const PROMPT: &str = " diff --git a/examples/games/contributors.rs b/examples/games/contributors.rs index bbaf58fb3539b..8d977fd21d8ac 100644 --- a/examples/games/contributors.rs +++ b/examples/games/contributors.rs @@ -1,9 +1,10 @@ //! This example displays each contributor to the bevy source code as a bouncing bevy-ball. -use bevy::{math::bounding::Aabb2d, prelude::*, utils::HashMap}; +use bevy::{math::bounding::Aabb2d, prelude::*}; use rand::{Rng, SeedableRng}; use rand_chacha::ChaCha8Rng; use std::{ + collections::HashMap, env::VarError, hash::{DefaultHasher, Hash, Hasher}, io::{self, BufRead, BufReader}, diff --git a/examples/picking/mesh_picking.rs b/examples/picking/mesh_picking.rs index a273e5f7faa7e..a6b1c0e9dd0ab 100644 --- a/examples/picking/mesh_picking.rs +++ b/examples/picking/mesh_picking.rs @@ -91,8 +91,8 @@ fn setup_scene( )) .observe(update_material_on::>(hover_matl.clone())) .observe(update_material_on::>(white_matl.clone())) - .observe(update_material_on::>(pressed_matl.clone())) - .observe(update_material_on::>(hover_matl.clone())) + .observe(update_material_on::>(pressed_matl.clone())) + .observe(update_material_on::>(hover_matl.clone())) .observe(rotate_on_drag); } @@ -114,8 +114,8 @@ fn setup_scene( )) .observe(update_material_on::>(hover_matl.clone())) .observe(update_material_on::>(white_matl.clone())) - .observe(update_material_on::>(pressed_matl.clone())) - .observe(update_material_on::>(hover_matl.clone())) + .observe(update_material_on::>(pressed_matl.clone())) + .observe(update_material_on::>(hover_matl.clone())) .observe(rotate_on_drag); } diff --git a/examples/picking/sprite_picking.rs b/examples/picking/sprite_picking.rs index da7f79b046bed..417f590cf81df 100644 --- a/examples/picking/sprite_picking.rs +++ b/examples/picking/sprite_picking.rs @@ -63,8 +63,8 @@ fn setup(mut commands: Commands, asset_server: Res) { )) .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))) .observe(recolor_on::>(Color::BLACK)) - .observe(recolor_on::>(Color::srgb(1.0, 1.0, 0.0))) - .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))); + .observe(recolor_on::>(Color::srgb(1.0, 1.0, 0.0))) + .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))); commands .spawn(( @@ -82,8 +82,8 @@ fn setup(mut commands: Commands, asset_server: Res) { )) .observe(recolor_on::>(Color::srgb(0.0, 1.0, 0.0))) .observe(recolor_on::>(Color::srgb(1.0, 0.0, 0.0))) - .observe(recolor_on::>(Color::srgb(0.0, 0.0, 1.0))) - .observe(recolor_on::>(Color::srgb(0.0, 1.0, 0.0))); + .observe(recolor_on::>(Color::srgb(0.0, 0.0, 1.0))) + .observe(recolor_on::>(Color::srgb(0.0, 1.0, 0.0))); } }); } @@ -143,8 +143,8 @@ fn setup_atlas( )) .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))) .observe(recolor_on::>(Color::srgb(1.0, 1.0, 1.0))) - .observe(recolor_on::>(Color::srgb(1.0, 1.0, 0.0))) - .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))); + .observe(recolor_on::>(Color::srgb(1.0, 1.0, 0.0))) + .observe(recolor_on::>(Color::srgb(0.0, 1.0, 1.0))); } // An observer listener that changes the target entity's color. diff --git a/examples/reflection/dynamic_types.rs b/examples/reflection/dynamic_types.rs index c892bc83de4a7..387fe7d0ed34d 100644 --- a/examples/reflection/dynamic_types.rs +++ b/examples/reflection/dynamic_types.rs @@ -195,7 +195,7 @@ fn main() { dynamic_set.remove(&"y"); - let mut my_set: HashSet<&str> = HashSet::new(); + let mut my_set: HashSet<&str> = HashSet::default(); my_set.apply(&dynamic_set); assert_eq!(my_set, HashSet::from_iter(["x", "z"])); } @@ -204,7 +204,7 @@ fn main() { { let dynamic_map = DynamicMap::from_iter([("x", 1u32), ("y", 2u32), ("z", 3u32)]); - let mut my_map: HashMap<&str, u32> = HashMap::new(); + let mut my_map: HashMap<&str, u32> = HashMap::default(); my_map.apply(&dynamic_map); assert_eq!(my_map.get("x"), Some(&1)); assert_eq!(my_map.get("y"), Some(&2)); diff --git a/examples/reflection/function_reflection.rs b/examples/reflection/function_reflection.rs index c37db7941a0d5..1a09cfa2b0d00 100644 --- a/examples/reflection/function_reflection.rs +++ b/examples/reflection/function_reflection.rs @@ -8,8 +8,8 @@ use bevy::reflect::{ func::{ - ArgList, DynamicFunction, DynamicFunctionMut, FunctionInfo, FunctionResult, IntoFunction, - IntoFunctionMut, Return, + ArgList, DynamicFunction, DynamicFunctionMut, FunctionResult, IntoFunction, + IntoFunctionMut, Return, SignatureInfo, }, PartialReflect, Reflect, }; @@ -83,7 +83,50 @@ fn main() { dbg!(closure.call_once(args).unwrap()); assert_eq!(count, 5); - // As stated before, this works for many kinds of simple functions. + // Generic functions can also be converted into a `DynamicFunction`, + // however, they will need to be manually monomorphized first. + fn stringify(value: T) -> String { + value.to_string() + } + + // We have to manually specify the concrete generic type we want to use. + let function = stringify::.into_function(); + + let args = ArgList::new().push_owned(123_i32); + let return_value = function.call(args).unwrap(); + let value: Box = return_value.unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), "123"); + + // To make things a little easier, we can also "overload" functions. + // This makes it so that a single `DynamicFunction` can represent multiple functions, + // and the correct one is chosen based on the types of the arguments. + // Each function overload must have a unique argument signature. + let function = stringify:: + .into_function() + .with_overload(stringify::); + + // Now our `function` accepts both `i32` and `f32` arguments. + let args = ArgList::new().push_owned(1.23_f32); + let return_value = function.call(args).unwrap(); + let value: Box = return_value.unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), "1.23"); + + // Function overloading even allows us to have a variable number of arguments. + let function = (|| 0) + .into_function() + .with_overload(|a: i32| a) + .with_overload(|a: i32, b: i32| a + b) + .with_overload(|a: i32, b: i32, c: i32| a + b + c); + + let args = ArgList::new() + .push_owned(1_i32) + .push_owned(2_i32) + .push_owned(3_i32); + let return_value = function.call(args).unwrap(); + let value: Box = return_value.unwrap_owned(); + assert_eq!(value.try_take::().unwrap(), 6); + + // As stated earlier, `IntoFunction` works for many kinds of simple functions. // Functions with non-reflectable arguments or return values may not be able to be converted. // Generic functions are also not supported (unless manually monomorphized like `foo::.into_function()`). // Additionally, the lifetime of the return value is tied to the lifetime of the first argument. @@ -118,7 +161,7 @@ fn main() { let value: &dyn PartialReflect = return_value.unwrap_ref(); assert_eq!(value.try_downcast_ref::().unwrap(), "Hello, world!"); - // Lastly, for more complex use cases, you can always create a custom `DynamicFunction` manually. + // For more complex use cases, you can always create a custom `DynamicFunction` manually. // This is useful for functions that can't be converted via the `IntoFunction` trait. // For example, this function doesn't implement `IntoFunction` due to the fact that // the lifetime of the return value is not tied to the lifetime of the first argument. @@ -150,7 +193,7 @@ fn main() { // This makes it easier to debug and is also required for function registration. // We can either give it a custom name or use the function's type name as // derived from `std::any::type_name_of_val`. - FunctionInfo::named(std::any::type_name_of_val(&get_or_insert)) + SignatureInfo::named(std::any::type_name_of_val(&get_or_insert)) // We can always change the name if needed. // It's a good idea to also ensure that the name is unique, // such as by using its type name or by prefixing it with your crate name. diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 8f9e9a73d7621..265e5f8adce60 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -68,7 +68,7 @@ enum F { } fn setup() { - let mut z = HashMap::default(); + let mut z = >::default(); z.insert("Hello".to_string(), 1.0); let value: Box = Box::new(A { x: 1, diff --git a/examples/shader/texture_binding_array.rs b/examples/shader/texture_binding_array.rs index 61cb6403e6e93..bf5dc3da9debe 100644 --- a/examples/shader/texture_binding_array.rs +++ b/examples/shader/texture_binding_array.rs @@ -144,6 +144,7 @@ impl AsBindGroup for BindlessMaterial { _layout: &BindGroupLayout, _render_device: &RenderDevice, _param: &mut SystemParamItem<'_, '_, Self::Param>, + _force_no_bindless: bool, ) -> Result, AsBindGroupError> { // We implement `as_bind_group`` directly because bindless texture // arrays can't be owned. @@ -152,7 +153,7 @@ impl AsBindGroup for BindlessMaterial { Err(AsBindGroupError::CreateBindGroupDirectly) } - fn bind_group_layout_entries(_: &RenderDevice) -> Vec + fn bind_group_layout_entries(_: &RenderDevice, _: bool) -> Vec where Self: Sized, { diff --git a/examples/stress_tests/many_components.rs b/examples/stress_tests/many_components.rs new file mode 100644 index 0000000000000..4bb87d322d5c7 --- /dev/null +++ b/examples/stress_tests/many_components.rs @@ -0,0 +1,196 @@ +//! Stress test for large ECS worlds. +//! +//! Running this example: +//! +//! ``` +//! cargo run --profile stress-test --example many_components [] [] [] +//! ``` +//! +//! `num_entities`: The number of entities in the world (must be nonnegative) +//! `num_components`: the number of components in the world (must be at least 10) +//! `num_systems`: the number of systems in the world (must be nonnegative) +//! +//! If no valid number is provided, for each argument there's a reasonable default. + +use bevy::{ + diagnostic::{ + DiagnosticPath, DiagnosticsPlugin, FrameTimeDiagnosticsPlugin, LogDiagnosticsPlugin, + }, + ecs::{ + component::{ComponentDescriptor, ComponentId, StorageType}, + system::QueryParamBuilder, + world::FilteredEntityMut, + }, + log::LogPlugin, + prelude::{App, In, IntoSystem, Query, Schedule, SystemParamBuilder, Update}, + ptr::OwningPtr, + MinimalPlugins, +}; + +use rand::prelude::{Rng, SeedableRng, SliceRandom}; +use rand_chacha::ChaCha8Rng; +use std::{alloc::Layout, num::Wrapping}; + +// A simple system that matches against several components and does some menial calculation to create +// some non-trivial load. +fn base_system(access_components: In>, mut query: Query) { + for mut filtered_entity in &mut query { + // We calculate Faulhaber's formula mod 256 with n = value and p = exponent. + // See https://en.wikipedia.org/wiki/Faulhaber%27s_formula + // The time is takes to compute this depends on the number of entities and the values in + // each entity. This is to ensure that each system takes a different amount of time. + let mut total: Wrapping = Wrapping(0); + let mut exponent: u32 = 1; + for component_id in &access_components.0 { + // find the value of the component + let ptr = filtered_entity.get_by_id(*component_id).unwrap(); + + #[expect(unsafe_code)] + // SAFETY: All components have a u8 layout + let value: u8 = unsafe { *ptr.deref::() }; + + for i in 0..=value { + let mut product = Wrapping(1); + for _ in 1..=exponent { + product *= Wrapping(i); + } + total += product; + } + exponent += 1; + } + + // we assign this value to all the components we can write to + for component_id in &access_components.0 { + if let Some(ptr) = filtered_entity.get_mut_by_id(*component_id) { + #[expect(unsafe_code)] + // SAFETY: All components have a u8 layout + unsafe { + let mut value = ptr.with_type::(); + *value = total.0; + } + } + } + } +} + +fn stress_test(num_entities: u32, num_components: u32, num_systems: u32) { + let mut rng = ChaCha8Rng::seed_from_u64(42); + let mut app = App::default(); + let world = app.world_mut(); + + // register a bunch of components + let component_ids: Vec = (1..=num_components) + .map(|i| { + world.register_component_with_descriptor( + #[allow(unsafe_code)] + // SAFETY: + // we don't implement a drop function + // u8 is Sync and Send + unsafe { + ComponentDescriptor::new_with_layout( + format!("Component{}", i).to_string(), + StorageType::Table, + Layout::new::(), + None, + true, // is mutable + ) + }, + ) + }) + .collect(); + + // fill the schedule with systems + let mut schedule = Schedule::new(Update); + for _ in 1..=num_systems { + let num_access_components = rng.gen_range(1..10); + let access_components: Vec = component_ids + .choose_multiple(&mut rng, num_access_components) + .copied() + .collect(); + let system = (QueryParamBuilder::new(|builder| { + for &access_component in &access_components { + if rand::random::() { + builder.mut_id(access_component); + } else { + builder.ref_id(access_component); + } + } + }),) + .build_state(world) + .build_any_system(base_system); + schedule.add_systems((move || access_components.clone()).pipe(system)); + } + + // spawn a bunch of entities + for _ in 1..=num_entities { + let num_components = rng.gen_range(1..10); + let components = component_ids.choose_multiple(&mut rng, num_components); + + let mut entity = world.spawn_empty(); + for &component_id in components { + let value: u8 = rng.gen_range(0..255); + OwningPtr::make(value, |ptr| { + #[allow(unsafe_code)] + // SAFETY: + // component_id is from the same world + // value is u8, so ptr is a valid reference for component_id + unsafe { + entity.insert_by_id(component_id, ptr); + } + }); + } + } + + // overwrite Update schedule in the app + app.add_schedule(schedule); + app.add_plugins(MinimalPlugins) + .add_plugins(DiagnosticsPlugin) + .add_plugins(LogPlugin::default()) + .add_plugins(FrameTimeDiagnosticsPlugin) + .add_plugins(LogDiagnosticsPlugin::filtered(vec![DiagnosticPath::new( + "fps", + )])); + app.run(); +} + +#[expect(missing_docs)] +pub fn main() { + const DEFAULT_NUM_ENTITIES: u32 = 50000; + const DEFAULT_NUM_COMPONENTS: u32 = 1000; + const DEFAULT_NUM_SYSTEMS: u32 = 800; + + // take input + let num_entities = std::env::args() + .nth(1) + .and_then(|string| string.parse::().ok()) + .unwrap_or_else(|| { + println!( + "No valid number of entities provided, using default {}", + DEFAULT_NUM_ENTITIES + ); + DEFAULT_NUM_ENTITIES + }); + let num_components = std::env::args() + .nth(2) + .and_then(|string| string.parse::().ok()) + .and_then(|n| if n >= 10 { Some(n) } else { None }) + .unwrap_or_else(|| { + println!( + "No valid number of components provided (>= 10), using default {}", + DEFAULT_NUM_COMPONENTS + ); + DEFAULT_NUM_COMPONENTS + }); + let num_systems = std::env::args() + .nth(3) + .and_then(|string| string.parse::().ok()) + .unwrap_or_else(|| { + println!( + "No valid number of systems provided, using default {}", + DEFAULT_NUM_SYSTEMS + ); + DEFAULT_NUM_SYSTEMS + }); + + stress_test(num_entities, num_components, num_systems); +} diff --git a/examples/ui/scroll.rs b/examples/ui/scroll.rs index 0467f61cee81f..c28abddd2a0c5 100644 --- a/examples/ui/scroll.rs +++ b/examples/ui/scroll.rs @@ -89,7 +89,7 @@ fn setup(mut commands: Commands, asset_server: Res) { ..default() }) .observe(| - trigger: Trigger>, + trigger: Trigger>, mut commands: Commands | { if trigger.event().button == PointerButton::Primary { diff --git a/tests/ecs/ambiguity_detection.rs b/tests/ecs/ambiguity_detection.rs index f6971b1ca2b23..b53450ba22bbf 100644 --- a/tests/ecs/ambiguity_detection.rs +++ b/tests/ecs/ambiguity_detection.rs @@ -71,7 +71,7 @@ fn configure_ambiguity_detection(sub_app: &mut SubApp) { /// Returns the number of conflicting systems per schedule. fn count_ambiguities(sub_app: &SubApp) -> AmbiguitiesCount { let schedules = sub_app.world().resource::(); - let mut ambiguities = HashMap::new(); + let mut ambiguities = >::default(); for (_, schedule) in schedules.iter() { let ambiguities_in_schedule = schedule.graph().conflicting_systems().len(); ambiguities.insert(schedule.label(), ambiguities_in_schedule); diff --git a/tools/build-templated-pages/Cargo.toml b/tools/build-templated-pages/Cargo.toml index b65f1bd358ac4..d19e553dd3781 100644 --- a/tools/build-templated-pages/Cargo.toml +++ b/tools/build-templated-pages/Cargo.toml @@ -12,7 +12,7 @@ toml_edit = { version = "0.22.7", default-features = false, features = [ tera = "1.15" serde = { version = "1.0", features = ["derive"] } bitflags = "2.3" -hashbrown = { version = "0.14", features = ["serde"] } +hashbrown = { version = "0.15", features = ["serde"] } [lints] workspace = true diff --git a/tools/example-showcase/remove-desktop-app-mode.patch b/tools/example-showcase/remove-desktop-app-mode.patch index 2d21e07902b89..b43cfce515d80 100644 --- a/tools/example-showcase/remove-desktop-app-mode.patch +++ b/tools/example-showcase/remove-desktop-app-mode.patch @@ -13,4 +13,4 @@ index 104384086..6e3c8dd83 100644 + Self::default() } - /// Returns the current [`UpdateMode`]. + /// Default settings for mobile.