diff --git a/crates/bevy_asset/src/path.rs b/crates/bevy_asset/src/path.rs index 85ca220a357da..7a329023e166f 100644 --- a/crates/bevy_asset/src/path.rs +++ b/crates/bevy_asset/src/path.rs @@ -9,7 +9,7 @@ use std::{ /// Represents a path to an asset in the file system. #[derive(Debug, Eq, PartialEq, Hash, Clone, Serialize, Deserialize, Reflect)] -#[reflect(Debug, PartialEq, Hash, Serialize, Deserialize)] +#[reflect(Debug, Serialize, Deserialize)] pub struct AssetPath<'a> { path: Cow<'a, Path>, label: Option>, diff --git a/crates/bevy_core_pipeline/src/fxaa/mod.rs b/crates/bevy_core_pipeline/src/fxaa/mod.rs index 8b0c0433d96fb..3dd0983a569ae 100644 --- a/crates/bevy_core_pipeline/src/fxaa/mod.rs +++ b/crates/bevy_core_pipeline/src/fxaa/mod.rs @@ -25,7 +25,6 @@ mod node; pub use node::FxaaNode; #[derive(Reflect, Eq, PartialEq, Hash, Clone, Copy)] -#[reflect(PartialEq, Hash)] pub enum Sensitivity { Low, Medium, diff --git a/crates/bevy_hierarchy/src/components/parent.rs b/crates/bevy_hierarchy/src/components/parent.rs index 7bccf33ed6e1e..16535807aa493 100644 --- a/crates/bevy_hierarchy/src/components/parent.rs +++ b/crates/bevy_hierarchy/src/components/parent.rs @@ -15,7 +15,7 @@ use std::ops::Deref; /// [`HierarchyQueryExt`]: crate::query_extension::HierarchyQueryExt /// [`Query`]: bevy_ecs::system::Query #[derive(Component, Debug, Eq, PartialEq, Reflect)] -#[reflect(Component, MapEntities, PartialEq)] +#[reflect(Component, MapEntities)] pub struct Parent(pub(crate) Entity); impl Parent { diff --git a/crates/bevy_input/src/gamepad.rs b/crates/bevy_input/src/gamepad.rs index e81c075b759a9..16de737429f9d 100644 --- a/crates/bevy_input/src/gamepad.rs +++ b/crates/bevy_input/src/gamepad.rs @@ -73,7 +73,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// /// The `ID` of a gamepad is fixed until the gamepad disconnects or the app is restarted. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -93,7 +93,7 @@ impl Gamepad { /// Metadata associated with a [`Gamepad`]. #[derive(Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -154,7 +154,7 @@ impl Gamepads { /// which in turn is used to create the [`Input`] or /// [`Axis`] `bevy` resources. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -219,7 +219,7 @@ pub enum GamepadButtonType { /// /// The gamepad button resources are updated inside of the [`gamepad_button_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -261,7 +261,7 @@ impl GamepadButton { /// [`GamepadAxisChangedEvent`]. It is also used in the [`GamepadAxis`] /// which in turn is used to create the [`Axis`] `bevy` resource. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -297,7 +297,7 @@ pub enum GamepadAxisType { /// /// The gamepad axes resources are updated inside of the [`gamepad_axis_event_system`]. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1026,7 +1026,7 @@ pub fn gamepad_connection_system( } #[derive(Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1040,7 +1040,7 @@ pub enum GamepadConnection { /// A Gamepad connection event. Created when a connection to a gamepad /// is established and when a gamepad is disconnected. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1071,7 +1071,7 @@ impl GamepadConnectionEvent { } #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1096,7 +1096,7 @@ impl GamepadAxisChangedEvent { /// Gamepad event for when the "value" (amount of pressure) on the button /// changes by an amount larger than the threshold defined in [`GamepadSettings`]. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -1159,7 +1159,7 @@ pub fn gamepad_button_event_system( /// [`GamepadButtonChangedEvent`] and [`GamepadAxisChangedEvent`] when /// the in-frame relative ordering of events is important. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_input/src/keyboard.rs b/crates/bevy_input/src/keyboard.rs index edf291b6320d9..20158146ff40b 100644 --- a/crates/bevy_input/src/keyboard.rs +++ b/crates/bevy_input/src/keyboard.rs @@ -20,7 +20,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// The event is consumed inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system) /// to update the [`Input`](crate::Input) resource. #[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -79,7 +79,7 @@ pub fn keyboard_input_system( /// /// The resource is updated inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system). #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -452,7 +452,7 @@ pub enum KeyCode { /// /// The resource is updated inside of the [`keyboard_input_system`](crate::keyboard::keyboard_input_system). #[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_input/src/lib.rs b/crates/bevy_input/src/lib.rs index ab5b445bf5529..e90faf40a57dc 100644 --- a/crates/bevy_input/src/lib.rs +++ b/crates/bevy_input/src/lib.rs @@ -141,7 +141,7 @@ impl Plugin for InputPlugin { /// The current "press" state of an element #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_input/src/mouse.rs b/crates/bevy_input/src/mouse.rs index deb8b8c4a03a1..8be09691432ed 100644 --- a/crates/bevy_input/src/mouse.rs +++ b/crates/bevy_input/src/mouse.rs @@ -20,7 +20,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// The event is read inside of the [`mouse_button_input_system`](crate::mouse::mouse_button_input_system) /// to update the [`Input`](crate::Input) resource. #[derive(Event, Debug, Clone, Copy, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -46,7 +46,7 @@ pub struct MouseButtonInput { /// /// The resource is updated inside of the [`mouse_button_input_system`](crate::mouse::mouse_button_input_system). #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -73,7 +73,7 @@ pub enum MouseButton { /// /// [`DeviceEvent::MouseMotion`]: https://docs.rs/winit/latest/winit/event/enum.DeviceEvent.html#variant.MouseMotion #[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -91,7 +91,7 @@ pub struct MouseMotion { /// The value of the event can either be interpreted as the amount of lines or the amount of pixels /// to scroll. #[derive(Debug, Clone, Copy, Eq, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -114,7 +114,7 @@ pub enum MouseScrollUnit { /// /// This event is the translated version of the `WindowEvent::MouseWheel` from the `winit` crate. #[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_input/src/touch.rs b/crates/bevy_input/src/touch.rs index 64293b9d22426..ade189a13acf5 100644 --- a/crates/bevy_input/src/touch.rs +++ b/crates/bevy_input/src/touch.rs @@ -31,7 +31,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// This event is the translated version of the `WindowEvent::Touch` from the `winit` crate. /// It is available to the end user and can be used for game logic. #[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -53,7 +53,7 @@ pub struct TouchInput { /// A force description of a [`Touch`](crate::touch::Touch) input. #[derive(Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -99,7 +99,7 @@ pub enum ForceTouch { /// or that a finger has moved. There is also a canceled phase that indicates that /// the system canceled the tracking of the finger. #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, Reflect)] -#[reflect(Debug, Hash, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_input/src/touchpad.rs b/crates/bevy_input/src/touchpad.rs index 358c44585edbd..10af4604ec4c0 100644 --- a/crates/bevy_input/src/touchpad.rs +++ b/crates/bevy_input/src/touchpad.rs @@ -13,7 +13,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; /// /// - Only available on **`macOS`**. #[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -30,7 +30,7 @@ pub struct TouchpadMagnify(pub f32); /// /// - Only available on **`macOS`**. #[derive(Event, Debug, Clone, Copy, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs index a7acbb2748d90..2187193b7787c 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs @@ -3,7 +3,7 @@ //! A container attribute is an attribute which applies to an entire struct or enum //! as opposed to a particular field or variant. An example of such an attribute is //! the derive helper attribute for `Reflect`, which looks like: -//! `#[reflect(PartialEq, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. +//! `#[reflect(Debug, Default, ...)]` and `#[reflect_value(PartialEq, Default, ...)]`. use crate::fq_std::{FQAny, FQOption}; use crate::utility; @@ -16,7 +16,7 @@ use syn::token::Comma; use syn::{LitBool, Meta, Path}; // The "special" trait idents that are used internally for reflection. -// Received via attributes like `#[reflect(PartialEq, Hash, ...)]` +// Received via attributes like `#[reflect_value(PartialEq, Hash, ...)]` const DEBUG_ATTR: &str = "Debug"; const PARTIAL_EQ_ATTR: &str = "PartialEq"; const HASH_ATTR: &str = "Hash"; @@ -139,7 +139,7 @@ impl FromReflectAttrs { /// // `Hash` is a "special trait" and does not need (nor have) a ReflectHash struct /// /// #[derive(Reflect, Hash)] -/// #[reflect(Hash)] +/// #[reflect_value(Hash)] /// struct Foo; /// ``` /// @@ -154,7 +154,7 @@ impl FromReflectAttrs { /// /// #[derive(Reflect)] /// // Register the custom `Hash` function -/// #[reflect(Hash(get_hash))] +/// #[reflect_value(Hash(get_hash))] /// struct Foo; /// ``` /// @@ -173,11 +173,12 @@ impl ReflectTraits { pub fn from_metas( metas: Punctuated, is_from_reflect_derive: bool, + is_reflect_value: bool, ) -> Result { let mut traits = ReflectTraits::default(); for meta in &metas { match meta { - // Handles `#[reflect( Hash, Default, ... )]` + // Handles `#[reflect( Default, ... )]` Meta::Path(path) => { // Get the first ident in the path (hopefully the path only contains one and not `std::hash::Hash`) let Some(segment) = path.segments.iter().next() else { @@ -193,10 +194,22 @@ impl ReflectTraits { DEBUG_ATTR => { traits.debug.merge(TraitImpl::Implemented(span))?; } - PARTIAL_EQ_ATTR => { + PARTIAL_EQ_ATTR if !is_reflect_value => { + return Err(syn::Error::new( + span, + "concrete `PartialEq` impl can only be used for items marked `#[reflect_value]`", + )); + } + PARTIAL_EQ_ATTR if is_reflect_value => { traits.partial_eq.merge(TraitImpl::Implemented(span))?; } - HASH_ATTR => { + HASH_ATTR if !is_reflect_value => { + return Err(syn::Error::new( + span, + "concrete `Hash` impl can only be used for items marked `#[reflect_value]`", + )); + } + HASH_ATTR if is_reflect_value => { traits.hash.merge(TraitImpl::Implemented(span))?; } // We only track reflected idents for traits not considered special @@ -232,7 +245,13 @@ impl ReflectTraits { PARTIAL_EQ_ATTR => { traits.partial_eq.merge(trait_func_ident)?; } - HASH_ATTR => { + HASH_ATTR if !is_reflect_value => { + return Err(syn::Error::new( + span, + "custom `Hash` function cannot be used for non-`reflect_value` types", + )); + } + HASH_ATTR if is_reflect_value => { traits.hash.merge(trait_func_ident)?; } _ => { @@ -376,7 +395,11 @@ impl ReflectTraits { impl Parse for ReflectTraits { fn parse(input: ParseStream) -> syn::Result { - ReflectTraits::from_metas(Punctuated::::parse_terminated(input)?, false) + ReflectTraits::from_metas( + Punctuated::::parse_terminated(input)?, + false, + true, + ) } } diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs index 0bf42461deb70..2af34b06ac708 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/derive_data.rs @@ -1,9 +1,13 @@ use crate::container_attributes::{FromReflectAttrs, ReflectTraits}; use crate::field_attributes::{parse_field_attrs, ReflectFieldAttr}; +use crate::fq_std::{FQAny, FQHash, FQOption}; use crate::type_path::parse_path_no_leading_colon; -use crate::utility::{members_to_serialization_denylist, StringExpr, WhereClauseOptions}; +use crate::utility::{ + ident_or_index, members_to_serialization_denylist, StringExpr, WhereClauseOptions, +}; use bit_set::BitSet; -use quote::{quote, ToTokens}; +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; use syn::token::Comma; use crate::{ @@ -13,8 +17,8 @@ use crate::{ use syn::punctuated::Punctuated; use syn::spanned::Spanned; use syn::{ - parse_str, Data, DeriveInput, Field, Fields, GenericParam, Generics, Ident, LitStr, Meta, Path, - PathSegment, Type, TypeParam, Variant, + parse_str, Data, DeriveInput, Field, Fields, GenericParam, Generics, Ident, Index, LitStr, + Meta, Path, PathSegment, Type, TypeParam, Variant, }; pub(crate) enum ReflectDerive<'a> { @@ -31,9 +35,9 @@ pub(crate) enum ReflectDerive<'a> { /// /// ```ignore /// #[derive(Reflect)] -/// // traits -/// // |----------------------------------------| -/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// // traits +/// // |-----------------------------| +/// #[reflect(Serialize, Deserialize, Default)] /// // type_name generics /// // |-------------------||----------| /// struct ThingThatImReflecting {/* ... */} @@ -56,7 +60,7 @@ pub(crate) struct ReflectMeta<'a> { /// /// ```ignore /// #[derive(Reflect)] -/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// #[reflect(Serialize, Deserialize, Default)] /// struct ThingThatImReflecting { /// x: T1, // | /// y: T2, // |- fields @@ -67,6 +71,7 @@ pub(crate) struct ReflectStruct<'a> { meta: ReflectMeta<'a>, serialization_denylist: BitSet, fields: Vec>, + is_tuple: bool, } /// Enum data used by derive macros for `Reflect` and `FromReflect`. @@ -75,7 +80,7 @@ pub(crate) struct ReflectStruct<'a> { /// /// ```ignore /// #[derive(Reflect)] -/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// #[reflect(Serialize, Deserialize, Default)] /// enum ThingThatImReflecting { /// A(T1), // | /// B, // |- variants @@ -163,6 +168,7 @@ impl<'a> ReflectDerive<'a> { let new_traits = ReflectTraits::from_metas( meta_list.parse_args_with(Punctuated::::parse_terminated)?, is_from_reflect_derive, + false, )?; traits.merge(new_traits)?; } @@ -178,6 +184,7 @@ impl<'a> ReflectDerive<'a> { let new_traits = ReflectTraits::from_metas( meta_list.parse_args_with(Punctuated::::parse_terminated)?, is_from_reflect_derive, + true, )?; traits.merge(new_traits)?; } @@ -268,7 +275,9 @@ impl<'a> ReflectDerive<'a> { return match &input.data { Data::Struct(data) => { let fields = Self::collect_struct_fields(&data.fields)?; + let is_tuple = matches!(data.fields, Fields::Unnamed(..)); let reflect_struct = ReflectStruct { + is_tuple, meta, serialization_denylist: members_to_serialization_denylist( fields.iter().map(|v| v.attrs.ignore), @@ -405,6 +414,34 @@ impl<'a> ReflectMeta<'a> { crate::registration::impl_get_type_registration(self, where_clause_options, None) } + pub fn to_type_info(&self) -> proc_macro2::TokenStream { + let bevy_reflect_path = self.bevy_reflect_path(); + + #[cfg(feature = "documentation")] + let doc_field = { + let doc = &self.docs; + quote! { + docs: #doc + } + }; + + #[cfg(not(feature = "documentation"))] + let doc_field = proc_macro2::TokenStream::new(); + + let meta = quote! { + #bevy_reflect_path::ValueMeta { + #doc_field + } + }; + + quote! { + #bevy_reflect_path::TypeInfo::Value( + #bevy_reflect_path::ValueInfo::new::() + .with_meta(#meta) + ) + } + } + /// The collection of docstrings for this type, if any. #[cfg(feature = "documentation")] pub fn doc(&self) -> &crate::documentation::Documentation { @@ -412,6 +449,50 @@ impl<'a> ReflectMeta<'a> { } } +impl<'a> StructField<'a> { + /// Generate the `NamedField`/`UnnamedField` for this field as a `TokenStream`. + pub fn to_field_info(&self, bevy_reflect_path: &Path) -> proc_macro2::TokenStream { + #[cfg(feature = "documentation")] + let doc_field = { + let doc = &self.doc; + quote! { + docs: #doc + } + }; + + #[cfg(not(feature = "documentation"))] + let doc_field = proc_macro2::TokenStream::new(); + + let field_ty = &self.data.ty; + let skip_hash = self.attrs.skip_hash; + let skip_partial_eq = self.attrs.skip_partial_eq; + + let meta = quote! { + #bevy_reflect_path::FieldMeta { + skip_hash: #skip_hash, + skip_partial_eq: #skip_partial_eq, + #doc_field + } + }; + + match &self.data.ident { + Some(ident) => { + let field_name = ident.to_string(); + quote! { + #bevy_reflect_path::NamedField::new::<#field_ty>(#field_name) + .with_meta(#meta) + } + } + None => { + let field_index = self.index; + quote! { + #bevy_reflect_path::UnnamedField::new::<#field_ty>(#field_index).with_meta(#meta) + } + } + } + } +} + impl<'a> ReflectStruct<'a> { /// Access the metadata associated with this struct definition. pub fn meta(&self) -> &ReflectMeta<'a> { @@ -440,13 +521,6 @@ impl<'a> ReflectStruct<'a> { ) } - /// Get a collection of types which are exposed to the reflection API - pub fn active_types(&self) -> Vec { - self.active_fields() - .map(|field| field.data.ty.clone()) - .collect() - } - /// Get an iterator of fields which are exposed to the reflection API pub fn active_fields(&self) -> impl Iterator> { self.fields @@ -470,6 +544,128 @@ impl<'a> ReflectStruct<'a> { pub fn where_clause_options(&self) -> WhereClauseOptions { WhereClauseOptions::new(self.meta(), self.active_fields(), self.ignored_fields()) } + + /// Generate the `Reflect::reflect_partial_eq` method for this struct as a `TokenStream`. + pub fn get_partial_eq_impl(&self) -> TokenStream { + let bevy_reflect_path = &self.meta().bevy_reflect_path; + let fq_option = FQOption; + + let fields = self + .active_fields() + .filter(|field| !field.attrs.skip_partial_eq) + .map(|field| ident_or_index(field.data.ident.as_ref(), field.index)); + + let dynamic_function = if self.is_tuple { + quote!(tuple_struct_partial_eq) + } else { + quote!(struct_partial_eq) + }; + + quote! { + fn reflect_partial_eq(&self, other: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + if let #FQOption::Some(other) = ::downcast_ref::(other) { + #( + if !#bevy_reflect_path::Reflect::reflect_partial_eq(&self.#fields, &other.#fields)? { + return #fq_option::Some(false); + } + )* + #FQOption::Some(true) + } else { + #bevy_reflect_path::#dynamic_function(self, other) + } + } + } + } + + /// Generate the `Reflect::reflect_hash` method for this struct as a `TokenStream`. + pub fn get_hash_impl(&self) -> TokenStream { + let bevy_reflect_path = &self.meta().bevy_reflect_path; + let fq_hash = FQHash; + + let fields = self + .active_fields() + .filter(|field| !field.attrs.skip_hash) + .map(|field| ident_or_index(field.data.ident.as_ref(), field.index)); + + let struct_type = if self.is_tuple { + quote!(TupleStruct) + } else { + quote!(Struct) + }; + + quote! { + fn reflect_hash(&self) -> #FQOption { + let mut hasher = #bevy_reflect_path::utility::reflect_hasher(); + #FQHash::hash(&#FQAny::type_id(self), &mut hasher); + #FQHash::hash(&#bevy_reflect_path::#struct_type::field_len(self), &mut hasher); + + #( + #fq_hash::hash( + &#bevy_reflect_path::Reflect::reflect_hash(&self.#fields)?, + &mut hasher + ); + )* + + #FQOption::Some( + ::core::hash::Hasher::finish(&hasher) + ) + } + } + } + + /// Generate the `TypeInfo` for this struct as a `TokenStream`. + pub fn to_type_info(&self) -> proc_macro2::TokenStream { + let bevy_reflect_path = self.meta.bevy_reflect_path(); + + let field_info = self + .active_fields() + .map(|field| field.to_field_info(bevy_reflect_path)); + + let string_name = self + .meta + .type_path() + .get_ident() + .expect("structs should never be anonymous") + .to_string(); + + #[cfg(feature = "documentation")] + let doc_field = { + let doc = &self.meta.doc(); + quote! { + docs: #doc, + } + }; + + #[cfg(not(feature = "documentation"))] + let doc_field = proc_macro2::TokenStream::new(); + + let meta = if self.is_tuple { + quote! { + #bevy_reflect_path::TupleStructMeta { + #doc_field + } + } + } else { + quote! { + #bevy_reflect_path::StructMeta { + #doc_field + } + } + }; + + let (info_variant, info_ty) = if self.is_tuple { + (quote!(TupleStruct), quote!(TupleStructInfo)) + } else { + (quote!(Struct), quote!(StructInfo)) + }; + + quote! { + #bevy_reflect_path::TypeInfo::#info_variant( + #bevy_reflect_path::#info_ty::new::(#string_name, &[#(#field_info),*]) + .with_meta(#meta) + ) + } + } } impl<'a> ReflectEnum<'a> { @@ -508,6 +704,163 @@ impl<'a> ReflectEnum<'a> { pub fn where_clause_options(&self) -> WhereClauseOptions { WhereClauseOptions::new(self.meta(), self.active_fields(), self.ignored_fields()) } + + /// Generate the `Reflect::reflect_partial_eq` method for this enum as a `TokenStream`. + pub fn get_partial_eq_impl(&self) -> TokenStream { + let bevy_reflect_path = &self.meta().bevy_reflect_path; + let fq_option = FQOption; + let ident = self + .meta() + .type_path() + .get_ident() + .expect("enums should never be anonymous"); + + // Essentially builds out match-arms of the following form: + // ``` + // Enum::Variant { 0: field_0, 2: field_2, .. } => { + // match other { + // Enum::Variant { 0: other_0, 2: other_2, .. } => { + // // Compare fields... + // } + // _ => return Some(false), + // } + // } + // ``` + let variants_concrete = self.variants.iter().map(|variant| { + variant.make_arm( + ident, + "field", + |field| !field.attrs.skip_partial_eq, + |fields| { + let field_idents = fields.iter().map(|(_, ident)| ident).collect::>(); + let other_arm = variant.make_arm( + ident, + "other", + |field| !field.attrs.skip_partial_eq, + |other_fields| { + let other_idents = other_fields.iter().map(|(_, ident)| ident); + quote! { + #( + if !#bevy_reflect_path::Reflect::reflect_partial_eq(#field_idents, #other_idents)? { + return #fq_option::Some(false); + } + )* + } + } + ); + + quote! { + match other { + #other_arm + _ => { + return #FQOption::Some(false); + } + } + } + }, + ) + }); + + quote! { + fn reflect_partial_eq(&self, other: &dyn #bevy_reflect_path::Reflect) -> #FQOption { + if let #FQOption::Some(other) = ::downcast_ref::(other) { + match self { + #(#variants_concrete)* + } + #FQOption::Some(true) + } else { + #bevy_reflect_path::enum_partial_eq(self, other) + } + } + } + } + + /// Generate the `Reflect::reflect_hash` method for this enum as a `TokenStream`. + pub fn get_hash_impl(&self) -> TokenStream { + let bevy_reflect_path = &self.meta().bevy_reflect_path; + let fq_hash = FQHash; + let ident = self + .meta() + .type_path() + .get_ident() + .expect("enums should never be anonymous"); + + let variants = self.variants.iter().map(|variant| { + let variant_name = variant.data.ident.to_string(); + variant.make_arm( + ident, + "field", + |field| !field.attrs.skip_hash, + |fields| { + let field_idents = fields.iter().map(|(_, ident)| ident); + quote! { + // We use the variant name instead of discriminant here so + // `DynamicEnum` proxies will result in the same hash. + #fq_hash::hash(#variant_name, &mut hasher); + + #( + #fq_hash::hash( + &#bevy_reflect_path::Reflect::reflect_hash(#field_idents)?, + &mut hasher + ); + )* + } + }, + ) + }); + + quote! { + fn reflect_hash(&self) -> #FQOption { + let mut hasher = #bevy_reflect_path::utility::reflect_hasher(); + #FQHash::hash(&#FQAny::type_id(self), &mut hasher); + #FQHash::hash(&#bevy_reflect_path::Enum::field_len(self), &mut hasher); + + match self { + #(#variants)* + } + + #FQOption::Some( + ::core::hash::Hasher::finish(&hasher) + ) + } + } + } + + /// Generate the `TypeInfo` for this enum as a `TokenStream`. + pub fn to_type_info(&self, variant_info: Vec) -> proc_macro2::TokenStream { + let bevy_reflect_path = self.meta.bevy_reflect_path(); + + let string_name = self + .meta + .type_path() + .get_ident() + .expect("enums should never be anonymous") + .to_string(); + + #[cfg(feature = "documentation")] + let doc_field = { + let doc = &self.meta.doc(); + quote! { + docs: #doc, + } + }; + + #[cfg(not(feature = "documentation"))] + let doc_field = proc_macro2::TokenStream::new(); + + let meta = quote! { + #bevy_reflect_path::EnumMeta { + #doc_field + } + }; + + quote! { + #bevy_reflect_path::TypeInfo::Enum( + #bevy_reflect_path::EnumInfo::new::(#string_name, &[#(#variant_info),*]) + .with_meta(#meta) + ) + } + } } impl<'a> EnumVariant<'a> { @@ -535,6 +888,49 @@ impl<'a> EnumVariant<'a> { EnumVariantFields::Unit => &[], } } + + /// Make an arm for this variant as it would be used in a match statement. + /// + /// This will generate a `TokenStream` like: + /// ```ignore + /// EnumName::VariantName { a: prefix_a, b: prefix_b, .. } => { + /// // ... + /// } + /// ``` + pub fn make_arm( + &self, + enum_ident: &Ident, + field_prefix: &str, + field_filter: impl FnMut(&&StructField) -> bool, + mut make_block: impl FnMut(&[(&StructField, Ident)]) -> TokenStream, + ) -> proc_macro2::TokenStream { + let ident = &self.data.ident; + + let (fields, field_patterns): (Vec<_>, Vec<_>) = self + .active_fields() + .filter(field_filter) + .map(|field| { + if let Some(ident) = &field.data.ident { + let new_ident = format_ident!("{}_{}", field_prefix, ident); + let pattern = quote!(#ident: #new_ident); + ((field, new_ident), pattern) + } else { + let index = Index::from(field.index); + let ident = format_ident!("{}_{}", field_prefix, field.index); + let pattern = quote!(#index: #ident); + ((field, ident), pattern) + } + }) + .unzip(); + + let block = make_block(&fields); + + quote! { + #enum_ident::#ident { #(#field_patterns,)* .. } => { + #block + } + } + } } /// Represents a path to a type. diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs index e3da50088d26d..18287ba2fa13e 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/field_attributes.rs @@ -11,6 +11,8 @@ use syn::{Attribute, Expr, ExprLit, Lit, Meta}; pub(crate) static IGNORE_SERIALIZATION_ATTR: &str = "skip_serializing"; pub(crate) static IGNORE_ALL_ATTR: &str = "ignore"; +pub(crate) static SKIP_HASH_ATTR: &str = "skip_hash"; +pub(crate) static SKIP_PARTIAL_EQ_ATTR: &str = "skip_partial_eq"; pub(crate) static DEFAULT_ATTR: &str = "default"; @@ -52,6 +54,10 @@ pub(crate) struct ReflectFieldAttr { pub ignore: ReflectIgnoreBehavior, /// Sets the default behavior of this field. pub default: DefaultBehavior, + /// Determines if this field should be skipped when hashing. + pub skip_hash: bool, + /// Determines if this field should be skipped when comparing for equality. + pub skip_partial_eq: bool, } /// Controls how the default value is determined for a field. @@ -111,6 +117,14 @@ fn parse_meta(args: &mut ReflectFieldAttr, meta: &Meta) -> Result<(), syn::Error args.default = DefaultBehavior::Default; Ok(()) } + Meta::Path(path) if path.is_ident(SKIP_HASH_ATTR) => { + args.skip_hash = true; + Ok(()) + } + Meta::Path(path) if path.is_ident(SKIP_PARTIAL_EQ_ATTR) => { + args.skip_partial_eq = true; + Ok(()) + } Meta::Path(path) => Err(syn::Error::new( path.span(), format!("unknown attribute parameter: {}", path.to_token_stream()), diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs b/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs index 788aa675efd38..9acd378bc8729 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/fq_std.rs @@ -37,6 +37,7 @@ pub(crate) struct FQAny; pub(crate) struct FQBox; pub(crate) struct FQClone; pub(crate) struct FQDefault; +pub(crate) struct FQHash; pub(crate) struct FQOption; pub(crate) struct FQResult; pub(crate) struct FQSend; @@ -66,6 +67,12 @@ impl ToTokens for FQDefault { } } +impl ToTokens for FQHash { + fn to_tokens(&self, tokens: &mut TokenStream) { + quote!(::core::hash::Hash).to_tokens(tokens); + } +} + impl ToTokens for FQOption { fn to_tokens(&self, tokens: &mut TokenStream) { quote!(::core::option::Option).to_tokens(tokens); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs index 41a47c2552c15..058448d87ce27 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/enums.rs @@ -34,55 +34,14 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream variant_constructors, } = get_variant_constructors(reflect_enum, &ref_value, true); - let hash_fn = reflect_enum - .meta() - .traits() - .get_hash_impl(bevy_reflect_path) - .unwrap_or_else(|| { - quote! { - fn reflect_hash(&self) -> #FQOption { - #bevy_reflect_path::enum_hash(self) - } - } - }); let debug_fn = reflect_enum.meta().traits().get_debug_impl(); - let partial_eq_fn = reflect_enum - .meta() - .traits() - .get_partial_eq_impl(bevy_reflect_path) - .unwrap_or_else(|| { - quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { - #bevy_reflect_path::enum_partial_eq(self, value) - } - } - }); - - let string_name = enum_path.get_ident().unwrap().to_string(); - - #[cfg(feature = "documentation")] - let info_generator = { - let doc = reflect_enum.meta().doc(); - quote! { - #bevy_reflect_path::EnumInfo::new::(#string_name, &variants).with_docs(#doc) - } - }; - - #[cfg(not(feature = "documentation"))] - let info_generator = { - quote! { - #bevy_reflect_path::EnumInfo::new::(#string_name, &variants) - } - }; + let hash_fn = reflect_enum.get_hash_impl(); + let partial_eq_fn = reflect_enum.get_partial_eq_impl(); let typed_impl = impl_typed( reflect_enum.meta(), &where_clause_options, - quote! { - let variants = [#(#variant_info),*]; - let info = #info_generator; - #bevy_reflect_path::TypeInfo::Enum(info) - }, + reflect_enum.to_type_info(variant_info), ); let type_path_impl = impl_type_path(reflect_enum.meta(), &where_clause_options); @@ -342,9 +301,9 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden #unit{..} => #variant_index }); - fn get_field_args( + fn get_field_infos( fields: &[StructField], - mut generate_for_field: impl FnMut(usize, usize, &StructField) -> proc_macro2::TokenStream, + mut generate_info: impl FnMut(usize, usize, &StructField) -> proc_macro2::TokenStream, ) -> Vec { let mut constructor_argument = Vec::new(); let mut reflect_idx = 0; @@ -353,26 +312,35 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden // Ignored field continue; } - constructor_argument.push(generate_for_field(reflect_idx, field.index, field)); + constructor_argument.push(generate_info(reflect_idx, field.index, field)); reflect_idx += 1; } constructor_argument } let mut push_variant = - |_variant: &EnumVariant, arguments: proc_macro2::TokenStream, field_len: usize| { + |_variant: &EnumVariant, info_args: proc_macro2::TokenStream, field_len: usize| { #[cfg(feature = "documentation")] - let with_docs = { - let doc = quote::ToTokens::to_token_stream(&_variant.doc); - Some(quote!(.with_docs(#doc))) + let doc_field = { + let doc = &_variant.doc; + quote! { + docs: #doc + } }; + #[cfg(not(feature = "documentation"))] - let with_docs: Option = None; + let doc_field = proc_macro2::TokenStream::new(); + + let meta = quote! { + #bevy_reflect_path::VariantMeta { + #doc_field + } + }; variant_info.push(quote! { #bevy_reflect_path::VariantInfo::#variant_type_ident( - #bevy_reflect_path::#variant_info_ident::new(#arguments) - #with_docs + #bevy_reflect_path::#variant_info_ident::new(#info_args) + .with_meta(#meta) ) }); enum_field_len.push(quote! { @@ -388,32 +356,20 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden push_variant(variant, quote!(#name), 0); } EnumVariantFields::Unnamed(fields) => { - let args = get_field_args(fields, |reflect_idx, declaration_index, field| { + let args = get_field_infos(fields, |reflect_idx, declaration_index, field| { let declare_field = syn::Index::from(declaration_index); enum_field_at.push(quote! { #unit { #declare_field : value, .. } if #ref_index == #reflect_idx => #FQOption::Some(value) }); - #[cfg(feature = "documentation")] - let with_docs = { - let doc = quote::ToTokens::to_token_stream(&field.doc); - Some(quote!(.with_docs(#doc))) - }; - #[cfg(not(feature = "documentation"))] - let with_docs: Option = None; - - let field_ty = &field.data.ty; - quote! { - #bevy_reflect_path::UnnamedField::new::<#field_ty>(#reflect_idx) - #with_docs - } + field.to_field_info(bevy_reflect_path) }); let field_len = args.len(); push_variant(variant, quote!(#name, &[ #(#args),* ]), field_len); } EnumVariantFields::Named(fields) => { - let args = get_field_args(fields, |reflect_idx, _, field| { + let args = get_field_infos(fields, |reflect_idx, _, field| { let field_ident = field.data.ident.as_ref().unwrap(); let field_name = field_ident.to_string(); enum_field.push(quote! { @@ -429,19 +385,7 @@ fn generate_impls(reflect_enum: &ReflectEnum, ref_index: &Ident, ref_name: &Iden #unit{ .. } if #ref_index == #reflect_idx => #FQOption::Some(#field_name) }); - #[cfg(feature = "documentation")] - let with_docs = { - let doc = quote::ToTokens::to_token_stream(&field.doc); - Some(quote!(.with_docs(#doc))) - }; - #[cfg(not(feature = "documentation"))] - let with_docs: Option = None; - - let field_ty = &field.data.ty; - quote! { - #bevy_reflect_path::NamedField::new::<#field_ty>(#field_name) - #with_docs - } + field.to_field_info(bevy_reflect_path) }); let field_len = args.len(); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs index 40ca5e3bd503b..36e9e6171d2be 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/structs.rs @@ -26,69 +26,19 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS .active_fields() .map(|field| ident_or_index(field.data.ident.as_ref(), field.index)) .collect::>(); - let field_types = reflect_struct.active_types(); let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); - let hash_fn = reflect_struct - .meta() - .traits() - .get_hash_impl(bevy_reflect_path); let debug_fn = reflect_struct.meta().traits().get_debug_impl(); - let partial_eq_fn = reflect_struct.meta() - .traits() - .get_partial_eq_impl(bevy_reflect_path) - .unwrap_or_else(|| { - quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { - #bevy_reflect_path::struct_partial_eq(self, value) - } - } - }); - #[cfg(feature = "documentation")] - let field_generator = { - let docs = reflect_struct - .active_fields() - .map(|field| quote::ToTokens::to_token_stream(&field.doc)); - quote! { - #(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names).with_docs(#docs) ,)* - } - }; - - #[cfg(not(feature = "documentation"))] - let field_generator = { - quote! { - #(#bevy_reflect_path::NamedField::new::<#field_types>(#field_names) ,)* - } - }; - - let string_name = struct_path.get_ident().unwrap().to_string(); - - #[cfg(feature = "documentation")] - let info_generator = { - let doc = reflect_struct.meta().doc(); - quote! { - #bevy_reflect_path::StructInfo::new::(#string_name, &fields).with_docs(#doc) - } - }; - - #[cfg(not(feature = "documentation"))] - let info_generator = { - quote! { - #bevy_reflect_path::StructInfo::new::(#string_name, &fields) - } - }; + let hash_fn = reflect_struct.get_hash_impl(); + let partial_eq_fn = reflect_struct.get_partial_eq_impl(); let where_clause_options = reflect_struct.where_clause_options(); let typed_impl = impl_typed( reflect_struct.meta(), &where_clause_options, - quote! { - let fields = [#field_generator]; - let info = #info_generator; - #bevy_reflect_path::TypeInfo::Struct(info) - }, + reflect_struct.to_type_info(), ); let type_path_impl = impl_type_path(reflect_struct.meta(), &where_clause_options); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs index 37984fc1f08d7..b9be958849313 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/tuple_structs.rs @@ -16,72 +16,20 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2:: .active_fields() .map(|field| Member::Unnamed(Index::from(field.index))) .collect::>(); - let field_types = reflect_struct.active_types(); let field_count = field_idents.len(); let field_indices = (0..field_count).collect::>(); let where_clause_options = reflect_struct.where_clause_options(); let get_type_registration_impl = reflect_struct.get_type_registration(&where_clause_options); - let hash_fn = reflect_struct - .meta() - .traits() - .get_hash_impl(bevy_reflect_path); let debug_fn = reflect_struct.meta().traits().get_debug_impl(); - let partial_eq_fn = reflect_struct - .meta() - .traits() - .get_partial_eq_impl(bevy_reflect_path) - .unwrap_or_else(|| { - quote! { - fn reflect_partial_eq(&self, value: &dyn #bevy_reflect_path::Reflect) -> #FQOption { - #bevy_reflect_path::tuple_struct_partial_eq(self, value) - } - } - }); - - #[cfg(feature = "documentation")] - let field_generator = { - let docs = reflect_struct - .active_fields() - .map(|field| quote::ToTokens::to_token_stream(&field.doc)); - quote! { - #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents).with_docs(#docs) ,)* - } - }; - - #[cfg(not(feature = "documentation"))] - let field_generator = { - quote! { - #(#bevy_reflect_path::UnnamedField::new::<#field_types>(#field_idents) ,)* - } - }; - - let string_name = struct_path.get_ident().unwrap().to_string(); - - #[cfg(feature = "documentation")] - let info_generator = { - let doc = reflect_struct.meta().doc(); - quote! { - #bevy_reflect_path::TupleStructInfo::new::(#string_name, &fields).with_docs(#doc) - } - }; - - #[cfg(not(feature = "documentation"))] - let info_generator = { - quote! { - #bevy_reflect_path::TupleStructInfo::new::(#string_name, &fields) - } - }; + let hash_fn = reflect_struct.get_hash_impl(); + let partial_eq_fn = reflect_struct.get_partial_eq_impl(); let typed_impl = impl_typed( reflect_struct.meta(), &where_clause_options, - quote! { - let fields = [#field_generator]; - let info = #info_generator; - #bevy_reflect_path::TypeInfo::TupleStruct(info) - }, + reflect_struct.to_type_info(), ); let type_path_impl = impl_type_path(reflect_struct.meta(), &where_clause_options); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs index e92383a746343..d44be3f8f4907 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/impls/values.rs @@ -13,23 +13,9 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream { let partial_eq_fn = meta.traits().get_partial_eq_impl(bevy_reflect_path); let debug_fn = meta.traits().get_debug_impl(); - #[cfg(feature = "documentation")] - let with_docs = { - let doc = quote::ToTokens::to_token_stream(meta.doc()); - Some(quote!(.with_docs(#doc))) - }; - #[cfg(not(feature = "documentation"))] - let with_docs: Option = None; - let where_clause_options = WhereClauseOptions::new_value(meta); - let typed_impl = impl_typed( - meta, - &where_clause_options, - quote! { - let info = #bevy_reflect_path::ValueInfo::new::() #with_docs; - #bevy_reflect_path::TypeInfo::Value(info) - }, - ); + + let typed_impl = impl_typed(meta, &where_clause_options, meta.to_type_info()); let type_path_impl = impl_type_path(meta, &where_clause_options); diff --git a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs index 2061782115988..7169701ee08b0 100644 --- a/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs +++ b/crates/bevy_reflect/bevy_reflect_derive/src/lib.rs @@ -92,15 +92,17 @@ pub(crate) static TYPE_NAME_ATTRIBUTE_NAME: &str = "type_name"; /// A custom implementation may be provided using `#[reflect(Debug(my_debug_func))]` where /// `my_debug_func` is the path to a function matching the signature: /// `(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result`. -/// * `#[reflect(PartialEq)]` will force the implementation of `Reflect::reflect_partial_eq` to rely on +/// * `#[reflect_value(PartialEq)]` will force the implementation of `Reflect::reflect_partial_eq` to rely on /// the type's [`PartialEq`] implementation. -/// A custom implementation may be provided using `#[reflect(PartialEq(my_partial_eq_func))]` where +/// A custom implementation may be provided using `#[reflect_value(PartialEq(my_partial_eq_func))]` where /// `my_partial_eq_func` is the path to a function matching the signature: /// `(&self, value: &dyn #bevy_reflect_path::Reflect) -> bool`. -/// * `#[reflect(Hash)]` will force the implementation of `Reflect::reflect_hash` to rely on +/// This may only be used for values derived with the `#[reflect_value]` attribute. +/// * `#[reflect_value(Hash)]` will force the implementation of `Reflect::reflect_hash` to rely on /// the type's [`Hash`] implementation. -/// A custom implementation may be provided using `#[reflect(Hash(my_hash_func))]` where +/// A custom implementation may be provided using `#[reflect_value(Hash(my_hash_func))]` where /// `my_hash_func` is the path to a function matching the signature: `(&self) -> u64`. +/// This may only be used for values derived with the `#[reflect_value]` attribute. /// * `#[reflect(Default)]` will register the `ReflectDefault` type data as normal. /// However, it will also affect how certain other operations are performed in order /// to improve performance and/or robustness. @@ -151,7 +153,28 @@ pub(crate) static TYPE_NAME_ATTRIBUTE_NAME: &str = "type_name"; /// What this does is register the `SerializationData` type within the `GetTypeRegistration` implementation, /// which will be used by the reflection serializers to determine whether or not the field is serializable. /// +/// ## `#[reflect(skip_hash)]` +/// +/// This attribute excludes the field from the hash computation in `Reflect::reflect_hash`. +/// +/// It's recommended that when using this attribute, the `#[reflect(skip_partial_eq)]` attribute also be used. +/// This is to uphold the following [property]: +/// ```text +/// a.reflect_partial_eq(b) -> a.reflect_hash() == b.reflect_hash() +/// ``` +/// +/// ## `#[reflect(skip_partial_eq)]` +/// +/// This attribute excludes the field from comparison in `Reflect::reflect_partial_eq`. +/// +/// It's recommended that when using this attribute, the `#[reflect(skip_hash)]` attribute also be used. +/// This is to uphold the following [property]: +/// ```text +/// a.reflect_partial_eq(b) -> a.reflect_hash() == b.reflect_hash() +/// ``` +/// /// [`reflect_trait`]: macro@reflect_trait +/// [property]: https://doc.rust-lang.org/std/hash/trait.Hash.html#hash-and-eq #[proc_macro_derive(Reflect, attributes(reflect, reflect_value, type_path, type_name))] pub fn derive_reflect(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); @@ -422,7 +445,7 @@ pub fn impl_reflect_value(input: TokenStream) -> TokenStream { /// use bevy::prelude::Vec3; /// /// impl_reflect_struct!( -/// #[reflect(PartialEq, Serialize, Deserialize, Default)] +/// #[reflect(Serialize, Deserialize, Default)] /// #[type_path = "bevy::prelude"] /// struct Vec3 { /// x: f32, diff --git a/crates/bevy_reflect/examples/reflect_docs.rs b/crates/bevy_reflect/examples/reflect_docs.rs index 10852185c00d8..55021c5786e59 100644 --- a/crates/bevy_reflect/examples/reflect_docs.rs +++ b/crates/bevy_reflect/examples/reflect_docs.rs @@ -43,7 +43,7 @@ fn main() { if let TypeInfo::Struct(struct_info) = player_info { for field in struct_info.iter() { let field_name = field.name(); - let field_docs = field.docs().unwrap_or(""); + let field_docs = field.meta().docs.unwrap_or(""); println!("-----[ Player::{field_name} ]-----\n{field_docs}"); } } diff --git a/crates/bevy_reflect/src/array.rs b/crates/bevy_reflect/src/array.rs index c1acb9835678b..6ae83799437ec 100644 --- a/crates/bevy_reflect/src/array.rs +++ b/crates/bevy_reflect/src/array.rs @@ -82,8 +82,7 @@ pub struct ArrayInfo { item_type_name: &'static str, item_type_id: TypeId, capacity: usize, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: ArrayMeta, } impl ArrayInfo { @@ -100,15 +99,13 @@ impl ArrayInfo { item_type_name: std::any::type_name::(), item_type_id: TypeId::of::(), capacity, - #[cfg(feature = "documentation")] - docs: None, + meta: ArrayMeta::new(), } } - /// Sets the docstring for this array. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this array. + pub fn with_meta(self, meta: ArrayMeta) -> Self { + Self { meta, ..self } } /// The compile-time capacity of the array. @@ -128,6 +125,11 @@ impl ArrayInfo { self.type_id } + /// The metadata of the array. + pub fn meta(&self) -> &ArrayMeta { + &self.meta + } + /// Check if the given type matches the array type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id @@ -149,11 +151,30 @@ impl ArrayInfo { pub fn item_is(&self) -> bool { TypeId::of::() == self.item_type_id } +} +/// Metadata for [arrays], accessed via [`ArrayInfo::meta`]. +/// +/// [arrays]: Array +#[derive(Clone, Debug)] +pub struct ArrayMeta { /// The docstring of this array, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl ArrayMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for ArrayMeta { + fn default() -> Self { + Self::new() } } @@ -375,13 +396,34 @@ impl<'a> ExactSizeIterator for ArrayIter<'a> {} /// Returns the `u64` hash of the given [array](Array). #[inline] -pub fn array_hash(array: &A) -> Option { +pub fn array_hash(value: &A) -> Option { let mut hasher = reflect_hasher(); - std::any::Any::type_id(array).hash(&mut hasher); - array.len().hash(&mut hasher); - for value in array.iter() { - hasher.write_u64(value.reflect_hash()?); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::Array(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for element in value.iter() { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for element in value.iter() { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + } } + Some(hasher.finish()) } @@ -412,17 +454,19 @@ pub fn array_apply(array: &mut A, reflect: &dyn Reflect) { /// /// Returns [`None`] if the comparison couldn't even be performed. #[inline] -pub fn array_partial_eq(array: &A, reflect: &dyn Reflect) -> Option { - match reflect.reflect_ref() { - ReflectRef::Array(reflect_array) if reflect_array.len() == array.len() => { - for (a, b) in array.iter().zip(reflect_array.iter()) { - let eq_result = a.reflect_partial_eq(b); - if let failed @ (Some(false) | None) = eq_result { - return failed; - } - } +pub fn array_partial_eq(a: &A, b: &dyn Reflect) -> Option { + let ReflectRef::Array(b) = b.reflect_ref() else { + return Some(false); + }; + + if a.len() != b.len() { + return Some(false); + } + + for (value_a, value_b) in a.iter().zip(b.iter()) { + if !value_a.reflect_partial_eq(value_b)? { + return Some(false); } - _ => return Some(false), } Some(true) diff --git a/crates/bevy_reflect/src/enums/enum_trait.rs b/crates/bevy_reflect/src/enums/enum_trait.rs index a4b9bec2a4512..c4546aea8bf3a 100644 --- a/crates/bevy_reflect/src/enums/enum_trait.rs +++ b/crates/bevy_reflect/src/enums/enum_trait.rs @@ -139,8 +139,7 @@ pub struct EnumInfo { variants: Box<[VariantInfo]>, variant_names: Box<[&'static str]>, variant_indices: HashMap<&'static str, usize>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: EnumMeta, } impl EnumInfo { @@ -167,15 +166,13 @@ impl EnumInfo { variants: variants.to_vec().into_boxed_slice(), variant_names, variant_indices, - #[cfg(feature = "documentation")] - docs: None, + meta: EnumMeta::new(), } } - /// Sets the docstring for this enum. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this enum. + pub fn with_meta(self, meta: EnumMeta) -> Self { + Self { meta, ..self } } /// A slice containing the names of all variants in order. @@ -243,15 +240,36 @@ impl EnumInfo { self.type_id } + /// The metadata of the enum. + pub fn meta(&self) -> &EnumMeta { + &self.meta + } + /// Check if the given type matches the enum type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} +#[derive(Clone, Debug)] +pub struct EnumMeta { /// The docstring of this enum, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl EnumMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for EnumMeta { + fn default() -> Self { + Self::new() } } diff --git a/crates/bevy_reflect/src/enums/helpers.rs b/crates/bevy_reflect/src/enums/helpers.rs index 09a3516c79d46..8c728cf1f6928 100644 --- a/crates/bevy_reflect/src/enums/helpers.rs +++ b/crates/bevy_reflect/src/enums/helpers.rs @@ -1,4 +1,9 @@ -use crate::{utility::reflect_hasher, Enum, Reflect, ReflectRef, VariantType}; +use crate::equality_utility::{ + compare_structs, compare_tuple_structs, extract_info, get_type_info_pair, +}; +use crate::utility::reflect_hasher; +use crate::{Enum, Reflect, ReflectRef, TypeInfo, VariantInfo, VariantType}; +use std::any::TypeId; use std::fmt::Debug; use std::hash::{Hash, Hasher}; @@ -6,73 +11,111 @@ use std::hash::{Hash, Hasher}; #[inline] pub fn enum_hash(value: &TEnum) -> Option { let mut hasher = reflect_hasher(); - std::any::Any::type_id(value).hash(&mut hasher); - value.variant_name().hash(&mut hasher); - value.variant_type().hash(&mut hasher); - for field in value.iter_fields() { - hasher.write_u64(field.value().reflect_hash()?); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::Enum(info) = info else { + return None; + }; + + let Some(variant) = info.variant(value.variant_name()) else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + Hash::hash(variant.name(), &mut hasher); + + match variant { + VariantInfo::Struct(info) => { + for field in info.iter() { + if field.meta().skip_hash { + continue; + } + + if let Some(value) = value.field(field.name()) { + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + VariantInfo::Tuple(info) => { + for field in info.iter() { + if field.meta().skip_hash { + continue; + } + + if let Some(value) = value.field_at(field.index()) { + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + VariantInfo::Unit(_) => {} + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + Hash::hash(&value.variant_name(), &mut hasher); + + for field in value.iter_fields() { + hasher.write_u64(field.value().reflect_hash()?); + } + } } + Some(hasher.finish()) } /// Compares an [`Enum`] with a [`Reflect`] value. /// -/// Returns true if and only if all of the following are true: +/// Returns `Some(true)` if and only if all of the following are true: /// - `b` is an enum; /// - `b` is the same variant as `a`; /// - For each field in `a`, `b` contains a field with the same name and /// [`Reflect::reflect_partial_eq`] returns `Some(true)` for the two field -/// values. +/// values[^1] +/// +/// Returns `None` if the comparison cannot be performed. +/// +/// [^1]: If a field is marked with `#[reflect(skip_partial_eq)]`, then it will be skipped +/// unless the corresponding field in `b` is not marked with `#[reflect(skip_partial_eq)]`, +/// in which case the comparison will return `Some(false)`. #[inline] pub fn enum_partial_eq(a: &TEnum, b: &dyn Reflect) -> Option { - // Both enums? - let ReflectRef::Enum(b) = b.reflect_ref() else { + let ReflectRef::Enum(b) = b.reflect_ref() else { return Some(false); }; - // Same variant name? if a.variant_name() != b.variant_name() { return Some(false); } - // Same variant type? if !a.is_variant(b.variant_type()) { return Some(false); } + let variant_name = a.variant_name(); + + let (info_a, info_b) = get_type_info_pair(a.as_reflect(), b.as_reflect()); + let info_a = extract_info!(info_a, TypeInfo::Enum(info) => info.variant(variant_name)); + let info_b = extract_info!(info_b, TypeInfo::Enum(info) => info.variant(variant_name)); + match a.variant_type() { VariantType::Struct => { - // Same struct fields? - for field in a.iter_fields() { - let field_name = field.name().unwrap(); - if let Some(field_value) = b.field(field_name) { - if let Some(false) | None = field_value.reflect_partial_eq(field.value()) { - // Fields failed comparison - return Some(false); - } - } else { - // Field does not exist - return Some(false); - } - } - Some(true) + let info_a = extract_info!(info_a, VariantInfo::Struct(info) => Some(info)); + let info_b = extract_info!(info_b, VariantInfo::Struct(info) => Some(info)); + + compare_structs!(a, b, info_a, info_b, accessor=.value) } VariantType::Tuple => { - // Same tuple fields? - for (i, field) in a.iter_fields().enumerate() { - if let Some(field_value) = b.field_at(i) { - if let Some(false) | None = field_value.reflect_partial_eq(field.value()) { - // Fields failed comparison - return Some(false); - } - } else { - // Field does not exist - return Some(false); - } - } - Some(true) + let info_a = extract_info!(info_a, VariantInfo::Tuple(info) => Some(info)); + let info_b = extract_info!(info_b, VariantInfo::Tuple(info) => Some(info)); + + compare_tuple_structs!(a, b, info_a, info_b, accessor=.field_at) } - _ => Some(true), + VariantType::Unit => Some(true), } } diff --git a/crates/bevy_reflect/src/enums/variants.rs b/crates/bevy_reflect/src/enums/variants.rs index 6901474041408..2970aecb47c92 100644 --- a/crates/bevy_reflect/src/enums/variants.rs +++ b/crates/bevy_reflect/src/enums/variants.rs @@ -73,13 +73,12 @@ impl VariantInfo { } } - /// The docstring of the underlying variant, if any. - #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&str> { + /// The metadata of the underlying variant. + pub fn meta(&self) -> &VariantMeta { match self { - Self::Struct(info) => info.docs(), - Self::Tuple(info) => info.docs(), - Self::Unit(info) => info.docs(), + Self::Struct(info) => info.meta(), + Self::Tuple(info) => info.meta(), + Self::Unit(info) => info.meta(), } } } @@ -91,8 +90,7 @@ pub struct StructVariantInfo { fields: Box<[NamedField]>, field_names: Box<[&'static str]>, field_indices: HashMap<&'static str, usize>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: VariantMeta, } impl StructVariantInfo { @@ -105,15 +103,13 @@ impl StructVariantInfo { fields: fields.to_vec().into_boxed_slice(), field_names, field_indices, - #[cfg(feature = "documentation")] - docs: None, + meta: VariantMeta::new(), } } - /// Sets the docstring for this variant. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this variant. + pub fn with_meta(self, meta: VariantMeta) -> Self { + Self { meta, ..self } } /// The name of this variant. @@ -161,10 +157,9 @@ impl StructVariantInfo { .collect() } - /// The docstring of this variant, if any. - #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + /// The metadata of the variant. + pub fn meta(&self) -> &VariantMeta { + &self.meta } } @@ -173,8 +168,7 @@ impl StructVariantInfo { pub struct TupleVariantInfo { name: &'static str, fields: Box<[UnnamedField]>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: VariantMeta, } impl TupleVariantInfo { @@ -183,15 +177,13 @@ impl TupleVariantInfo { Self { name, fields: fields.to_vec().into_boxed_slice(), - #[cfg(feature = "documentation")] - docs: None, + meta: VariantMeta::new(), } } - /// Sets the docstring for this variant. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this variant. + pub fn with_meta(self, meta: VariantMeta) -> Self { + Self { meta, ..self } } /// The name of this variant. @@ -214,10 +206,9 @@ impl TupleVariantInfo { self.fields.len() } - /// The docstring of this variant, if any. - #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + /// The metadata of the variant. + pub fn meta(&self) -> &VariantMeta { + &self.meta } } @@ -225,8 +216,7 @@ impl TupleVariantInfo { #[derive(Clone, Debug)] pub struct UnitVariantInfo { name: &'static str, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: VariantMeta, } impl UnitVariantInfo { @@ -234,15 +224,13 @@ impl UnitVariantInfo { pub fn new(name: &'static str) -> Self { Self { name, - #[cfg(feature = "documentation")] - docs: None, + meta: VariantMeta::new(), } } - /// Sets the docstring for this variant. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this variant. + pub fn with_meta(self, meta: VariantMeta) -> Self { + Self { meta, ..self } } /// The name of this variant. @@ -250,9 +238,30 @@ impl UnitVariantInfo { self.name } + /// The metadata of the variant. + pub fn meta(&self) -> &VariantMeta { + &self.meta + } +} + +#[derive(Clone, Debug)] +pub struct VariantMeta { /// The docstring of this variant, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl VariantMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for VariantMeta { + fn default() -> Self { + Self::new() } } diff --git a/crates/bevy_reflect/src/equality_utility.rs b/crates/bevy_reflect/src/equality_utility.rs new file mode 100644 index 0000000000000..c024d46df93cb --- /dev/null +++ b/crates/bevy_reflect/src/equality_utility.rs @@ -0,0 +1,179 @@ +//! Utilities for performing partial equality checks on [`Reflect`] types. + +use crate::{Reflect, TypeInfo}; + +/// Helper macro for extracting type information for use in [`Reflect::reflect_partial_eq`] implementations. +/// +/// `$info` is an [`Option`] that must match a pattern, `$pat`, and +/// return expression, `$expr`, with type `Option`. +/// +/// If `$info` does not match the givn pattern or `None`, the macro performs an early return +/// with the value `Some(false)`. +/// +/// [`Option`]: TypeInfo +macro_rules! extract_info { + ($info:ident, $pat:pat => $expr:expr) => {{ + match $info { + None => None, + Some($pat) => $expr, + _ => return Some(false), + } + }}; +} + +/// Helper macro for comparing two struct-like types with [`Reflect::reflect_partial_eq`]. +/// +/// By using a macro, we are able to share logic between standard structs and struct variants. +macro_rules! compare_structs { + ($a:ident, $b:ident, $info_a:ident, $info_b:ident $(, accessor=.$field_accessor:ident)? $(,)?) => {{ + // By default, we only need to perform a separate check on `$b` if it has + // a different number of fields than `$a`. + let mut needs_field_check = $a.field_len() != $b.field_len(); + + if $info_a.is_none() && $info_b.is_none() { + // Both are pure dynamic types, so no fields are skippable + if $a.field_len() != $b.field_len() { + return Some(false); + } + } + + for (index, value_a) in $a.iter_fields().enumerate() { + let value_a = value_a $(.$field_accessor())?; + let field_name = $a.name_at(index)?; + + let field_a = $info_a.and_then(|info| info.field(field_name)); + let field_b = $info_b.and_then(|info| info.field(field_name)); + match (field_a, field_b) { + (Some(field_a), Some(field_b)) => { + if field_a.meta().skip_partial_eq && field_b.meta().skip_partial_eq { + // Both fields have opted out of partial equality + continue; + } + + if field_a.meta().skip_partial_eq != field_b.meta().skip_partial_eq { + // Only one of the fields is required for partial equality + return Some(false); + } + } + (Some(field_info), None) => { + if field_info.meta().skip_partial_eq { + // Field doesn't exist on `$b`, but is skipped by `$a`. + // If the number of fields are the same, we have to check + // if the corresponding field on `$b` is also skipped. + needs_field_check |= $a.field_len() == $b.field_len(); + continue; + } + } + (None, Some(field_info)) => { + if field_info.meta().skip_partial_eq { + // Field exists on `$a` and `$b`, but is skipped by `$b`. + continue; + } + } + (None, None) => {} + } + + let Some(value_b) = $b.field(field_name) else { return Some(false); }; + let is_equal = value_a.reflect_partial_eq(value_b)?; + + if !is_equal { + return Some(false); + } + } + + // If 1 or more fields are missing from `b`, we need to check that they + // are all marked as `skip_partial_eq` so as to preserve symmetry. + if needs_field_check { + for (index, _) in $b.iter_fields().enumerate() { + let field_name = $b.name_at(index)?; + if let Some(field_b) = $info_b.and_then(|info| info.field(field_name)) { + if field_b.meta().skip_partial_eq { + continue; + } + } + + if $a.field(field_name).is_none() { + return Some(false); + } + } + } + + Some(true) + }}; +} + +/// Helper macro for comparing two tuple struct-like types with [`Reflect::reflect_partial_eq`]. +/// +/// By using a macro, we are able to share logic between standard tuple structs and tuple variants. +macro_rules! compare_tuple_structs { + ($a:ident, $b:ident, $info_a:ident, $info_b:ident, accessor=.$field_accessor:ident $(,)?) => {{ + if $info_a.is_none() && $info_b.is_none() { + // Both are pure dynamic types, so no fields are skippable + if $a.field_len() != $b.field_len() { + return Some(false); + } + } + + let a_len = $a.field_len(); + let b_len = $b.field_len(); + let max_len = a_len.max(b_len); + + for index in 0..max_len { + let field_a = $info_a.and_then(|info| info.field_at(index)); + let field_b = $info_b.and_then(|info| info.field_at(index)); + match (field_a, field_b) { + (Some(field_a), Some(field_b)) => { + if field_a.meta().skip_partial_eq && field_b.meta().skip_partial_eq { + // Both fields have opted out of partial equality + continue; + } + + if field_a.meta().skip_partial_eq != field_b.meta().skip_partial_eq { + // Only one of the fields is required for partial equality + return Some(false); + } + } + (Some(field_info), None) | (None, Some(field_info)) => { + if field_info.meta().skip_partial_eq { + continue; + } + } + (None, None) => {} + } + + let Some(value_a) = $a.$field_accessor(index) else { return Some(false); }; + let Some(value_b) = $b.$field_accessor(index) else { return Some(false); }; + let is_equal = value_a.reflect_partial_eq(value_b)?; + + if !is_equal { + return Some(false); + } + } + + Some(true) + }}; +} + +pub(crate) use compare_structs; +pub(crate) use compare_tuple_structs; +pub(crate) use extract_info; + +/// Helper function for returning a tuple of [`Option's`] for two [`Reflect`] values. +/// +/// This function will try to be smart about when it calls [`Reflect::get_represented_type_info`] +/// so that it avoids calling the method twice for concrete values of the same type. +/// +/// [`Option's`]: TypeInfo +pub(crate) fn get_type_info_pair( + a: &dyn Reflect, + b: &dyn Reflect, +) -> (Option<&'static TypeInfo>, Option<&'static TypeInfo>) { + let same_concrete_type = a.type_id() == b.type_id() && !a.is_dynamic() && !b.is_dynamic(); + if same_concrete_type || a.type_name() == b.type_name() { + // Fast path for the common case of two concrete/proxy values representing the same type + let info = a.get_represented_type_info(); + return (info, info); + } + + (a.get_represented_type_info(), b.get_represented_type_info()) +} diff --git a/crates/bevy_reflect/src/fields.rs b/crates/bevy_reflect/src/fields.rs index 62c5c332adecc..008c91e57cd15 100644 --- a/crates/bevy_reflect/src/fields.rs +++ b/crates/bevy_reflect/src/fields.rs @@ -7,8 +7,7 @@ pub struct NamedField { name: &'static str, type_name: &'static str, type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: FieldMeta, } impl NamedField { @@ -18,15 +17,13 @@ impl NamedField { name, type_name: std::any::type_name::(), type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, + meta: FieldMeta::new(), } } - /// Sets the docstring for this field. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this field. + pub fn with_meta(self, meta: FieldMeta) -> Self { + Self { meta, ..self } } /// The name of the field. @@ -46,16 +43,15 @@ impl NamedField { self.type_id } + /// The metadata of the field. + pub fn meta(&self) -> &FieldMeta { + &self.meta + } + /// Check if the given type matches the field type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } - - /// The docstring of this field, if any. - #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs - } } /// The unnamed field of a reflected tuple or tuple struct. @@ -64,8 +60,7 @@ pub struct UnnamedField { index: usize, type_name: &'static str, type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: FieldMeta, } impl UnnamedField { @@ -74,15 +69,13 @@ impl UnnamedField { index, type_name: std::any::type_name::(), type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, + meta: FieldMeta::new(), } } - /// Sets the docstring for this field. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this field. + pub fn with_meta(self, meta: FieldMeta) -> Self { + Self { meta, ..self } } /// Returns the index of the field. @@ -102,14 +95,53 @@ impl UnnamedField { self.type_id } + /// The metadata of the field. + pub fn meta(&self) -> &FieldMeta { + &self.meta + } + /// Check if the given type matches the field type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} +/// Metadata for both [named] and [unnamed] fields. +/// +/// [named]: NamedField +/// [unnamed]: UnnamedField +#[derive(Clone, Debug)] +pub struct FieldMeta { + /// This field should not be used in its container's [`Reflect::reflect_hash`] implementation. + /// + /// This may be configured when [deriving `Reflect`] by adding `#[reflect(skip_hash)]` to the field. + /// + /// [deriving `Reflect`]: bevy_reflect_derive::Reflect + pub skip_hash: bool, + /// This field should not be used in its container's [`Reflect::reflect_partial_eq`] implementation. + /// + /// This may be configured when [deriving `Reflect`] by adding `#[reflect(skip_partial_eq)]` to the field. + /// + /// [deriving `Reflect`]: bevy_reflect_derive::Reflect + pub skip_partial_eq: bool, /// The docstring of this field, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl FieldMeta { + pub const fn new() -> Self { + Self { + skip_hash: false, + skip_partial_eq: false, + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for FieldMeta { + fn default() -> Self { + Self::new() } } diff --git a/crates/bevy_reflect/src/impls/glam.rs b/crates/bevy_reflect/src/impls/glam.rs index e5f481e71c537..14e0aefc1c827 100644 --- a/crates/bevy_reflect/src/impls/glam.rs +++ b/crates/bevy_reflect/src/impls/glam.rs @@ -5,7 +5,7 @@ use bevy_reflect_derive::{impl_reflect_struct, impl_reflect_value}; use glam::*; impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct IVec2 { x: i32, @@ -13,7 +13,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct IVec3 { x: i32, @@ -22,7 +22,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct IVec4 { x: i32, @@ -33,7 +33,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct UVec2 { x: u32, @@ -41,7 +41,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct UVec3 { x: u32, @@ -50,7 +50,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, Hash, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct UVec4 { x: u32, @@ -60,7 +60,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Vec2 { x: f32, @@ -68,7 +68,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Vec3 { x: f32, @@ -77,7 +77,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Vec3A { x: f32, @@ -86,7 +86,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Vec4 { x: f32, @@ -97,7 +97,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct BVec2 { x: bool, @@ -105,7 +105,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct BVec3 { x: bool, @@ -114,7 +114,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct BVec4 { x: bool, @@ -125,7 +125,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DVec2 { x: f64, @@ -133,7 +133,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DVec3 { x: f64, @@ -142,7 +142,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DVec4 { x: f64, @@ -153,7 +153,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Mat2 { x_axis: Vec2, @@ -161,7 +161,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Mat3 { x_axis: Vec3, @@ -170,7 +170,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Mat3A { x_axis: Vec3A, @@ -179,7 +179,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Mat4 { x_axis: Vec4, @@ -190,7 +190,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DMat2 { x_axis: DVec2, @@ -198,7 +198,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DMat3 { x_axis: DVec3, @@ -207,7 +207,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DMat4 { x_axis: DVec4, @@ -218,7 +218,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Affine2 { matrix2: Mat2, @@ -226,7 +226,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct Affine3A { matrix3: Mat3A, @@ -235,7 +235,7 @@ impl_reflect_struct!( ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DAffine2 { matrix2: DMat2, @@ -243,7 +243,7 @@ impl_reflect_struct!( } ); impl_reflect_struct!( - #[reflect(Debug, PartialEq, Default)] + #[reflect(Debug, Default)] #[type_path = "glam"] struct DAffine3 { matrix3: DMat3, diff --git a/crates/bevy_reflect/src/impls/rect.rs b/crates/bevy_reflect/src/impls/rect.rs index b215a58599d20..1e4588184c58d 100644 --- a/crates/bevy_reflect/src/impls/rect.rs +++ b/crates/bevy_reflect/src/impls/rect.rs @@ -5,7 +5,7 @@ use bevy_math::{Rect, Vec2}; use bevy_reflect_derive::impl_reflect_struct; impl_reflect_struct!( - #[reflect(Debug, PartialEq, Serialize, Deserialize, Default)] + #[reflect(Debug, Serialize, Deserialize, Default)] #[type_path = "bevy_math"] struct Rect { min: Vec2, diff --git a/crates/bevy_reflect/src/impls/smallvec.rs b/crates/bevy_reflect/src/impls/smallvec.rs index 1f7bce2b8bcb4..ab171464e9d3d 100644 --- a/crates/bevy_reflect/src/impls/smallvec.rs +++ b/crates/bevy_reflect/src/impls/smallvec.rs @@ -1,6 +1,6 @@ use bevy_reflect_derive::impl_type_path; use smallvec::SmallVec; -use std::any::Any; +use std::any::{Any, TypeId}; use crate::utility::GenericTypeInfoCell; use crate::{ @@ -8,6 +8,7 @@ use crate::{ Reflect, ReflectFromPtr, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed, }; +use std::hash::{Hash, Hasher}; impl List for SmallVec where @@ -137,8 +138,42 @@ where Box::new(self.clone_dynamic()) } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - crate::list_partial_eq(self, value) + fn reflect_hash(&self) -> Option { + let mut hasher = crate::utility::reflect_hasher(); + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&self.len(), &mut hasher); + + for element in self { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, other: &dyn Reflect) -> Option { + if let Some(other) = other.downcast_ref::() { + for (a, b) in <[T::Item]>::iter(self).zip(<[T::Item]>::iter(other)) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } else { + let ReflectRef::List(other) = Reflect::reflect_ref(other) else { + return Some(false); + }; + + if other.len() != self.len() { + return Some(false); + } + + for (a, b) in self.iter().zip(other.iter()) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } + + Some(true) } } diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index ab8b482c18df0..ecfdb82bbb7be 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1,17 +1,18 @@ use crate::std_traits::ReflectDefault; use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned}; use crate::{ - impl_type_path, map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicEnum, - DynamicMap, Enum, EnumInfo, FromReflect, FromType, GetTypeRegistration, List, ListInfo, - ListIter, Map, MapInfo, MapIter, Reflect, ReflectDeserialize, ReflectMut, ReflectRef, - ReflectSerialize, TupleVariantInfo, TypeInfo, TypePath, TypeRegistration, Typed, - UnitVariantInfo, UnnamedField, ValueInfo, VariantFieldIter, VariantInfo, VariantType, + impl_type_path, map_apply, Array, ArrayInfo, ArrayIter, DynamicEnum, DynamicMap, Enum, + EnumInfo, FromReflect, FromType, GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, + MapIter, Reflect, ReflectDeserialize, ReflectMut, ReflectRef, ReflectSerialize, + TupleVariantInfo, TypeInfo, TypePath, TypeRegistration, Typed, UnitVariantInfo, UnnamedField, + ValueInfo, VariantFieldIter, VariantInfo, VariantType, }; use crate::utility::{ reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell, }; use bevy_reflect_derive::impl_reflect_value; +use std::any::TypeId; use std::fmt; use std::{ any::Any, @@ -333,11 +334,43 @@ macro_rules! impl_reflect_for_veclike { } fn reflect_hash(&self) -> Option { - crate::list_hash(self) + let mut hasher = reflect_hasher(); + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&self.len(), &mut hasher); + + for element in self { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + + Some(hasher.finish()) } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - crate::list_partial_eq(self, value) + fn reflect_partial_eq(&self, other: &dyn Reflect) -> Option { + if let Some(other) = other.downcast_ref::() { + if other.len() != self.len() { + return Some(false); + } + + for (a, b) in <$sub>::iter(self).zip(<$sub>::iter(other)) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } else { + let ReflectRef::List(other) = Reflect::reflect_ref(other) else { return Some(false); }; + + if other.len() != self.len() { + return Some(false); + } + + for (a, b) in self.iter().zip(other.iter()) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } + + Some(true) } } @@ -551,8 +584,40 @@ macro_rules! impl_reflect_for_hashmap { Box::new(self.clone_dynamic()) } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - map_partial_eq(self, value) + fn reflect_partial_eq(&self, other: &dyn Reflect) -> Option { + if let Some(other) = other.downcast_ref::() { + if other.len() != self.len() { + return Some(false); + } + + for (key, a) in Self::iter(self) { + let Some(b) = other.get(key) else { + return Some(false); + }; + + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } else { + let ReflectRef::Map(other) = Reflect::reflect_ref(other) else { return Some(false); }; + + if other.len() != self.len() { + return Some(false); + } + + for (key, a) in self.iter() { + let Some(b) = other.get(key) else { + return Some(false); + }; + + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } + + Some(true) } } @@ -726,12 +791,42 @@ impl Reflect for [T; N] { #[inline] fn reflect_hash(&self) -> Option { - crate::array_hash(self) + let mut hasher = reflect_hasher(); + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&self.len(), &mut hasher); + + for element in self { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + + Some(hasher.finish()) } #[inline] - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - crate::array_partial_eq(self, value) + fn reflect_partial_eq(&self, other: &dyn Reflect) -> Option { + if let Some(other) = other.downcast_ref::() { + for (a, b) in <[T]>::iter(self).zip(<[T]>::iter(other)) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } else { + let ReflectRef::Array(other) = Reflect::reflect_ref(other) else { + return Some(false); + }; + + if other.len() != self.len() { + return Some(false); + } + + for (a, b) in self.iter().zip(other.iter()) { + if !a.reflect_partial_eq(b)? { + return Some(false); + } + } + } + + Some(true) } } @@ -975,7 +1070,20 @@ impl Reflect for Option { } fn reflect_hash(&self) -> Option { - crate::enum_hash(self) + let mut hasher = reflect_hasher(); + Hash::hash(&TypeId::of::(), &mut hasher); + + match self { + None => { + Hash::hash("None", &mut hasher); + } + Some(value) => { + Hash::hash("Some", &mut hasher); + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + + Some(Hasher::finish(&hasher)) } fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 9a70da803f215..7d1ffdb61c6f8 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -444,6 +444,7 @@ #![allow(clippy::type_complexity)] mod array; +mod equality_utility; mod fields; mod from_reflect; mod list; @@ -617,8 +618,7 @@ mod tests { #[test] fn reflect_map() { - #[derive(Reflect, Hash)] - #[reflect(Hash)] + #[derive(Reflect)] struct Foo { a: u32, b: String, @@ -677,15 +677,732 @@ mod tests { fn reflect_map_no_hash() { #[derive(Reflect)] struct Foo { - a: u32, + a: f32, } - let foo = Foo { a: 1 }; + let foo = Foo { a: 1.23 }; let mut map = DynamicMap::default(); map.insert(foo, 10u32); } + mod reflect_hash { + use super::*; + + /// Helper macro for generating tests for [`Reflect::reflect_hash`]. + /// + /// # Arguments + /// * `$title` - (Required) The string literal title of the test case. + /// * `$a` - (Required) A known hashable value. Should implement [`Clone`]. + /// * `$b` - (Optional) A known hashable value with the same type as `$a` but a different value. + /// * `$c` - (Optional) A known non-hashable value. + /// + /// # Description + /// + /// Tests the following: + /// 1. `$a` should return a hash (concrete and dynamic) + /// 2. `$a` should return the same hash as its clone (concrete and dynamic) + /// 3. `$a` should not return the same hash as an equivalent non-proxy dynamic value + /// 4. `$a` should not return the same hash as `$b` (concrete and dynamic) + /// 5. `$c` should not return a hash (concrete and dynamic) + /// 6. Two non-proxy dynamics should return the same hash if they are equivalent + /// + macro_rules! validate_hash { + ($title: literal: $a: ident, $b: ident, $c: ident $(,)?) => {{ + validate_hash!($title: $a, $b); + + // 5. + assert!( + $c.reflect_hash().is_none(), + "{}: expected `c` to not be hashable", + $title + ); + assert!( + $c.clone_value().reflect_hash().is_none(), + "{}: expected proxy of `c` to not be hashable", + $title + ); + }}; + ($title: literal: $a: ident, $b: ident $(,)?) => {{ + use std::any::Any; + + assert_eq!( + $a.type_id(), $b.type_id(), + "{}: expected `a` and `b` to be the same type", + $title + ); + + validate_hash!($title: $a); + + // 4. + assert_ne!( + $a.reflect_hash(), + $b.reflect_hash(), + "{}: expected `a` to return a different hash than `b`", + $title + ); + assert_ne!( + $a.reflect_hash(), + $b.clone_value().reflect_hash(), + "{}: expected `a` to return a different hash than a proxy of `b`", + $title + ); + }}; + ($title: literal: $a: ident $(,)?) => {{ + // 1. + assert!( + $a.reflect_hash().is_some(), + "{}: expected `a` to be hashable", + $title + ); + assert!( + $a.clone_value().reflect_hash().is_some(), + "{}: expected proxy of `a` to be hashable", + $title + ); + + // 2. + let a2 = $a.clone(); + assert_eq!( + $a.reflect_hash(), + a2.reflect_hash(), + "{}: expected `a` to return the same hash as its clone", + $title + ); + assert_eq!( + $a.reflect_hash(), + a2.clone_value().reflect_hash(), + "{}: expected `a` to return the same hash as a proxy of its clone", + $title + ); + + // 3. + let mut a3 = $a.clone_dynamic(); + a3.set_represented_type(None); + assert_ne!( + $a.reflect_hash(), + a3.reflect_hash(), + "{}: expected `a` to return a different hash than a dynamic with the same value but not a proxy", + $title + ); + + // 6. + let a4 = $a.clone_dynamic(); + assert_eq!( + a4.reflect_hash(), + a4.clone_value().reflect_hash(), + "{}: expected two non-proxy dynamic values of `a` to return the same hash", + $title + ); + }}; + } + + #[test] + fn should_hash_value() { + #[derive(Reflect, Hash, Clone)] + #[reflect_value(Hash)] + struct Foo(u32); + + #[derive(Reflect, Clone)] + #[reflect_value] + struct Baz(/* Note: NOT f32 */ u32); + + // === Primitive === // + let value = 123u32; + assert!(value.reflect_hash().is_some()); + + // === Custom === // + let value = Foo(123); + assert!(value.reflect_hash().is_some()); + + let value2 = Foo(123); + assert_eq!(value.reflect_hash(), value2.reflect_hash()); + + let value3 = Foo(456); + assert_ne!(value.reflect_hash(), value3.reflect_hash()); + + // === Custom (not hashable) === // + let value = Baz(123); + assert!(value.reflect_hash().is_none()); + } + + #[test] + fn should_hash_tuple() { + let a = (123, 345, (678,)); + let b = (123, 345, (679,)); + let c = (1.23,); + validate_hash!("Tuple": a, b, c); + } + + #[test] + fn should_hash_array() { + let a = [123, 345, 678]; + let b = [123, 345, 679]; + let c = [1.23]; + validate_hash!("Array": a, b, c); + } + + #[test] + fn should_hash_list() { + let a = vec![123, 345, 678]; + let b = vec![123, 345, 679]; + let c = vec![1.23]; + validate_hash!("List": a, b, c); + } + + #[test] + fn should_not_hash_map() { + // Note: The default `HashMap` is not hashable since it is unordered. + // However, other kinds of maps are hashable, such as `BTreeMap`. + // Once we support maps like `BTreeMap`, we can update this test. + let map = HashMap::::new(); + assert!(map.reflect_hash().is_none()); + } + + #[test] + fn should_hash_unit_struct() { + #[derive(Reflect)] + struct Foo; + + #[derive(Reflect)] + struct Bar; + + let foo = Foo; + assert!(foo.reflect_hash().is_some()); + + let foo2 = Foo; + assert_eq!(foo.reflect_hash(), foo2.reflect_hash()); + + let bar = Bar; + assert_ne!(foo.reflect_hash(), bar.reflect_hash()); + } + + #[test] + fn should_hash_tuple_struct() { + #[derive(Reflect, Clone)] + struct Foo(u32, #[reflect(skip_hash)] f32, Bar); + + #[derive(Reflect, Clone)] + struct Bar(u32); + + #[derive(Reflect)] + struct Baz(f32); + + let a = Foo(123, 3.45, Bar(678)); + let b = Foo(123, 3.45, Bar(679)); + let c = Baz(1.23); + + validate_hash!("Tuple Struct": a, b, c); + } + + #[test] + fn should_hash_struct() { + #[derive(Reflect, Clone)] + struct Foo { + x: u32, + #[reflect(skip_hash)] + y: f32, + z: Bar, + } + + #[derive(Reflect, Clone)] + struct Bar { + value: u32, + } + + #[derive(Reflect)] + struct Baz { + value: f32, + } + + let a = Foo { + x: 123, + y: 3.45, + z: Bar { value: 678 }, + }; + + let b = Foo { + x: 123, + y: 3.45, + z: Bar { value: 679 }, + }; + + let c = Baz { value: 1.23 }; + + validate_hash!("Struct": a, b, c); + } + + #[test] + fn should_hash_enum() { + #[derive(Reflect, Clone)] + enum Foo { + UnitA, + UnitB, + Tuple(u32, #[reflect(skip_hash)] f32, Bar), + Struct { + a: u32, + #[reflect(skip_hash)] + b: f32, + c: Bar, + }, + } + + #[derive(Reflect, Clone)] + enum Bar { + UnitA, + Tuple(u32), + Struct { a: u32 }, + } + + #[derive(Reflect, Clone)] + enum Baz { + Unit, + Tuple(f32), + Struct { a: f32 }, + } + + // === Unit Variant === // + let a = Foo::UnitA; + let b = Foo::UnitB; + validate_hash!("Unit Variant": a, b); + + let c = Baz::Unit; + validate_hash!("Unit Variant (non-hashable sibling variants)": c); + + // === Tuple Variant === // + let a = Foo::Tuple(123, 3.45, Bar::Tuple(678)); + let b = Foo::Tuple(123, 3.45, Bar::Tuple(679)); + let c = Baz::Tuple(1.23); + validate_hash!("Tuple Variant": a, b, c); + + // === Struct Variant === // + let a = Foo::Struct { + a: 123, + b: 3.45, + c: Bar::Struct { a: 678 }, + }; + let b = Foo::Struct { + a: 123, + b: 3.45, + c: Bar::Struct { a: 679 }, + }; + let c = Baz::Struct { a: 1.23 }; + validate_hash!("Struct Variant": a, b, c); + } + } + + mod reflect_partial_eq { + use super::*; + + #[derive(Reflect, Clone)] + #[reflect_value] + struct NotComparable; + + /// Helper macro for generating tests for [`Reflect::reflect_partial_eq`]. + /// + /// # Arguments + /// * `$title` - (Required) The string literal title of the test case. + /// * `$a` - (Required) A known comparable value. Should implement [`Clone`]. + /// * `$b` - (Optional) A known comparable value but not equivalent to `$a`. + /// * `$c` - (Optional) A known incomparable value. + /// + /// # Description + /// + /// Tests the following: + /// 1. `$a` should be partially equal to its clone (concrete, dynamic other, and dynamic self) + /// 2. `$a` should be partially equal to an equivalent non-proxy dynamic value (dynamic other and dynamic self) + /// 3. `$a` should not be partially equal to `$b` (concrete, dynamic other, and dynamic self) + /// 4. `$c` should not be comparable (concrete, dynamic other, and dynamic self) + /// 5. Two non-proxy dynamics should be partially equal if they are equivalent + /// + macro_rules! validate_partial_eq { + ($title: literal: $a: ident, $b: ident, $c: ident $(,)?) => {{ + validate_partial_eq!($title: $a, $b); + + // 4. + assert!( + $c.reflect_partial_eq(&$c).is_none(), + "{}: expected `c` to not be comparable", + $title + ); + let c2 = $c.clone_value(); + assert!( + $c.reflect_partial_eq(&*c2).is_none(), + "{}: expected proxy of `c` to not be comparable (dynamic other)", + $title + ); + assert!( + c2.reflect_partial_eq(&$c).is_none(), + "{}: expected proxy of `c` to not be comparable (dynamic self)", + $title + ); + }}; + ($title: literal: $a: ident, $b: ident $(,)?) => {{ + validate_partial_eq!($title: $a); + + // 3. + assert_eq!( + Some(false), + $a.reflect_partial_eq(&$b), + "{}: expected `a` to not be partially equal to `b`", + $title + ); + assert_eq!( + Some(false), + $a.reflect_partial_eq(&*$b.clone_value()), + "{}: expected `a` to not be partially equal to a proxy of `b`", + $title + ); + assert_eq!( + Some(false), + $b.clone_value().reflect_partial_eq(&$a), + "{}: expected proxy of `b` to not be partially equal to `a`", + $title + ); + }}; + ($title: literal: $a: ident $(,)?) => {{ + // 1. + let a2 = $a.clone(); + assert_eq!( + Some(true), + $a.reflect_partial_eq(&a2), + "{}: expected `a` to partially equal its clone", + $title + ); + assert_eq!( + Some(true), + $a.reflect_partial_eq(&*a2.clone_value()), + "{}: expected `a` to partially equal a proxy of its clone", + $title + ); + assert_eq!( + Some(true), + a2.clone_value().reflect_partial_eq(&$a), + "{}: expected a proxy of `a` to partially equal `a`", + $title + ); + + // 2. + let mut a3 = $a.clone_dynamic(); + a3.set_represented_type(None); + assert_eq!( + Some(true), + $a.reflect_partial_eq(&a3), + "{}: expected `a` to partially equal a dynamic with the same value but not a proxy", + $title + ); + assert_eq!( + Some(true), + a3.reflect_partial_eq(&$a), + "{}: expected dynamic with the same value as `a` but not a proxy to partially equal `a`", + $title + ); + + // 5. + let a4 = $a.clone_dynamic(); + assert_eq!( + Some(true), + a4.reflect_partial_eq(&*a4.clone_value()), + "{}: expected two non-proxy dynamic values of `a` tobe partially equal", + $title + ); + }}; + } + + #[test] + fn should_partial_eq_value() { + #[derive(Reflect, PartialEq, Clone)] + #[reflect_value(PartialEq)] + struct Foo(u32); + + #[derive(Reflect, Clone)] + #[reflect_value] + struct Baz(u32); + + // === Primitive === // + let a = 123u32; + let b = 123u32; + assert_eq!(Some(true), a.reflect_partial_eq(&b)); + + // === Custom === // + let a = Foo(123); + let b = Foo(123); + assert_eq!(Some(true), a.reflect_partial_eq(&b)); + + let a = Foo(123); + let b = Foo(456); + assert_eq!(Some(false), a.reflect_partial_eq(&b)); + + // === Custom (not comparable) === // + let a = Baz(123); + let b = Baz(123); + assert!(a.reflect_partial_eq(&b).is_none()); + } + + #[test] + fn should_partial_eq_tuple() { + let a = (123, 345, (678,)); + let b = (123, 345, (679,)); + let c = (NotComparable,); + validate_partial_eq!("Tuple": a, b, c); + + let a = (123, 345, (678,)); + let b = (123, 345, (678,), 0); + validate_partial_eq!("Tuple (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_array() { + let a = [123, 345, 678]; + let b = [123, 345, 679]; + let c = [NotComparable]; + validate_partial_eq!("Array": a, b, c); + + let a = [123, 345, 678]; + let b = [123, 345, 678, 0]; + validate_partial_eq!("Array (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_list() { + let a = vec![123, 345, 678]; + let b = vec![123, 345, 679]; + let c = vec![NotComparable]; + validate_partial_eq!("List": a, b, c); + + let a = vec![123, 345, 678]; + let b = vec![123, 345, 678, 0]; + validate_partial_eq!("List (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_map() { + let a = HashMap::from([(123, 1.23), (345, 3.45), (678, 6.78)]); + let b = HashMap::from([(123, 1.23), (345, 3.45), (678, 6.79)]); + let c = HashMap::from([(123, NotComparable)]); + validate_partial_eq!("Map": a, b, c); + + let a = HashMap::from([(123, 1.23), (345, 3.45), (678, 6.78)]); + let b = HashMap::from([(123, 1.23), (345, 3.45), (678, 6.78), (0, 0.0)]); + validate_partial_eq!("Map (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_unit_struct() { + #[derive(Reflect)] + struct Foo; + + #[derive(Reflect)] + struct Bar; + + let a = Foo; + let b = Foo; + assert_eq!( + Some(true), + a.reflect_partial_eq(&b), + "expected unit struct to be partially equal to itself" + ); + + let a = Foo; + let b = Bar; + assert_eq!( + Some(true), + a.reflect_partial_eq(&b), + "expected unit struct to be partially equal to another unit struct" + ); + } + + #[test] + fn should_partial_eq_tuple_struct() { + #[derive(Reflect, Clone)] + struct Foo(u32, #[reflect(skip_partial_eq)] NotComparable, Bar); + + #[derive(Reflect, Clone)] + struct Bar(u32); + + #[derive(Reflect)] + struct Baz(NotComparable); + + let a = Foo(123, NotComparable, Bar(678)); + let b = Foo(123, NotComparable, Bar(679)); + let c = Baz(NotComparable); + + validate_partial_eq!("Tuple Struct": a, b, c); + + let a = Foo(123, NotComparable, Bar(678)); + let mut b = a.clone_dynamic(); + b.insert(123); + validate_partial_eq!("Tuple Struct (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_struct() { + #[derive(Reflect, Clone)] + struct Foo { + x: u32, + #[reflect(skip_partial_eq)] + y: NotComparable, + z: Bar, + } + + #[derive(Reflect, Clone)] + struct Bar { + value: u32, + } + + #[derive(Reflect)] + struct Baz { + value: NotComparable, + } + + let a = Foo { + x: 123, + y: NotComparable, + z: Bar { value: 678 }, + }; + + let b = Foo { + x: 123, + y: NotComparable, + z: Bar { value: 679 }, + }; + + let c = Baz { + value: NotComparable, + }; + + validate_partial_eq!("Struct": a, b, c); + + let a = Foo { + x: 123, + y: NotComparable, + z: Bar { value: 678 }, + }; + let mut b = a.clone_dynamic(); + b.insert("w", 0); + validate_partial_eq!("Struct (different lengths)": a, b); + } + + #[test] + fn should_partial_eq_struct_with_different_skipped_fields() { + #[derive(Reflect, Clone)] + struct Foo { + #[reflect(skip_partial_eq)] + a: u32, + b: u32, + } + + #[derive(Reflect, Clone)] + struct Bar { + a: u32, + b: u32, + } + + #[derive(Reflect, Clone)] + struct Baz { + #[allow(dead_code)] + #[reflect(ignore)] + a: u32, + b: u32, + } + + let foo = Foo { a: 123, b: 456 }; + let bar = Bar { a: 321, b: 456 }; + assert_eq!(Some(false), foo.reflect_partial_eq(&bar)); + assert_eq!(Some(false), bar.reflect_partial_eq(&foo)); + + let foo = Foo { a: 123, b: 456 }; + let baz = Baz { a: 321, b: 456 }; + assert_eq!(Some(true), foo.reflect_partial_eq(&baz)); + assert_eq!(Some(true), baz.reflect_partial_eq(&foo)); + } + + #[test] + fn should_partial_eq_tuple_struct_with_different_skipped_fields() { + #[derive(Reflect, Clone)] + struct Foo(u32, #[reflect(skip_partial_eq)] u32); + + #[derive(Reflect, Clone)] + struct Bar(u32, u32); + + #[derive(Reflect, Clone)] + struct Baz( + u32, + #[allow(dead_code)] + #[reflect(ignore)] + u32, + ); + + let foo = Foo(123, 456); + let bar = Bar(123, 654); + assert_eq!(Some(false), foo.reflect_partial_eq(&bar)); + assert_eq!(Some(false), bar.reflect_partial_eq(&foo)); + + let foo = Foo(123, 456); + let baz = Baz(123, 654); + assert_eq!(Some(true), foo.reflect_partial_eq(&baz)); + assert_eq!(Some(true), baz.reflect_partial_eq(&foo)); + } + + #[test] + fn should_partial_eq_enum() { + #[derive(Reflect, Clone)] + enum Foo { + UnitA, + UnitB, + Tuple(u32, #[reflect(skip_partial_eq)] NotComparable, Bar), + Struct { + a: u32, + #[reflect(skip_partial_eq)] + b: NotComparable, + c: Bar, + }, + } + + #[derive(Reflect, Clone)] + enum Bar { + UnitA, + Tuple(u32), + Struct { a: u32 }, + } + + #[derive(Reflect, Clone)] + enum Baz { + Unit, + Tuple(NotComparable), + Struct { a: NotComparable }, + } + + // === Unit Variant === // + let a = Foo::UnitA; + let b = Foo::UnitB; + validate_partial_eq!("Unit Variant": a, b); + + let c = Baz::Unit; + validate_partial_eq!("Unit Variant (incomparable sibling variants)": c); + + // === Tuple Variant === // + let a = Foo::Tuple(123, NotComparable, Bar::Tuple(678)); + let b = Foo::Tuple(123, NotComparable, Bar::Tuple(679)); + let c = Baz::Tuple(NotComparable); + validate_partial_eq!("Tuple Variant": a, b, c); + + // === Struct Variant === // + let a = Foo::Struct { + a: 123, + b: NotComparable, + c: Bar::Struct { a: 678 }, + }; + let b = Foo::Struct { + a: 123, + b: NotComparable, + c: Bar::Struct { a: 679 }, + }; + let c = Baz::Struct { a: NotComparable }; + validate_partial_eq!("Struct Variant": a, b, c); + } + } + #[test] fn reflect_ignore() { #[derive(Reflect)] @@ -835,7 +1552,6 @@ mod tests { #[test] fn reflect_complex_patch() { #[derive(Reflect, Eq, PartialEq, Debug)] - #[reflect(PartialEq)] struct Foo { a: u32, #[reflect(ignore)] @@ -849,7 +1565,6 @@ mod tests { } #[derive(Reflect, Eq, PartialEq, Clone, Debug)] - #[reflect(PartialEq)] struct Bar { x: u32, } @@ -1087,7 +1802,6 @@ mod tests { #[test] fn reflect_take() { #[derive(Reflect, Debug, PartialEq)] - #[reflect(PartialEq)] struct Bar { x: u32, } @@ -1612,9 +2326,9 @@ mod tests { let info = ::type_info(); if let TypeInfo::Struct(info) = info { let mut fields = info.iter(); - assert_eq!(Some(" The name"), fields.next().unwrap().docs()); - assert_eq!(Some(" The index"), fields.next().unwrap().docs()); - assert_eq!(None, fields.next().unwrap().docs()); + assert_eq!(Some(" The name"), fields.next().unwrap().meta().docs); + assert_eq!(Some(" The index"), fields.next().unwrap().meta().docs); + assert_eq!(None, fields.next().unwrap().meta().docs); } else { panic!("expected struct info"); } @@ -1641,22 +2355,22 @@ mod tests { let info = ::type_info(); if let TypeInfo::Enum(info) = info { let mut variants = info.iter(); - assert_eq!(None, variants.next().unwrap().docs()); + assert_eq!(None, variants.next().unwrap().meta().docs); let variant = variants.next().unwrap(); - assert_eq!(Some(" Option A"), variant.docs()); + assert_eq!(Some(" Option A"), variant.meta().docs); if let VariantInfo::Tuple(variant) = variant { let field = variant.field_at(0).unwrap(); - assert_eq!(Some(" Index"), field.docs()); + assert_eq!(Some(" Index"), field.meta().docs); } else { panic!("expected tuple variant") } let variant = variants.next().unwrap(); - assert_eq!(Some(" Option B"), variant.docs()); + assert_eq!(Some(" Option B"), variant.meta().docs); if let VariantInfo::Struct(variant) = variant { let field = variant.field_at(0).unwrap(); - assert_eq!(Some(" Name"), field.docs()); + assert_eq!(Some(" Name"), field.meta().docs); } else { panic!("expected struct variant") } @@ -1796,9 +2510,8 @@ bevy_reflect::tests::should_reflect_debug::Test { #[test] fn multiple_reflect_lists() { - #[derive(Hash, PartialEq, Reflect)] - #[reflect(Debug, Hash)] - #[reflect(PartialEq)] + #[derive(PartialEq, Reflect)] + #[reflect(Debug)] struct Foo(i32); impl Debug for Foo { diff --git a/crates/bevy_reflect/src/list.rs b/crates/bevy_reflect/src/list.rs index 94c269c5203c4..a0d7a3dd1fcfc 100644 --- a/crates/bevy_reflect/src/list.rs +++ b/crates/bevy_reflect/src/list.rs @@ -112,8 +112,7 @@ pub struct ListInfo { type_id: TypeId, item_type_name: &'static str, item_type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: ListMeta, } impl ListInfo { @@ -124,15 +123,13 @@ impl ListInfo { type_id: TypeId::of::(), item_type_name: std::any::type_name::(), item_type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, + meta: ListMeta::new(), } } - /// Sets the docstring for this list. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this list. + pub fn with_meta(self, meta: ListMeta) -> Self { + Self { meta, ..self } } /// The [type name] of the list. @@ -147,6 +144,11 @@ impl ListInfo { self.type_id } + /// The metadata of the list. + pub fn meta(&self) -> &ListMeta { + &self.meta + } + /// Check if the given type matches the list type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id @@ -168,11 +170,30 @@ impl ListInfo { pub fn item_is(&self) -> bool { TypeId::of::() == self.item_type_id } +} +/// Metadata for [lists], accessed via [`ListInfo::meta`]. +/// +/// [lists]: List +#[derive(Clone, Debug)] +pub struct ListMeta { /// The docstring of this list, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl ListMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for ListMeta { + fn default() -> Self { + Self::new() } } @@ -408,13 +429,34 @@ impl<'a> ExactSizeIterator for ListIter<'a> {} /// Returns the `u64` hash of the given [list](List). #[inline] -pub fn list_hash(list: &L) -> Option { +pub fn list_hash(value: &L) -> Option { let mut hasher = reflect_hasher(); - std::any::Any::type_id(list).hash(&mut hasher); - list.len().hash(&mut hasher); - for value in list.iter() { - hasher.write_u64(value.reflect_hash()?); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::List(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for element in value.iter() { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for element in value.iter() { + Hash::hash(&element.reflect_hash()?, &mut hasher); + } + } } + Some(hasher.finish()) } @@ -453,18 +495,17 @@ pub fn list_apply(a: &mut L, b: &dyn Reflect) { /// Returns [`None`] if the comparison couldn't even be performed. #[inline] pub fn list_partial_eq(a: &L, b: &dyn Reflect) -> Option { - let ReflectRef::List(list) = b.reflect_ref() else { + let ReflectRef::List(b) = b.reflect_ref() else { return Some(false); }; - if a.len() != list.len() { + if a.len() != b.len() { return Some(false); } - for (a_value, b_value) in a.iter().zip(list.iter()) { - let eq_result = a_value.reflect_partial_eq(b_value); - if let failed @ (Some(false) | None) = eq_result { - return failed; + for (a_value, b_value) in a.iter().zip(b.iter()) { + if !a_value.reflect_partial_eq(b_value)? { + return Some(false); } } diff --git a/crates/bevy_reflect/src/map.rs b/crates/bevy_reflect/src/map.rs index e31f4280c2aa6..fa5288f4c6fe4 100644 --- a/crates/bevy_reflect/src/map.rs +++ b/crates/bevy_reflect/src/map.rs @@ -1,10 +1,11 @@ use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; -use std::hash::Hash; +use std::hash::{Hash, Hasher}; use bevy_reflect_derive::impl_type_path; use bevy_utils::{Entry, HashMap}; +use crate::utility::reflect_hasher; use crate::{self as bevy_reflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo}; /// A trait used to power [map-like] operations via [reflection]. @@ -99,8 +100,7 @@ pub struct MapInfo { key_type_id: TypeId, value_type_name: &'static str, value_type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: MapMeta, } impl MapInfo { @@ -113,15 +113,13 @@ impl MapInfo { key_type_id: TypeId::of::(), value_type_name: std::any::type_name::(), value_type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, + meta: MapMeta::new(), } } - /// Sets the docstring for this map. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this map. + pub fn with_meta(self, meta: MapMeta) -> Self { + Self { meta, ..self } } /// The [type name] of the map. @@ -136,6 +134,11 @@ impl MapInfo { self.type_id } + /// The metadata of the struct. + pub fn meta(&self) -> &MapMeta { + &self.meta + } + /// Check if the given type matches the map type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id @@ -174,11 +177,30 @@ impl MapInfo { pub fn value_is(&self) -> bool { TypeId::of::() == self.value_type_id } +} +/// Metadata for [maps], accessed via [`MapInfo::meta`]. +/// +/// [maps]: Map +#[derive(Clone, Debug)] +pub struct MapMeta { /// The docstring of this map, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl MapMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for MapMeta { + fn default() -> Self { + Self::new() } } @@ -424,6 +446,43 @@ impl IntoIterator for DynamicMap { impl<'a> ExactSizeIterator for MapIter<'a> {} +/// Returns the `u64` hash of the given [map](Map). +#[inline] +pub fn map_hash(value: &M) -> Option { + let mut hasher = reflect_hasher(); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::Map(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&info.key_type_id(), &mut hasher); + Hash::hash(&info.value_type_id(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for (key, value) in value.iter() { + Hash::hash(&key.reflect_hash()?, &mut hasher); + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.len(), &mut hasher); + + for (key, value) in value.iter() { + Hash::hash(&key.reflect_hash()?, &mut hasher); + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + + Some(hasher.finish()) +} + /// Compares a [`Map`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: @@ -435,21 +494,20 @@ impl<'a> ExactSizeIterator for MapIter<'a> {} /// Returns [`None`] if the comparison couldn't even be performed. #[inline] pub fn map_partial_eq(a: &M, b: &dyn Reflect) -> Option { - let ReflectRef::Map(map) = b.reflect_ref() else { + let ReflectRef::Map(b) = b.reflect_ref() else { return Some(false); }; - if a.len() != map.len() { + if a.len() != b.len() { return Some(false); } - for (key, value) in a.iter() { - if let Some(map_value) = map.get(key) { - let eq_result = value.reflect_partial_eq(map_value); - if let failed @ (Some(false) | None) = eq_result { - return failed; - } - } else { + for (key, value_a) in a.iter() { + let Some(value_b) = b.get(key) else { + return Some(false); + }; + + if !value_a.reflect_partial_eq(value_b)? { return Some(false); } } diff --git a/crates/bevy_reflect/src/reflect.rs b/crates/bevy_reflect/src/reflect.rs index 9c33ff6753bb5..8cf2b49e8624d 100644 --- a/crates/bevy_reflect/src/reflect.rs +++ b/crates/bevy_reflect/src/reflect.rs @@ -183,17 +183,52 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync { /// use those subtraits' respective `clone_dynamic` methods. fn clone_value(&self) -> Box; - /// Returns a hash of the value (which includes the type). + /// Returns a hash of the value. /// - /// If the underlying type does not support hashing, returns `None`. + /// If the underlying type or any of its members do not support hashing, returns `None`. + /// + /// # For Implementors + /// + /// For value types (i.e. [`ReflectRef::Value`]), the hash should be the same + /// as its [`Hash`] implementation. + /// If it does not implement `Hash`, then it should return `None`. + /// + /// For all other types, the hash should be consistent with its respective dynamic type, + /// such that a concrete type and its dynamic type return the same hash value given + /// the same contents. + /// + /// For example, a [`Struct`] should return the same hash as [`DynamicStruct`]. + /// This can easily be achieved by using the pre-built hash function, [`struct_hash`]. + /// + /// [`Hash`]: std::hash::Hash + /// [`DynamicStruct`]: crate::DynamicStruct + /// [`struct_hash`]: crate::struct_hash fn reflect_hash(&self) -> Option { None } - /// Returns a "partial equality" comparison result. + /// Compares this value to another reflected value, similar to [`PartialEq`]. /// - /// If the underlying type does not support equality testing, returns `None`. - fn reflect_partial_eq(&self, _value: &dyn Reflect) -> Option { + /// If the underlying type or any of its members do not support comparisons, returns `None`. + /// + /// # For Implementors + /// + /// For value types (i.e. [`ReflectRef::Value`]), the result should be the same + /// as its `PartialEq` implementation. + /// If it does not implement `PartialEq`, then it should return `None`. + /// + /// For all other types, the result should be consistent with its respective dynamic type, + /// such that a concrete type and its dynamic type return the same result given + /// the same contents. + /// + /// For example, a [`Struct`] should return the same result as [`DynamicStruct`]. + /// This can easily be achieved by using the pre-built comparison function, [`struct_partial_eq`]. + /// + /// [`PartialEq`]: std::cmp::PartialEq + /// [`DynamicStruct`]: crate::DynamicStruct + /// [`struct_partial_eq`]: crate::struct_partial_eq + #[allow(unused_variables)] + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { None } diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index 58231b2654ac2..7ad50aa00ce4b 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -19,7 +19,6 @@ mod tests { #[test] fn test_serialization_struct() { #[derive(Debug, Reflect, PartialEq)] - #[reflect(PartialEq)] struct TestStruct { a: i32, #[reflect(ignore)] @@ -61,7 +60,6 @@ mod tests { #[test] fn test_serialization_tuple_struct() { #[derive(Debug, Reflect, PartialEq)] - #[reflect(PartialEq)] struct TestStruct( i32, #[reflect(ignore)] i32, diff --git a/crates/bevy_reflect/src/struct_trait.rs b/crates/bevy_reflect/src/struct_trait.rs index 36e3aaa56a830..dcacb53bc4397 100644 --- a/crates/bevy_reflect/src/struct_trait.rs +++ b/crates/bevy_reflect/src/struct_trait.rs @@ -1,9 +1,12 @@ +use crate::equality_utility::{compare_structs, extract_info, get_type_info_pair}; +use crate::utility::reflect_hasher; use crate::{ self as bevy_reflect, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, }; use bevy_reflect_derive::impl_type_path; use bevy_utils::{Entry, HashMap}; use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::{ any::{Any, TypeId}, borrow::Cow, @@ -81,8 +84,7 @@ pub struct StructInfo { fields: Box<[NamedField]>, field_names: Box<[&'static str]>, field_indices: HashMap<&'static str, usize>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: StructMeta, } impl StructInfo { @@ -109,15 +111,13 @@ impl StructInfo { fields: fields.to_vec().into_boxed_slice(), field_names, field_indices, - #[cfg(feature = "documentation")] - docs: None, + meta: StructMeta::new(), } } - /// Sets the docstring for this struct. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this struct. + pub fn with_meta(self, meta: StructMeta) -> Self { + Self { meta, ..self } } /// A slice containing the names of all fields in order. @@ -173,15 +173,39 @@ impl StructInfo { self.type_id } + /// The metadata of the struct. + pub fn meta(&self) -> &StructMeta { + &self.meta + } + /// Check if the given type matches the struct type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} +/// Metadata for [structs], accessed via [`StructInfo::meta`]. +/// +/// [structs]: Struct +#[derive(Clone, Debug)] +pub struct StructMeta { /// The docstring of this struct, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl StructMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for StructMeta { + fn default() -> Self { + Self::new() } } @@ -472,6 +496,10 @@ impl Reflect for DynamicStruct { Ok(()) } + fn reflect_hash(&self) -> Option { + struct_hash(self) + } + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { struct_partial_eq(self, value) } @@ -496,38 +524,68 @@ impl Debug for DynamicStruct { } } +/// Returns the `u64` hash of the given [struct](Struct). +#[inline] +pub fn struct_hash(value: &S) -> Option { + let mut hasher = reflect_hasher(); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::Struct(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in info.iter() { + if field.meta().skip_hash { + continue; + } + + if let Some(value) = value.field(field.name()) { + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in value.iter_fields() { + Hash::hash(&field.reflect_hash()?, &mut hasher); + } + } + } + + Some(hasher.finish()) +} + /// Compares a [`Struct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: /// - `b` is a struct; /// - For each field in `a`, `b` contains a field with the same name and /// [`Reflect::reflect_partial_eq`] returns `Some(true)` for the two field -/// values. +/// values[^1] /// -/// Returns [`None`] if the comparison couldn't even be performed. -#[inline] +/// Returns `None` if the comparison cannot be performed. +/// +/// [^1]: If a field is marked with `#[reflect(skip_partial_eq)]`, then it will be skipped +/// unless the corresponding field in `b` is not marked with `#[reflect(skip_partial_eq)]`, +/// in which case the comparison will return `Some(false)`. pub fn struct_partial_eq(a: &S, b: &dyn Reflect) -> Option { - let ReflectRef::Struct(struct_value) = b.reflect_ref() else { + let ReflectRef::Struct(b) = b.reflect_ref() else { return Some(false); }; - if a.field_len() != struct_value.field_len() { - return Some(false); - } - - for (i, value) in struct_value.iter_fields().enumerate() { - let name = struct_value.name_at(i).unwrap(); - if let Some(field_value) = a.field(name) { - let eq_result = field_value.reflect_partial_eq(value); - if let failed @ (Some(false) | None) = eq_result { - return failed; - } - } else { - return Some(false); - } - } + let (info_a, info_b) = get_type_info_pair(a.as_reflect(), b.as_reflect()); + let info_a = extract_info!(info_a, TypeInfo::Struct(info) => Some(info)); + let info_b = extract_info!(info_b, TypeInfo::Struct(info) => Some(info)); - Some(true) + compare_structs!(a, b, info_a, info_b) } /// The default debug formatter for [`Struct`] types. diff --git a/crates/bevy_reflect/src/tuple.rs b/crates/bevy_reflect/src/tuple.rs index 0480d3b3094a3..e7fb1c6b330cf 100644 --- a/crates/bevy_reflect/src/tuple.rs +++ b/crates/bevy_reflect/src/tuple.rs @@ -1,13 +1,14 @@ -use bevy_reflect_derive::impl_type_path; - +use crate::utility::reflect_hasher; use crate::{ self as bevy_reflect, utility::GenericTypePathCell, FromReflect, GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, Typed, UnnamedField, }; +use bevy_reflect_derive::impl_type_path; use std::any::{Any, TypeId}; use std::borrow::Cow; use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::slice::Iter; /// A trait used to power [tuple-like] operations via [reflection]. @@ -141,8 +142,7 @@ pub struct TupleInfo { type_name: &'static str, type_id: TypeId, fields: Box<[UnnamedField]>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: TupleMeta, } impl TupleInfo { @@ -157,15 +157,13 @@ impl TupleInfo { type_name: std::any::type_name::(), type_id: TypeId::of::(), fields: fields.to_vec().into_boxed_slice(), - #[cfg(feature = "documentation")] - docs: None, + meta: TupleMeta::new(), } } - /// Sets the docstring for this tuple. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this tuple. + pub fn with_meta(self, meta: TupleMeta) -> Self { + Self { meta, ..self } } /// Get the field at the given index. @@ -195,15 +193,39 @@ impl TupleInfo { self.type_id } + /// The metadata of the struct. + pub fn meta(&self) -> &TupleMeta { + &self.meta + } + /// Check if the given type matches the tuple type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} +/// Metadata for [tuples], accessed via [`TupleInfo::meta`]. +/// +/// [tuples]: Tuple +#[derive(Clone, Debug)] +pub struct TupleMeta { /// The docstring of this tuple, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl TupleMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for TupleMeta { + fn default() -> Self { + Self::new() } } @@ -380,6 +402,10 @@ impl Reflect for DynamicTuple { Ok(()) } + fn reflect_hash(&self) -> Option { + tuple_hash(self) + } + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { tuple_partial_eq(self, value) } @@ -398,6 +424,41 @@ impl Reflect for DynamicTuple { impl_type_path!((in bevy_reflect) DynamicTuple); +/// Returns the `u64` hash of the given [tuple](Tuple). +#[inline] +pub fn tuple_hash(value: &T) -> Option { + let mut hasher = reflect_hasher(); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::Tuple(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in info.iter() { + if let Some(value) = value.field(field.index()) { + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in value.iter_fields() { + Hash::hash(&field.reflect_hash()?, &mut hasher); + } + } + } + + Some(hasher.finish()) +} + /// Applies the elements of `b` to the corresponding elements of `a`. /// /// # Panics @@ -426,7 +487,7 @@ pub fn tuple_apply(a: &mut T, b: &dyn Reflect) { /// Returns [`None`] if the comparison couldn't even be performed. #[inline] pub fn tuple_partial_eq(a: &T, b: &dyn Reflect) -> Option { - let ReflectRef::Tuple(b) = b.reflect_ref() else { + let ReflectRef::Tuple(b) = b.reflect_ref() else { return Some(false); }; @@ -434,10 +495,9 @@ pub fn tuple_partial_eq(a: &T, b: &dyn Reflect) -> Option { return Some(false); } - for (a_field, b_field) in a.iter_fields().zip(b.iter_fields()) { - let eq_result = a_field.reflect_partial_eq(b_field); - if let failed @ (Some(false) | None) = eq_result { - return failed; + for (value_a, value_b) in a.iter_fields().zip(b.iter_fields()) { + if !value_a.reflect_partial_eq(value_b)? { + return Some(false); } } @@ -491,8 +551,8 @@ macro_rules! impl_reflect_tuple { #[inline] fn field_len(&self) -> usize { - let indices: &[usize] = &[$($index as usize),*]; - indices.len() + const INDICES: &[usize] = &[$($index as usize),*]; + INDICES.len() } #[inline] @@ -582,8 +642,47 @@ macro_rules! impl_reflect_tuple { Box::new(self.clone_dynamic()) } - fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { - crate::tuple_partial_eq(self, value) + fn reflect_hash(&self) -> Option { + let mut hasher = crate::utility::reflect_hasher(); + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&self.field_len(), &mut hasher); + + $( + Hash::hash(&self.$index.reflect_hash()?, &mut hasher); + )* + + Some(hasher.finish()) + } + + fn reflect_partial_eq(&self, other: &dyn Reflect) -> Option { + #[allow(unused_variables)] + if let Some(other) = other.downcast_ref::() { + $( + if !self.$index.reflect_partial_eq(&other.$index)? { + return Some(false); + } + )* + } else { + let ReflectRef::Tuple(other) = Reflect::reflect_ref(other) else { + return Some(false); + }; + + if other.field_len() != self.field_len() { + return Some(false); + } + + $( + let Some(other_field) = other.field($index) else { + return Some(false); + }; + + if !self.$index.reflect_partial_eq(other_field)? { + return Some(false); + } + )* + } + + Some(true) } } diff --git a/crates/bevy_reflect/src/tuple_struct.rs b/crates/bevy_reflect/src/tuple_struct.rs index ae455be39108c..1ca87c3a15ebd 100644 --- a/crates/bevy_reflect/src/tuple_struct.rs +++ b/crates/bevy_reflect/src/tuple_struct.rs @@ -1,10 +1,12 @@ -use bevy_reflect_derive::impl_type_path; - +use crate::equality_utility::{compare_tuple_structs, extract_info, get_type_info_pair}; +use crate::utility::reflect_hasher; use crate::{ self as bevy_reflect, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, UnnamedField, }; +use bevy_reflect_derive::impl_type_path; use std::any::{Any, TypeId}; use std::fmt::{Debug, Formatter}; +use std::hash::{Hash, Hasher}; use std::slice::Iter; /// A trait used to power [tuple struct-like] operations via [reflection]. @@ -59,8 +61,7 @@ pub struct TupleStructInfo { type_name: &'static str, type_id: TypeId, fields: Box<[UnnamedField]>, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: TupleStructMeta, } impl TupleStructInfo { @@ -77,15 +78,13 @@ impl TupleStructInfo { type_name: std::any::type_name::(), type_id: TypeId::of::(), fields: fields.to_vec().into_boxed_slice(), - #[cfg(feature = "documentation")] - docs: None, + meta: TupleStructMeta::new(), } } - /// Sets the docstring for this struct. - #[cfg(feature = "documentation")] - pub fn with_docs(self, docs: Option<&'static str>) -> Self { - Self { docs, ..self } + /// Add metadata for this struct. + pub fn with_meta(self, meta: TupleStructMeta) -> Self { + Self { meta, ..self } } /// Get the field at the given index. @@ -124,15 +123,39 @@ impl TupleStructInfo { self.type_id } + /// The metadata of the struct. + pub fn meta(&self) -> &TupleStructMeta { + &self.meta + } + /// Check if the given type matches the tuple struct type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} - /// The docstring of this struct, if any. +/// Metadata for [tuple structs], accessed via [`TupleStructInfo::meta`]. +/// +/// [tuple structs]: TupleStruct +#[derive(Clone, Debug)] +pub struct TupleStructMeta { + /// The docstring of this tuple struct, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl TupleStructMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for TupleStructMeta { + fn default() -> Self { + Self::new() } } @@ -374,6 +397,10 @@ impl Reflect for DynamicTupleStruct { Ok(()) } + fn reflect_hash(&self) -> Option { + tuple_struct_hash(self) + } + fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option { tuple_struct_partial_eq(self, value) } @@ -398,36 +425,68 @@ impl Debug for DynamicTupleStruct { } } +/// Returns the `u64` hash of the given [tuple struct](TupleStruct). +#[inline] +pub fn tuple_struct_hash(value: &T) -> Option { + let mut hasher = reflect_hasher(); + + match value.get_represented_type_info() { + // Proxy case + Some(info) => { + let TypeInfo::TupleStruct(info) = info else { + return None; + }; + + Hash::hash(&info.type_id(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in info.iter() { + if field.meta().skip_hash { + continue; + } + + if let Some(value) = value.field(field.index()) { + Hash::hash(&value.reflect_hash()?, &mut hasher); + } + } + } + // Dynamic case + None => { + Hash::hash(&TypeId::of::(), &mut hasher); + Hash::hash(&value.field_len(), &mut hasher); + + for field in value.iter_fields() { + Hash::hash(&field.reflect_hash()?, &mut hasher); + } + } + } + + Some(hasher.finish()) +} + /// Compares a [`TupleStruct`] with a [`Reflect`] value. /// /// Returns true if and only if all of the following are true: /// - `b` is a tuple struct; /// - `b` has the same number of fields as `a`; -/// - [`Reflect::reflect_partial_eq`] returns `Some(true)` for pairwise fields of `a` and `b`. +/// - [`Reflect::reflect_partial_eq`] returns `Some(true)` for pairwise fields of `a` and `b`[^1] /// -/// Returns [`None`] if the comparison couldn't even be performed. +/// Returns `None` if the comparison cannot be performed. +/// +/// [^1]: If a field is marked with `#[reflect(skip_partial_eq)]`, then it will be skipped +/// unless the corresponding field in `b` is not marked with `#[reflect(skip_partial_eq)]`, +/// in which case the comparison will return `Some(false)`. #[inline] pub fn tuple_struct_partial_eq(a: &S, b: &dyn Reflect) -> Option { - let ReflectRef::TupleStruct(tuple_struct) = b.reflect_ref() else { + let ReflectRef::TupleStruct(b) = b.reflect_ref() else { return Some(false); }; - if a.field_len() != tuple_struct.field_len() { - return Some(false); - } - - for (i, value) in tuple_struct.iter_fields().enumerate() { - if let Some(field_value) = a.field(i) { - let eq_result = field_value.reflect_partial_eq(value); - if let failed @ (Some(false) | None) = eq_result { - return failed; - } - } else { - return Some(false); - } - } + let (info_a, info_b) = get_type_info_pair(a.as_reflect(), b.as_reflect()); + let info_a = extract_info!(info_a, TypeInfo::TupleStruct(info) => Some(info)); + let info_b = extract_info!(info_b, TypeInfo::TupleStruct(info) => Some(info)); - Some(true) + compare_tuple_structs!(a, b, info_a, info_b, accessor=.field) } /// The default debug formatter for [`TupleStruct`] types. diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index 6db1f26b49992..76c710569a639 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -153,14 +153,14 @@ impl TypeInfo { #[cfg(feature = "documentation")] pub fn docs(&self) -> Option<&str> { match self { - Self::Struct(info) => info.docs(), - Self::TupleStruct(info) => info.docs(), - Self::Tuple(info) => info.docs(), - Self::List(info) => info.docs(), - Self::Array(info) => info.docs(), - Self::Map(info) => info.docs(), - Self::Enum(info) => info.docs(), - Self::Value(info) => info.docs(), + Self::Struct(info) => info.meta().docs, + Self::TupleStruct(info) => info.meta().docs, + Self::Tuple(info) => info.meta().docs, + Self::List(info) => info.meta().docs, + Self::Array(info) => info.meta().docs, + Self::Map(info) => info.meta().docs, + Self::Enum(info) => info.meta().docs, + Self::Value(info) => info.meta().docs, } } } @@ -177,8 +177,7 @@ impl TypeInfo { pub struct ValueInfo { type_name: &'static str, type_id: TypeId, - #[cfg(feature = "documentation")] - docs: Option<&'static str>, + meta: ValueMeta, } impl ValueInfo { @@ -186,15 +185,13 @@ impl ValueInfo { Self { type_name: std::any::type_name::(), type_id: TypeId::of::(), - #[cfg(feature = "documentation")] - docs: None, + meta: ValueMeta::new(), } } - /// Sets the docstring for this value. - #[cfg(feature = "documentation")] - pub fn with_docs(self, doc: Option<&'static str>) -> Self { - Self { docs: doc, ..self } + /// Add metadata for this value. + pub fn with_meta(self, meta: ValueMeta) -> Self { + Self { meta, ..self } } /// The [type name] of the value. @@ -209,14 +206,38 @@ impl ValueInfo { self.type_id } + /// The metadata of the value. + pub fn meta(&self) -> &ValueMeta { + &self.meta + } + /// Check if the given type matches the value type. pub fn is(&self) -> bool { TypeId::of::() == self.type_id } +} - /// The docstring of this dynamic value, if any. +/// Metadata for [value types], accessed via [`ValueInfo::meta`]. +/// +/// [value types]: ValueInfo +#[derive(Clone, Debug)] +pub struct ValueMeta { + /// The docstring of this value, if any. #[cfg(feature = "documentation")] - pub fn docs(&self) -> Option<&'static str> { - self.docs + pub docs: Option<&'static str>, +} + +impl ValueMeta { + pub const fn new() -> Self { + Self { + #[cfg(feature = "documentation")] + docs: None, + } + } +} + +impl Default for ValueMeta { + fn default() -> Self { + Self::new() } } diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.rs new file mode 100644 index 0000000000000..5469f5e0f88ff --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.rs @@ -0,0 +1,17 @@ +use bevy_reflect::prelude::*; + +// Reason: `#[reflect(Hash)]` only supported for value types +#[derive(Reflect, Hash)] +#[reflect(Hash)] +struct Foo { + a: u32, +} + +#[derive(Reflect)] +struct Bar { + // Reason: `#[reflect(skip_hash)]` does not take any arguments + #[reflect(skip_hash = true)] + a: u32, +} + +fn main() {} diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.stderr b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.stderr new file mode 100644 index 0000000000000..e89b7396ffe20 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.fail.stderr @@ -0,0 +1,11 @@ +error: concrete `Hash` impl can only be used for items marked `#[reflect_value]` + --> tests/reflect_derive/hash.fail.rs:5:11 + | +5 | #[reflect(Hash)] + | ^^^^ + +error: unknown attribute parameter: skip_hash + --> tests/reflect_derive/hash.fail.rs:13:15 + | +13 | #[reflect(skip_hash = true)] + | ^^^^^^^^^ diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.pass.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.pass.rs new file mode 100644 index 0000000000000..eedb6114f456e --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/hash.pass.rs @@ -0,0 +1,15 @@ +use bevy_reflect::prelude::*; + +#[derive(Reflect, Hash, Clone)] +#[reflect_value(Hash)] +struct Foo { + a: u32, +} + +#[derive(Reflect)] +struct Bar { + #[reflect(skip_hash)] + a: u32, +} + +fn main() {} diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.rs new file mode 100644 index 0000000000000..e2e7c10119b64 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.rs @@ -0,0 +1,17 @@ +use bevy_reflect::prelude::*; + +// Reason: `#[reflect(PartialEq)]` only supported for value types +#[derive(Reflect, PartialEq)] +#[reflect(PartialEq)] +struct Foo { + a: u32, +} + +#[derive(Reflect)] +struct Bar { + // Reason: `#[reflect(skip_partial_eq)]` does not take any arguments + #[reflect(skip_partial_eq = true)] + a: u32, +} + +fn main() {} diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.stderr b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.stderr new file mode 100644 index 0000000000000..5f143cc36c429 --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.fail.stderr @@ -0,0 +1,11 @@ +error: concrete `PartialEq` impl can only be used for items marked `#[reflect_value]` + --> tests/reflect_derive/partial_eq.fail.rs:5:11 + | +5 | #[reflect(PartialEq)] + | ^^^^^^^^^ + +error: unknown attribute parameter: skip_partial_eq + --> tests/reflect_derive/partial_eq.fail.rs:13:15 + | +13 | #[reflect(skip_partial_eq = true)] + | ^^^^^^^^^^^^^^^ diff --git a/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.pass.rs b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.pass.rs new file mode 100644 index 0000000000000..1c9e59f75781e --- /dev/null +++ b/crates/bevy_reflect_compile_fail_tests/tests/reflect_derive/partial_eq.pass.rs @@ -0,0 +1,15 @@ +use bevy_reflect::prelude::*; + +#[derive(Reflect, PartialEq, Clone)] +#[reflect_value(PartialEq)] +struct Foo { + a: u32, +} + +#[derive(Reflect)] +struct Bar { + #[reflect(skip_partial_eq)] + a: u32, +} + +fn main() {} diff --git a/crates/bevy_render/src/color/mod.rs b/crates/bevy_render/src/color/mod.rs index 795ba48e3e0fc..1b4e44603a5cb 100644 --- a/crates/bevy_render/src/color/mod.rs +++ b/crates/bevy_render/src/color/mod.rs @@ -9,7 +9,7 @@ use std::ops::{Add, AddAssign, Mul, MulAssign}; use thiserror::Error; #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum Color { /// sRGBA color Rgba { diff --git a/crates/bevy_render/src/view/visibility/render_layers.rs b/crates/bevy_render/src/view/visibility/render_layers.rs index 677c0877777ba..3ac21da9c5ad9 100644 --- a/crates/bevy_render/src/view/visibility/render_layers.rs +++ b/crates/bevy_render/src/view/visibility/render_layers.rs @@ -21,7 +21,7 @@ pub type Layer = u8; /// /// Entities without this component belong to layer `0`. #[derive(Component, Copy, Clone, Reflect, PartialEq, Eq, PartialOrd, Ord)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default)] pub struct RenderLayers(LayerMask); impl std::fmt::Debug for RenderLayers { diff --git a/crates/bevy_scene/src/serde.rs b/crates/bevy_scene/src/serde.rs index a16a8d8957543..9861c202b4d77 100644 --- a/crates/bevy_scene/src/serde.rs +++ b/crates/bevy_scene/src/serde.rs @@ -470,7 +470,7 @@ mod tests { } #[derive(Clone, Component, Reflect, PartialEq)] - #[reflect(Component, MapEntities, PartialEq)] + #[reflect(Component, MapEntities)] struct MyEntityRef(Entity); impl MapEntities for MyEntityRef { diff --git a/crates/bevy_transform/src/components/global_transform.rs b/crates/bevy_transform/src/components/global_transform.rs index 4e1480c44b0b5..fd6a0185e35c9 100644 --- a/crates/bevy_transform/src/components/global_transform.rs +++ b/crates/bevy_transform/src/components/global_transform.rs @@ -35,7 +35,7 @@ use bevy_reflect::{std_traits::ReflectDefault, Reflect}; /// [`transform`]: https://github.com/bevyengine/bevy/blob/latest/examples/transforms/transform.rs #[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default)] pub struct GlobalTransform(Affine3A); macro_rules! impl_local_axis { diff --git a/crates/bevy_transform/src/components/transform.rs b/crates/bevy_transform/src/components/transform.rs index e02ebebbbd98d..1b8df27ce9791 100644 --- a/crates/bevy_transform/src/components/transform.rs +++ b/crates/bevy_transform/src/components/transform.rs @@ -37,7 +37,7 @@ use std::ops::Mul; /// [`Transform`]: super::Transform #[derive(Component, Debug, PartialEq, Clone, Copy, Reflect)] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default)] pub struct Transform { /// Position of the entity. In 2d, the last value of the `Vec3` is used for z-ordering. /// diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index e3ce2ce927c7d..5ea5c4849a47e 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -33,7 +33,7 @@ use smallvec::SmallVec; /// Note that you can also control the visibility of a node using the [`Display`](crate::ui_node::Display) property, /// which fully collapses it during layout calculations. #[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[reflect(Component, Serialize, Deserialize)] pub enum Interaction { /// The node has been clicked Clicked, @@ -71,7 +71,7 @@ impl Default for Interaction { Serialize, Deserialize, )] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[reflect(Component, Serialize, Deserialize)] pub struct RelativeCursorPosition { /// Cursor position relative to size and position of the Node. pub normalized: Option, @@ -88,7 +88,7 @@ impl RelativeCursorPosition { /// Describes whether the node should block interactions with lower nodes #[derive(Component, Copy, Clone, Eq, PartialEq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(Component, Serialize, Deserialize, PartialEq)] +#[reflect(Component, Serialize, Deserialize)] pub enum FocusPolicy { /// Blocks interaction Block, diff --git a/crates/bevy_ui/src/geometry.rs b/crates/bevy_ui/src/geometry.rs index aca0c10b016a1..caf23c8e56d8b 100644 --- a/crates/bevy_ui/src/geometry.rs +++ b/crates/bevy_ui/src/geometry.rs @@ -46,7 +46,6 @@ use bevy_reflect::Reflect; /// }; /// ``` #[derive(Copy, Clone, PartialEq, Debug, Reflect)] -#[reflect(PartialEq)] pub struct UiRect { /// The value corresponding to the left side of the UI rect. pub left: Val, diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index 532007ed3b752..b9b4160254042 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -78,7 +78,7 @@ impl Default for Node { /// This enum allows specifying values for various [`Style`] properties in different units, /// such as logical pixels, percentages, or automatically determined values. #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum Val { /// Automatically determine the value based on the context and other [`Style`] properties. Auto, @@ -294,7 +294,7 @@ impl Val { /// - [CSS Grid Garden](https://cssgridgarden.com/). An interactive tutorial/game that teaches the essential parts of CSS Grid in a fun engaging way. #[derive(Component, Clone, PartialEq, Debug, Reflect)] -#[reflect(Component, Default, PartialEq)] +#[reflect(Component, Default)] pub struct Style { /// Which layout algorithm to use when laying out this node's contents: /// - [`Display::Flex`]: Use the Flexbox layout algorithm @@ -628,7 +628,7 @@ impl Default for Style { /// How items are aligned according to the cross axis #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum AlignItems { /// The items are packed in their default position as if no alignment was applied Default, @@ -662,7 +662,7 @@ impl Default for AlignItems { /// How items are aligned according to the cross axis #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum JustifyItems { /// The items are packed in their default position as if no alignment was applied Default, @@ -691,7 +691,7 @@ impl Default for JustifyItems { /// How this item is aligned according to the cross axis. /// Overrides [`AlignItems`]. #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum AlignSelf { /// Use the parent node's [`AlignItems`] value to determine how this item should be aligned. Auto, @@ -726,7 +726,7 @@ impl Default for AlignSelf { /// How this item is aligned according to the cross axis. /// Overrides [`AlignItems`]. #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum JustifySelf { /// Use the parent node's [`AlignItems`] value to determine how this item should be aligned. Auto, @@ -756,7 +756,7 @@ impl Default for JustifySelf { /// /// It only applies if [`FlexWrap::Wrap`] is present and if there are multiple lines of items. #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum AlignContent { /// The items are packed in their default position as if no alignment was applied Default, @@ -795,7 +795,7 @@ impl Default for AlignContent { /// Defines how items are aligned according to the main axis #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum JustifyContent { /// The items are packed in their default position as if no alignment was applied Default, @@ -831,7 +831,7 @@ impl Default for JustifyContent { /// /// For example English is written LTR (left-to-right) while Arabic is written RTL (right-to-left). #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum Direction { /// Inherit from parent node. Inherit, @@ -855,7 +855,7 @@ impl Default for Direction { /// /// Part of the [`Style`] component. #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum Display { /// Use Flexbox layout model to determine the position of this [`Node`]. Flex, @@ -880,7 +880,7 @@ impl Default for Display { /// Defines how flexbox items are ordered within a flexbox #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum FlexDirection { /// Same way as text direction along the main axis. Row, @@ -904,7 +904,7 @@ impl Default for FlexDirection { /// Whether to show or hide overflowing items #[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub struct Overflow { /// Whether to show or clip overflowing items on the x axis pub x: OverflowAxis, @@ -964,7 +964,7 @@ impl Default for Overflow { /// Whether to show or hide overflowing items #[derive(Copy, Clone, PartialEq, Eq, Debug, Reflect, Serialize, Deserialize)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum OverflowAxis { /// Show overflowing items. Visible, @@ -989,7 +989,7 @@ impl Default for OverflowAxis { /// The strategy used to position this node #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum PositionType { /// Relative to all other nodes with the [`PositionType::Relative`] value. Relative, @@ -1011,7 +1011,7 @@ impl Default for PositionType { /// Defines if flexbox items appear on a single line or on multiple lines #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum FlexWrap { /// Single line, will overflow if needed. NoWrap, @@ -1039,7 +1039,7 @@ impl Default for FlexWrap { /// /// #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub enum GridAutoFlow { /// Items are placed by filling each row in turn, adding new rows as necessary Row, @@ -1102,7 +1102,7 @@ pub enum MaxTrackSizingFunction { /// A [`GridTrack`] is a Row or Column of a CSS Grid. This struct specifies what size the track should be. /// See below for the different "track sizing functions" you can specify. #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub struct GridTrack { pub(crate) min_sizing_function: MinTrackSizingFunction, pub(crate) max_sizing_function: MaxTrackSizingFunction, @@ -1220,7 +1220,7 @@ impl Default for GridTrack { } #[derive(Copy, Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] /// How many times to repeat a repeated grid track /// /// @@ -1270,7 +1270,7 @@ impl From for GridTrackRepetition { /// then all track (in and outside of the repetition) must be fixed size (px or percent). Integer repetitions are just shorthand for writing out /// N tracks longhand and are not subject to the same limitations. #[derive(Clone, PartialEq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] pub struct RepeatedGridTrack { pub(crate) repetition: GridTrackRepetition, pub(crate) tracks: SmallVec<[GridTrack; 1]>, @@ -1420,7 +1420,7 @@ impl From for Vec { } #[derive(Copy, Clone, PartialEq, Eq, Debug, Serialize, Deserialize, Reflect)] -#[reflect(PartialEq, Serialize, Deserialize)] +#[reflect(Serialize, Deserialize)] /// Represents the position of a grid item in a single axis. /// /// There are 3 fields which may be set: diff --git a/crates/bevy_window/src/cursor.rs b/crates/bevy_window/src/cursor.rs index 7867c27a30cd4..16cd359450d47 100644 --- a/crates/bevy_window/src/cursor.rs +++ b/crates/bevy_window/src/cursor.rs @@ -14,7 +14,7 @@ use bevy_reflect::{ReflectDeserialize, ReflectSerialize}; derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Default)] +#[reflect(Debug, Default)] pub enum CursorIcon { /// The platform-dependent default cursor. #[default] diff --git a/crates/bevy_window/src/event.rs b/crates/bevy_window/src/event.rs index bc0dc9d872d7d..859011730349c 100644 --- a/crates/bevy_window/src/event.rs +++ b/crates/bevy_window/src/event.rs @@ -12,7 +12,7 @@ use crate::WindowTheme; /// A window event that is sent whenever a window's logical size has changed. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -30,7 +30,7 @@ pub struct WindowResized { /// An event that indicates all of the application's windows should be redrawn, /// even if their control flow is set to `Wait` and there have been no window events. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -42,7 +42,7 @@ pub struct RequestRedraw; /// /// To create a new window, spawn an entity with a [`crate::Window`] on it. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -64,7 +64,7 @@ pub struct WindowCreated { /// [`WindowPlugin`]: crate::WindowPlugin /// [`Window`]: crate::Window #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -78,7 +78,7 @@ pub struct WindowCloseRequested { /// An event that is sent whenever a window is closed. This will be sent when /// the window entity loses its [`Window`](crate::window::Window) component or is despawned. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -101,7 +101,7 @@ pub struct WindowClosed { /// [`WindowEvent::CursorMoved`]: https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.CursorMoved /// [`MouseMotion`]: bevy_input::mouse::MouseMotion #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -116,7 +116,7 @@ pub struct CursorMoved { /// An event that is sent whenever the user's cursor enters a window. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -129,7 +129,7 @@ pub struct CursorEntered { /// An event that is sent whenever the user's cursor leaves a window. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -142,7 +142,7 @@ pub struct CursorLeft { /// An event that is sent whenever a window receives a character from the OS or underlying system. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -161,7 +161,7 @@ pub struct ReceivedCharacter { /// /// It is only sent if IME was enabled on the window with [`Window::ime_enabled`](crate::window::Window::ime_enabled). #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -203,7 +203,7 @@ pub enum Ime { /// An event that indicates a window has received or lost focus. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -218,7 +218,7 @@ pub struct WindowFocused { /// An event that indicates a window's scale factor has changed. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -233,7 +233,7 @@ pub struct WindowScaleFactorChanged { /// An event that indicates a window's OS-reported scale factor has changed. #[derive(Event, Debug, Clone, PartialEq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -248,7 +248,7 @@ pub struct WindowBackendScaleFactorChanged { /// Events related to files being dragged and dropped on a window. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -280,7 +280,7 @@ pub enum FileDragAndDrop { /// An event that is sent when a window is repositioned in physical pixels. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), @@ -298,7 +298,7 @@ pub struct WindowMoved { /// This event is only sent when the window is relying on the system theme to control its appearance. /// i.e. It is only sent when [`Window::window_theme`](crate::window::Window::window_theme) is `None` and the system theme changes. #[derive(Event, Debug, Clone, PartialEq, Eq, Reflect)] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] #[cfg_attr( feature = "serialize", derive(serde::Serialize, serde::Deserialize), diff --git a/crates/bevy_window/src/window.rs b/crates/bevy_window/src/window.rs index b5135751845f1..cec142e9622d8 100644 --- a/crates/bevy_window/src/window.rs +++ b/crates/bevy_window/src/window.rs @@ -342,7 +342,7 @@ impl Window { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Default)] +#[reflect(Debug, Default)] pub struct WindowResizeConstraints { /// The minimum width the window can have. pub min_width: f32, @@ -461,7 +461,7 @@ impl Default for Cursor { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] pub enum WindowPosition { /// Position will be set by the window manager. /// Bevy will delegate this decision to the window manager and no guarantees can be made about where the window will be placed. @@ -550,7 +550,7 @@ impl WindowPosition { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Default)] +#[reflect(Debug, Default)] pub struct WindowResolution { /// Width of the window in physical pixels. physical_width: u32, @@ -725,7 +725,7 @@ impl From for WindowResolution { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Default)] +#[reflect(Debug, Default)] pub enum CursorGrabMode { /// The cursor can freely leave the window. #[default] @@ -743,7 +743,7 @@ pub enum CursorGrabMode { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Default)] +#[reflect(Debug, Default)] pub struct InternalWindowState { /// If this is true then next frame we will ask to minimize the window. minimize_request: Option, @@ -774,7 +774,7 @@ impl InternalWindowState { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] pub enum MonitorSelection { /// Uses the current monitor of the window. /// @@ -813,7 +813,7 @@ pub enum MonitorSelection { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Hash)] +#[reflect(Debug)] #[doc(alias = "vsync")] pub enum PresentMode { /// Chooses FifoRelaxed -> Fifo based on availability. @@ -853,7 +853,7 @@ pub enum PresentMode { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq, Hash)] +#[reflect(Debug)] pub enum CompositeAlphaMode { /// Chooses either [`Opaque`](CompositeAlphaMode::Opaque) or [`Inherit`](CompositeAlphaMode::Inherit) /// automatically, depending on the `alpha_mode` that the current surface can support. @@ -888,7 +888,7 @@ pub enum CompositeAlphaMode { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] pub enum WindowMode { /// The window should take a portion of the screen, using the window resolution size. #[default] @@ -935,7 +935,7 @@ pub enum WindowMode { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] pub enum WindowLevel { /// The window will always be below [`WindowLevel::Normal`] and [`WindowLevel::AlwaysOnTop`] windows. /// @@ -955,7 +955,7 @@ pub enum WindowLevel { derive(serde::Serialize, serde::Deserialize), reflect(Serialize, Deserialize) )] -#[reflect(Debug, PartialEq)] +#[reflect(Debug)] pub enum WindowTheme { /// Use the light variant. Light, diff --git a/examples/reflection/reflection_types.rs b/examples/reflection/reflection_types.rs index 584f45822a6c1..6f086c853890d 100644 --- a/examples/reflection/reflection_types.rs +++ b/examples/reflection/reflection_types.rs @@ -40,16 +40,6 @@ pub enum D { C { value: f32 }, } -/// Reflect has "built in" support for some common traits like `PartialEq`, `Hash`, and `Serialize`. -/// These are exposed via methods like `Reflect::reflect_hash()`, `Reflect::reflect_partial_eq()`, and -/// `Reflect::serializable()`. You can force these implementations to use the actual trait -/// implementations (instead of their defaults) like this: -#[derive(Reflect, Hash, Serialize, PartialEq, Eq)] -#[reflect(Hash, Serialize, PartialEq)] -pub struct E { - x: usize, -} - /// By default, deriving with Reflect assumes the type is either a "struct" or an "enum". /// You can tell reflect to treat your type instead as a "value type" by using the `reflect_value` /// attribute in place of `reflect`. It is generally a good idea to implement (and reflect)