From ba3d9b3fb6a4ef9b1835146d73fab30c5385f8e4 Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Mon, 9 Sep 2024 07:03:42 -0700 Subject: [PATCH 001/115] bevy_reflect: Refactor `serde` module (#15107) # Objective The `ser` and `de` modules in `bevy_reflect/serde` are very long and difficult to navigate. ## Solution Refactor both modules into many smaller modules that each have a single primary focus (i.e. a `structs` module that only handles struct serialization/deserialization). I chose to keep the `ser` and `de` modules separate. We could have instead broken it up kind (e.g. lists, maps, etc.), but I think this is a little cleaner. Serialization and deserialization, while related, can be very different. So keeping them separated makes sense for organizational purposes. That being said, if people disagree and think we should structure this a different way, I am open to changing it. Note that this PR's changes are mainly structural. There are a few places I refactored code to reduce duplication and to make things a bit cleaner, but these are largely cosmetic and shouldn't have any impact on behavior. ### Other Details This PR also hides a lot of the internal logic from being exported. These were originally public, but it's unlikely they really saw any use outside of these modules. In fact, you don't really gain anything by using them outside of this module either. By privatizing these fields and items, we also set ourselves up for more easily changing internal logic around without involving a breaking change. I also chose not to mess around with tests since that would really blow up the diff haha. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect --all-features ``` --- ## Migration Guide The fields on `ReflectSerializer` and `TypedReflectSerializer` are now private. To instantiate, the corresponding constructor must be used: ```rust // BEFORE let serializer = ReflectSerializer { value: &my_value, registry: &type_registry, }; // AFTER let serializer = ReflectSerializer::new(&my_value, &type_registry); ``` Additionally, the following types are no longer public: - `ArraySerializer` - `EnumSerializer` - `ListSerializer` - `MapSerializer` - `ReflectValueSerializer` (fully removed) - `StructSerializer` - `TupleSerializer` - `TupleStructSerializer` As well as the following traits: - `DeserializeValue` (fully removed) --- crates/bevy_reflect/src/serde/de.rs | 1712 ----------------- crates/bevy_reflect/src/serde/de/arrays.rs | 53 + .../bevy_reflect/src/serde/de/deserializer.rs | 333 ++++ crates/bevy_reflect/src/serde/de/enums.rs | 198 ++ crates/bevy_reflect/src/serde/de/helpers.rs | 70 + crates/bevy_reflect/src/serde/de/lists.rs | 45 + crates/bevy_reflect/src/serde/de/maps.rs | 49 + crates/bevy_reflect/src/serde/de/mod.rs | 515 +++++ crates/bevy_reflect/src/serde/de/options.rs | 62 + .../src/serde/de/registration_utils.rs | 15 + .../src/serde/de/registrations.rs | 52 + crates/bevy_reflect/src/serde/de/sets.rs | 44 + .../bevy_reflect/src/serde/de/struct_utils.rs | 168 ++ crates/bevy_reflect/src/serde/de/structs.rs | 50 + .../src/serde/de/tuple_structs.rs | 49 + .../bevy_reflect/src/serde/de/tuple_utils.rs | 102 + crates/bevy_reflect/src/serde/de/tuples.rs | 43 + crates/bevy_reflect/src/serde/ser.rs | 1012 ---------- crates/bevy_reflect/src/serde/ser/arrays.rs | 29 + crates/bevy_reflect/src/serde/ser/enums.rs | 123 ++ crates/bevy_reflect/src/serde/ser/lists.rs | 29 + crates/bevy_reflect/src/serde/ser/maps.rs | 32 + crates/bevy_reflect/src/serde/ser/mod.rs | 456 +++++ .../src/serde/ser/serializable.rs | 66 + .../bevy_reflect/src/serde/ser/serializer.rs | 179 ++ crates/bevy_reflect/src/serde/ser/sets.rs | 29 + crates/bevy_reflect/src/serde/ser/structs.rs | 67 + .../src/serde/ser/tuple_structs.rs | 66 + crates/bevy_reflect/src/serde/ser/tuples.rs | 30 + 29 files changed, 2954 insertions(+), 2724 deletions(-) delete mode 100644 crates/bevy_reflect/src/serde/de.rs create mode 100644 crates/bevy_reflect/src/serde/de/arrays.rs create mode 100644 crates/bevy_reflect/src/serde/de/deserializer.rs create mode 100644 crates/bevy_reflect/src/serde/de/enums.rs create mode 100644 crates/bevy_reflect/src/serde/de/helpers.rs create mode 100644 crates/bevy_reflect/src/serde/de/lists.rs create mode 100644 crates/bevy_reflect/src/serde/de/maps.rs create mode 100644 crates/bevy_reflect/src/serde/de/mod.rs create mode 100644 crates/bevy_reflect/src/serde/de/options.rs create mode 100644 crates/bevy_reflect/src/serde/de/registration_utils.rs create mode 100644 crates/bevy_reflect/src/serde/de/registrations.rs create mode 100644 crates/bevy_reflect/src/serde/de/sets.rs create mode 100644 crates/bevy_reflect/src/serde/de/struct_utils.rs create mode 100644 crates/bevy_reflect/src/serde/de/structs.rs create mode 100644 crates/bevy_reflect/src/serde/de/tuple_structs.rs create mode 100644 crates/bevy_reflect/src/serde/de/tuple_utils.rs create mode 100644 crates/bevy_reflect/src/serde/de/tuples.rs delete mode 100644 crates/bevy_reflect/src/serde/ser.rs create mode 100644 crates/bevy_reflect/src/serde/ser/arrays.rs create mode 100644 crates/bevy_reflect/src/serde/ser/enums.rs create mode 100644 crates/bevy_reflect/src/serde/ser/lists.rs create mode 100644 crates/bevy_reflect/src/serde/ser/maps.rs create mode 100644 crates/bevy_reflect/src/serde/ser/mod.rs create mode 100644 crates/bevy_reflect/src/serde/ser/serializable.rs create mode 100644 crates/bevy_reflect/src/serde/ser/serializer.rs create mode 100644 crates/bevy_reflect/src/serde/ser/sets.rs create mode 100644 crates/bevy_reflect/src/serde/ser/structs.rs create mode 100644 crates/bevy_reflect/src/serde/ser/tuple_structs.rs create mode 100644 crates/bevy_reflect/src/serde/ser/tuples.rs diff --git a/crates/bevy_reflect/src/serde/de.rs b/crates/bevy_reflect/src/serde/de.rs deleted file mode 100644 index 5563d9192f..0000000000 --- a/crates/bevy_reflect/src/serde/de.rs +++ /dev/null @@ -1,1712 +0,0 @@ -use crate::serde::SerializationData; -use crate::{ - ArrayInfo, DynamicArray, DynamicEnum, DynamicList, DynamicMap, DynamicSet, DynamicStruct, - DynamicTuple, DynamicTupleStruct, DynamicVariant, EnumInfo, ListInfo, Map, MapInfo, NamedField, - PartialReflect, Reflect, ReflectDeserialize, Set, SetInfo, StructInfo, StructVariantInfo, - TupleInfo, TupleStructInfo, TupleVariantInfo, TypeInfo, TypeRegistration, TypeRegistry, - VariantInfo, -}; -use erased_serde::Deserializer; -use serde::de::{ - DeserializeSeed, EnumAccess, Error, IgnoredAny, MapAccess, SeqAccess, VariantAccess, Visitor, -}; -use serde::Deserialize; -use std::any::TypeId; -use std::fmt; -use std::fmt::{Debug, Display, Formatter}; -use std::slice::Iter; - -pub trait DeserializeValue { - fn deserialize( - deserializer: &mut dyn Deserializer, - type_registry: &TypeRegistry, - ) -> Result, erased_serde::Error>; -} - -trait StructLikeInfo { - fn get_field(&self, name: &str) -> Option<&NamedField>; - fn field_at(&self, index: usize) -> Option<&NamedField>; - fn get_field_len(&self) -> usize; - fn iter_fields(&self) -> Iter<'_, NamedField>; -} - -trait TupleLikeInfo { - fn get_field_len(&self) -> usize; -} - -trait Container { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E>; -} - -impl StructLikeInfo for StructInfo { - fn get_field(&self, name: &str) -> Option<&NamedField> { - self.field(name) - } - - fn field_at(&self, index: usize) -> Option<&NamedField> { - self.field_at(index) - } - - fn get_field_len(&self) -> usize { - self.field_len() - } - - fn iter_fields(&self) -> Iter<'_, NamedField> { - self.iter() - } -} - -impl Container for StructInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on struct {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl StructLikeInfo for StructVariantInfo { - fn get_field(&self, name: &str) -> Option<&NamedField> { - self.field(name) - } - - fn field_at(&self, index: usize) -> Option<&NamedField> { - self.field_at(index) - } - - fn get_field_len(&self) -> usize { - self.field_len() - } - - fn iter_fields(&self) -> Iter<'_, NamedField> { - self.iter() - } -} - -impl Container for StructVariantInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on variant {}", - index, - self.name(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleStructInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleStructInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple struct {}", - index, - self.type_path(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -impl TupleLikeInfo for TupleVariantInfo { - fn get_field_len(&self) -> usize { - self.field_len() - } -} - -impl Container for TupleVariantInfo { - fn get_field_registration<'a, E: Error>( - &self, - index: usize, - registry: &'a TypeRegistry, - ) -> Result<&'a TypeRegistration, E> { - let field = self.field_at(index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple variant {}", - index, - self.name(), - )) - })?; - get_registration(field.type_id(), field.type_path(), registry) - } -} - -/// A debug struct used for error messages that displays a list of expected values. -/// -/// # Example -/// -/// ```ignore (Can't import private struct from doctest) -/// let expected = vec!["foo", "bar", "baz"]; -/// assert_eq!("`foo`, `bar`, `baz`", format!("{}", ExpectedValues(expected))); -/// ``` -struct ExpectedValues(Vec); - -impl Debug for ExpectedValues { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let len = self.0.len(); - for (index, item) in self.0.iter().enumerate() { - write!(f, "`{item}`")?; - if index < len - 1 { - write!(f, ", ")?; - } - } - Ok(()) - } -} - -/// Represents a simple reflected identifier. -#[derive(Debug, Clone, Eq, PartialEq)] -struct Ident(String); - -impl<'de> Deserialize<'de> for Ident { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct IdentVisitor; - - impl<'de> Visitor<'de> for IdentVisitor { - type Value = Ident; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("identifier") - } - - fn visit_str(self, value: &str) -> Result - where - E: Error, - { - Ok(Ident(value.to_string())) - } - - fn visit_string(self, value: String) -> Result - where - E: Error, - { - Ok(Ident(value)) - } - } - - deserializer.deserialize_identifier(IdentVisitor) - } -} - -/// A deserializer for type registrations. -/// -/// This will return a [`&TypeRegistration`] corresponding to the given type. -/// This deserializer expects a string containing the _full_ [type path] of the -/// type to find the `TypeRegistration` of. -/// -/// [`&TypeRegistration`]: TypeRegistration -/// [type path]: crate::TypePath::type_path -pub struct TypeRegistrationDeserializer<'a> { - registry: &'a TypeRegistry, -} - -impl<'a> TypeRegistrationDeserializer<'a> { - pub fn new(registry: &'a TypeRegistry) -> Self { - Self { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { - type Value = &'a TypeRegistration; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct TypeRegistrationVisitor<'a>(&'a TypeRegistry); - - impl<'de, 'a> Visitor<'de> for TypeRegistrationVisitor<'a> { - type Value = &'a TypeRegistration; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("string containing `type` entry for the reflected value") - } - - fn visit_str(self, type_path: &str) -> Result - where - E: Error, - { - self.0.get_with_type_path(type_path).ok_or_else(|| { - Error::custom(format_args!("No registration found for `{type_path}`")) - }) - } - } - - deserializer.deserialize_str(TypeRegistrationVisitor(self.registry)) - } -} - -/// A general purpose deserializer for reflected types. -/// -/// This is the deserializer counterpart to [`ReflectSerializer`]. -/// -/// See [`TypedReflectDeserializer`] for a deserializer that expects a known type. -/// -/// # Input -/// -/// This deserializer expects a map with a single entry, -/// where the key is the _full_ [type path] of the reflected type -/// and the value is the serialized data. -/// -/// # Output -/// -/// This deserializer will return a [`Box`] containing the deserialized data. -/// -/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, -/// this `Box` will contain the expected type. -/// For example, deserializing an `i32` will return a `Box` (as a `Box`). -/// -/// Otherwise, this `Box` will contain the dynamic equivalent. -/// For example, a deserialized struct might return a [`Box`] -/// and a deserialized `Vec` might return a [`Box`]. -/// -/// This means that if the actual type is needed, these dynamic representations will need to -/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. -/// -/// # Example -/// -/// ``` -/// # use serde::de::DeserializeSeed; -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::ReflectDeserializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = r#"{ -/// "my_crate::MyStruct": ( -/// value: 123 -/// ) -/// }"#; -/// -/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); -/// let reflect_deserializer = ReflectDeserializer::new(®istry); -/// -/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); -/// -/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, -/// // we know that its deserialized value will be a `DynamicStruct`, -/// // although it will represent `MyStruct`. -/// assert!(output.as_partial_reflect().represents::()); -/// -/// // We can convert back to `MyStruct` using `FromReflect`. -/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); -/// assert_eq!(value, MyStruct { value: 123 }); -/// -/// // We can also do this dynamically with `ReflectFromReflect`. -/// let type_id = output.get_represented_type_info().unwrap().type_id(); -/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); -/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); -/// assert!(value.is::()); -/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); -/// ``` -/// -/// [`ReflectSerializer`]: crate::serde::ReflectSerializer -/// [type path]: crate::TypePath::type_path -/// [`Box`]: crate::Reflect -/// [`ReflectKind::Value`]: crate::ReflectKind::Value -/// [`ReflectDeserialize`]: crate::ReflectDeserialize -/// [`Box`]: crate::DynamicStruct -/// [`Box`]: crate::DynamicList -/// [`FromReflect`]: crate::FromReflect -/// [`ReflectFromReflect`]: crate::ReflectFromReflect -pub struct ReflectDeserializer<'a> { - registry: &'a TypeRegistry, -} - -impl<'a> ReflectDeserializer<'a> { - pub fn new(registry: &'a TypeRegistry) -> Self { - Self { registry } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for ReflectDeserializer<'a> { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct UntypedReflectDeserializerVisitor<'a> { - registry: &'a TypeRegistry, - } - - impl<'a, 'de> Visitor<'de> for UntypedReflectDeserializerVisitor<'a> { - type Value = Box; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter - .write_str("map containing `type` and `value` entries for the reflected value") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let registration = map - .next_key_seed(TypeRegistrationDeserializer::new(self.registry))? - .ok_or_else(|| Error::invalid_length(0, &"a single entry"))?; - - let value = map.next_value_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })?; - - if map.next_key::()?.is_some() { - return Err(Error::invalid_length(2, &"a single entry")); - } - - Ok(value) - } - } - - deserializer.deserialize_map(UntypedReflectDeserializerVisitor { - registry: self.registry, - }) - } -} - -/// A deserializer for reflected types whose [`TypeRegistration`] is known. -/// -/// This is the deserializer counterpart to [`TypedReflectSerializer`]. -/// -/// See [`ReflectDeserializer`] for a deserializer that expects an unknown type. -/// -/// # Input -/// -/// Since the type is already known, the input is just the serialized data. -/// -/// # Output -/// -/// This deserializer will return a [`Box`] containing the deserialized data. -/// -/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, -/// this `Box` will contain the expected type. -/// For example, deserializing an `i32` will return a `Box` (as a `Box`). -/// -/// Otherwise, this `Box` will contain the dynamic equivalent. -/// For example, a deserialized struct might return a [`Box`] -/// and a deserialized `Vec` might return a [`Box`]. -/// -/// This means that if the actual type is needed, these dynamic representations will need to -/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. -/// -/// # Example -/// -/// ``` -/// # use std::any::TypeId; -/// # use serde::de::DeserializeSeed; -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::TypedReflectDeserializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = r#"( -/// value: 123 -/// )"#; -/// -/// let registration = registry.get(TypeId::of::()).unwrap(); -/// -/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); -/// let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); -/// -/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); -/// -/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, -/// // we know that its deserialized value will be a `DynamicStruct`, -/// // although it will represent `MyStruct`. -/// assert!(output.as_partial_reflect().represents::()); -/// -/// // We can convert back to `MyStruct` using `FromReflect`. -/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); -/// assert_eq!(value, MyStruct { value: 123 }); -/// -/// // We can also do this dynamically with `ReflectFromReflect`. -/// let type_id = output.get_represented_type_info().unwrap().type_id(); -/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); -/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); -/// assert!(value.is::()); -/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); -/// ``` -/// -/// [`TypedReflectSerializer`]: crate::serde::TypedReflectSerializer -/// [`Box`]: crate::Reflect -/// [`ReflectKind::Value`]: crate::ReflectKind::Value -/// [`ReflectDeserialize`]: crate::ReflectDeserialize -/// [`Box`]: crate::DynamicStruct -/// [`Box`]: crate::DynamicList -/// [`FromReflect`]: crate::FromReflect -/// [`ReflectFromReflect`]: crate::ReflectFromReflect -pub struct TypedReflectDeserializer<'a> { - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a> TypedReflectDeserializer<'a> { - pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { - Self { - registration, - registry, - } - } -} - -impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { - type Value = Box; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let type_path = self.registration.type_info().type_path(); - - // Handle both Value case and types that have a custom `ReflectDeserialize` - if let Some(deserialize_reflect) = self.registration.data::() { - let value = deserialize_reflect.deserialize(deserializer)?; - return Ok(value.into_partial_reflect()); - } - - match self.registration.type_info() { - TypeInfo::Struct(struct_info) => { - let mut dynamic_struct = deserializer.deserialize_struct( - struct_info.type_path_table().ident().unwrap(), - struct_info.field_names(), - StructVisitor { - struct_info, - registration: self.registration, - registry: self.registry, - }, - )?; - dynamic_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_struct)) - } - TypeInfo::TupleStruct(tuple_struct_info) => { - let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( - tuple_struct_info.type_path_table().ident().unwrap(), - tuple_struct_info.field_len(), - TupleStructVisitor { - tuple_struct_info, - registry: self.registry, - registration: self.registration, - }, - )?; - dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple_struct)) - } - TypeInfo::List(list_info) => { - let mut dynamic_list = deserializer.deserialize_seq(ListVisitor { - list_info, - registry: self.registry, - })?; - dynamic_list.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_list)) - } - TypeInfo::Array(array_info) => { - let mut dynamic_array = deserializer.deserialize_tuple( - array_info.capacity(), - ArrayVisitor { - array_info, - registry: self.registry, - }, - )?; - dynamic_array.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_array)) - } - TypeInfo::Map(map_info) => { - let mut dynamic_map = deserializer.deserialize_map(MapVisitor { - map_info, - registry: self.registry, - })?; - dynamic_map.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_map)) - } - TypeInfo::Set(set_info) => { - let mut dynamic_set = deserializer.deserialize_seq(SetVisitor { - set_info, - registry: self.registry, - })?; - dynamic_set.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_set)) - } - TypeInfo::Tuple(tuple_info) => { - let mut dynamic_tuple = deserializer.deserialize_tuple( - tuple_info.field_len(), - TupleVisitor { - tuple_info, - registration: self.registration, - registry: self.registry, - }, - )?; - dynamic_tuple.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple)) - } - TypeInfo::Enum(enum_info) => { - let mut dynamic_enum = if enum_info.type_path_table().module_path() - == Some("core::option") - && enum_info.type_path_table().ident() == Some("Option") - { - deserializer.deserialize_option(OptionVisitor { - enum_info, - registry: self.registry, - })? - } else { - deserializer.deserialize_enum( - enum_info.type_path_table().ident().unwrap(), - enum_info.variant_names(), - EnumVisitor { - enum_info, - registration: self.registration, - registry: self.registry, - }, - )? - }; - dynamic_enum.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_enum)) - } - TypeInfo::Value(_) => { - // This case should already be handled - Err(Error::custom(format_args!( - "Type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", - ))) - } - } - } -} - -struct StructVisitor<'a> { - struct_info: &'static StructInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { - type Value = DynamicStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected struct value") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - visit_struct(&mut map, self.struct_info, self.registration, self.registry) - } -} - -struct TupleStructVisitor<'a> { - tuple_struct_info: &'static TupleStructInfo, - registry: &'a TypeRegistry, - registration: &'a TypeRegistration, -} - -impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { - type Value = DynamicTupleStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple struct value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple( - &mut seq, - self.tuple_struct_info, - self.registration, - self.registry, - ) - .map(DynamicTupleStruct::from) - } -} - -struct TupleVisitor<'a> { - tuple_info: &'static TupleInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> { - type Value = DynamicTuple; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) - } -} - -struct ArrayVisitor<'a> { - array_info: &'static ArrayInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> { - type Value = DynamicArray; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected array value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); - let registration = get_registration( - self.array_info.item_ty().id(), - self.array_info.item_ty().path(), - self.registry, - )?; - while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })? { - vec.push(value); - } - - if vec.len() != self.array_info.capacity() { - return Err(Error::invalid_length( - vec.len(), - &self.array_info.capacity().to_string().as_str(), - )); - } - - Ok(DynamicArray::new(vec.into_boxed_slice())) - } -} - -struct ListVisitor<'a> { - list_info: &'static ListInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { - type Value = DynamicList; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected list value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - let mut list = DynamicList::default(); - let registration = get_registration( - self.list_info.item_ty().id(), - self.list_info.item_ty().path(), - self.registry, - )?; - while let Some(value) = seq.next_element_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })? { - list.push_box(value); - } - Ok(list) - } -} - -struct MapVisitor<'a> { - map_info: &'static MapInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { - type Value = DynamicMap; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected map value") - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - let mut dynamic_map = DynamicMap::default(); - let key_registration = get_registration( - self.map_info.key_ty().id(), - self.map_info.key_ty().path(), - self.registry, - )?; - let value_registration = get_registration( - self.map_info.value_ty().id(), - self.map_info.value_ty().path(), - self.registry, - )?; - while let Some(key) = map.next_key_seed(TypedReflectDeserializer { - registration: key_registration, - registry: self.registry, - })? { - let value = map.next_value_seed(TypedReflectDeserializer { - registration: value_registration, - registry: self.registry, - })?; - dynamic_map.insert_boxed(key, value); - } - - Ok(dynamic_map) - } -} - -struct SetVisitor<'a> { - set_info: &'static SetInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { - type Value = DynamicSet; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected set value") - } - - fn visit_seq(self, mut set: V) -> Result - where - V: SeqAccess<'de>, - { - let mut dynamic_set = DynamicSet::default(); - let value_registration = get_registration( - self.set_info.value_ty().id(), - self.set_info.value_ty().path(), - self.registry, - )?; - while let Some(value) = set.next_element_seed(TypedReflectDeserializer { - registration: value_registration, - registry: self.registry, - })? { - dynamic_set.insert_boxed(value); - } - - Ok(dynamic_set) - } -} - -struct EnumVisitor<'a> { - enum_info: &'static EnumInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { - type Value = DynamicEnum; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected enum value") - } - - fn visit_enum(self, data: A) -> Result - where - A: EnumAccess<'de>, - { - let mut dynamic_enum = DynamicEnum::default(); - let (variant_info, variant) = data.variant_seed(VariantDeserializer { - enum_info: self.enum_info, - })?; - - let value: DynamicVariant = match variant_info { - VariantInfo::Unit(..) => variant.unit_variant()?.into(), - VariantInfo::Struct(struct_info) => variant - .struct_variant( - struct_info.field_names(), - StructVariantVisitor { - struct_info, - registration: self.registration, - registry: self.registry, - }, - )? - .into(), - VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { - let registration = tuple_info.get_field_registration(0, self.registry)?; - let value = variant.newtype_variant_seed(TypedReflectDeserializer { - registration, - registry: self.registry, - })?; - let mut dynamic_tuple = DynamicTuple::default(); - dynamic_tuple.insert_boxed(value); - dynamic_tuple.into() - } - VariantInfo::Tuple(tuple_info) => variant - .tuple_variant( - tuple_info.field_len(), - TupleVariantVisitor { - tuple_info, - registration: self.registration, - registry: self.registry, - }, - )? - .into(), - }; - let variant_name = variant_info.name(); - let variant_index = self - .enum_info - .index_of(variant_name) - .expect("variant should exist"); - dynamic_enum.set_variant_with_index(variant_index, variant_name, value); - Ok(dynamic_enum) - } -} - -struct VariantDeserializer { - enum_info: &'static EnumInfo, -} - -impl<'de> DeserializeSeed<'de> for VariantDeserializer { - type Value = &'static VariantInfo; - - fn deserialize(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - struct VariantVisitor(&'static EnumInfo); - - impl<'de> Visitor<'de> for VariantVisitor { - type Value = &'static VariantInfo; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("expected either a variant index or variant name") - } - - fn visit_u32(self, variant_index: u32) -> Result - where - E: Error, - { - self.0.variant_at(variant_index as usize).ok_or_else(|| { - Error::custom(format_args!( - "no variant found at index `{}` on enum `{}`", - variant_index, - self.0.type_path() - )) - }) - } - - fn visit_str(self, variant_name: &str) -> Result - where - E: Error, - { - self.0.variant(variant_name).ok_or_else(|| { - let names = self.0.iter().map(VariantInfo::name); - Error::custom(format_args!( - "unknown variant `{}`, expected one of {:?}", - variant_name, - ExpectedValues(names.collect()) - )) - }) - } - } - - deserializer.deserialize_identifier(VariantVisitor(self.enum_info)) - } -} - -struct StructVariantVisitor<'a> { - struct_info: &'static StructVariantInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { - type Value = DynamicStruct; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected struct variant value") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) - } - - fn visit_map(self, mut map: V) -> Result - where - V: MapAccess<'de>, - { - visit_struct(&mut map, self.struct_info, self.registration, self.registry) - } -} - -struct TupleVariantVisitor<'a> { - tuple_info: &'static TupleVariantInfo, - registration: &'a TypeRegistration, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> { - type Value = DynamicTuple; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected tuple variant value") - } - - fn visit_seq(self, mut seq: V) -> Result - where - V: SeqAccess<'de>, - { - visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) - } -} - -struct OptionVisitor<'a> { - enum_info: &'static EnumInfo, - registry: &'a TypeRegistry, -} - -impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> { - type Value = DynamicEnum; - - fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { - formatter.write_str("reflected option value of type ")?; - formatter.write_str(self.enum_info.type_path()) - } - - fn visit_none(self) -> Result - where - E: Error, - { - let mut option = DynamicEnum::default(); - option.set_variant("None", ()); - Ok(option) - } - - fn visit_some(self, deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - let variant_info = self.enum_info.variant("Some").unwrap(); - match variant_info { - VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { - let field = tuple_info.field_at(0).unwrap(); - let registration = - get_registration(field.type_id(), field.type_path(), self.registry)?; - let de = TypedReflectDeserializer { - registration, - registry: self.registry, - }; - let mut value = DynamicTuple::default(); - value.insert_boxed(de.deserialize(deserializer)?); - let mut option = DynamicEnum::default(); - option.set_variant("Some", value); - Ok(option) - } - info => Err(Error::custom(format_args!( - "invalid variant, expected `Some` but got `{}`", - info.name() - ))), - } - } -} - -fn visit_struct<'de, T, V>( - map: &mut V, - info: &'static T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: StructLikeInfo, - V: MapAccess<'de>, -{ - let mut dynamic_struct = DynamicStruct::default(); - while let Some(Ident(key)) = map.next_key::()? { - let field = info.get_field(&key).ok_or_else(|| { - let fields = info.iter_fields().map(NamedField::name); - Error::custom(format_args!( - "unknown field `{}`, expected one of {:?}", - key, - ExpectedValues(fields.collect()) - )) - })?; - let registration = get_registration(field.type_id(), field.type_path(), registry)?; - let value = map.next_value_seed(TypedReflectDeserializer { - registration, - registry, - })?; - dynamic_struct.insert_boxed(&key, value); - } - - if let Some(serialization_data) = registration.data::() { - for (skipped_index, skipped_field) in serialization_data.iter_skipped() { - let Some(field) = info.field_at(*skipped_index) else { - continue; - }; - dynamic_struct.insert_boxed( - field.name(), - skipped_field.generate_default().into_partial_reflect(), - ); - } - } - - Ok(dynamic_struct) -} - -fn visit_tuple<'de, T, V>( - seq: &mut V, - info: &T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: TupleLikeInfo + Container, - V: SeqAccess<'de>, -{ - let mut tuple = DynamicTuple::default(); - - let len = info.get_field_len(); - - if len == 0 { - // Handle empty tuple/tuple struct - return Ok(tuple); - } - - let serialization_data = registration.data::(); - - for index in 0..len { - if let Some(value) = serialization_data.and_then(|data| data.generate_default(index)) { - tuple.insert_boxed(value.into_partial_reflect()); - continue; - } - - let value = seq - .next_element_seed(TypedReflectDeserializer { - registration: info.get_field_registration(index, registry)?, - registry, - })? - .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; - tuple.insert_boxed(value); - } - - Ok(tuple) -} - -fn visit_struct_seq<'de, T, V>( - seq: &mut V, - info: &T, - registration: &TypeRegistration, - registry: &TypeRegistry, -) -> Result -where - T: StructLikeInfo + Container, - V: SeqAccess<'de>, -{ - let mut dynamic_struct = DynamicStruct::default(); - - let len = info.get_field_len(); - - if len == 0 { - // Handle unit structs - return Ok(dynamic_struct); - } - - let serialization_data = registration.data::(); - - for index in 0..len { - let name = info.field_at(index).unwrap().name(); - - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or_default() - { - if let Some(value) = serialization_data.unwrap().generate_default(index) { - dynamic_struct.insert_boxed(name, value.into_partial_reflect()); - } - continue; - } - - let value = seq - .next_element_seed(TypedReflectDeserializer { - registration: info.get_field_registration(index, registry)?, - registry, - })? - .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; - dynamic_struct.insert_boxed(name, value); - } - - Ok(dynamic_struct) -} - -fn get_registration<'a, E: Error>( - type_id: TypeId, - type_path: &str, - registry: &'a TypeRegistry, -) -> Result<&'a TypeRegistration, E> { - let registration = registry.get(type_id).ok_or_else(|| { - Error::custom(format_args!("no registration found for type `{type_path}`")) - })?; - Ok(registration) -} - -#[cfg(test)] -mod tests { - use bincode::Options; - use std::any::TypeId; - use std::f32::consts::PI; - use std::ops::RangeInclusive; - - use serde::de::DeserializeSeed; - use serde::Deserialize; - - use bevy_utils::{HashMap, HashSet}; - - use crate as bevy_reflect; - use crate::serde::{ReflectDeserializer, ReflectSerializer, TypedReflectDeserializer}; - use crate::{ - DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistry, - }; - - #[derive(Reflect, Debug, PartialEq)] - struct MyStruct { - primitive_value: i8, - option_value: Option, - option_value_complex: Option, - tuple_value: (f32, usize), - list_value: Vec, - array_value: [i32; 5], - map_value: HashMap, - set_value: HashSet, - struct_value: SomeStruct, - tuple_struct_value: SomeTupleStruct, - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum, - newtype_enum: SomeEnum, - tuple_enum: SomeEnum, - struct_enum: SomeEnum, - ignored_struct: SomeIgnoredStruct, - ignored_tuple_struct: SomeIgnoredTupleStruct, - ignored_struct_variant: SomeIgnoredEnum, - ignored_tuple_variant: SomeIgnoredEnum, - custom_deserialize: CustomDeserialize, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeStruct { - foo: i64, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeTupleStruct(String); - - #[derive(Reflect, Debug, PartialEq)] - struct SomeUnitStruct; - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredStruct { - #[reflect(ignore)] - ignored: i32, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); - - #[derive(Reflect, Debug, PartialEq, Deserialize)] - struct SomeDeserializableStruct { - foo: i64, - } - - /// Implements a custom deserialize using `#[reflect(Deserialize)]`. - /// - /// For testing purposes, this is just the auto-generated one from deriving. - #[derive(Reflect, Debug, PartialEq, Deserialize)] - #[reflect(Deserialize)] - struct CustomDeserialize { - value: usize, - #[serde(alias = "renamed")] - inner_struct: SomeDeserializableStruct, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { foo: String }, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeIgnoredEnum { - Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), - Struct { - #[reflect(ignore)] - foo: String, - }, - } - - fn get_registry() -> TypeRegistry { - let mut registry = TypeRegistry::default(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::<(f32, usize)>(); - registry.register::<[i32; 5]>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register::>(); - registry.register_type_data::, ReflectDeserialize>(); - registry - } - - fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); - map.insert(64, 32); - - let mut set = HashSet::new(); - set.insert(64); - - MyStruct { - primitive_value: 123, - option_value: Some(String::from("Hello world!")), - option_value_complex: Some(SomeStruct { foo: 123 }), - tuple_value: (PI, 1337), - list_value: vec![-2, -1, 0, 1, 2], - array_value: [-2, -1, 0, 1, 2], - map_value: map, - set_value: set, - struct_value: SomeStruct { foo: 999999999 }, - tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum::Unit, - newtype_enum: SomeEnum::NewType(123), - tuple_enum: SomeEnum::Tuple(1.23, 3.21), - struct_enum: SomeEnum::Struct { - foo: String::from("Struct variant value"), - }, - ignored_struct: SomeIgnoredStruct { ignored: 0 }, - ignored_tuple_struct: SomeIgnoredTupleStruct(0), - ignored_struct_variant: SomeIgnoredEnum::Struct { - foo: String::default(), - }, - ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), - custom_deserialize: CustomDeserialize { - value: 100, - inner_struct: SomeDeserializableStruct { foo: 101 }, - }, - } - } - - #[test] - fn should_deserialize() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = r#"{ - "bevy_reflect::serde::de::tests::MyStruct": ( - primitive_value: 123, - option_value: Some("Hello world!"), - option_value_complex: Some(( - foo: 123, - )), - tuple_value: (3.1415927, 1337), - list_value: [ - -2, - -1, - 0, - 1, - 2, - ], - array_value: (-2, -1, 0, 1, 2), - map_value: { - 64: 32, - }, - set_value: [ - 64, - ], - struct_value: ( - foo: 999999999, - ), - tuple_struct_value: ("Tuple Struct"), - unit_struct: (), - unit_enum: Unit, - newtype_enum: NewType(123), - tuple_enum: Tuple(1.23, 3.21), - struct_enum: Struct( - foo: "Struct variant value", - ), - ignored_struct: (), - ignored_tuple_struct: (), - ignored_struct_variant: Struct(), - ignored_tuple_variant: Tuple(), - custom_deserialize: ( - value: 100, - renamed: ( - foo: 101, - ), - ), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_value() { - let input = r#"{ - "f32": 1.23, - }"#; - - let registry = get_registry(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - let output = dynamic_output - .try_take::() - .expect("underlying type should be f32"); - assert_eq!(1.23, output); - } - - #[test] - fn should_deserialized_typed() { - #[derive(Reflect, Debug, PartialEq)] - struct Foo { - bar: i32, - } - - let expected = Foo { bar: 123 }; - - let input = r#"( - bar: 123 - )"#; - - let mut registry = get_registry(); - registry.register::(); - let registration = registry.get(TypeId::of::()).unwrap(); - let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = - ::from_reflect(dynamic_output.as_ref().as_partial_reflect()) - .unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_option() { - #[derive(Reflect, Debug, PartialEq)] - struct OptionTest { - none: Option<()>, - simple: Option, - complex: Option, - } - - let expected = OptionTest { - none: None, - simple: Some(String::from("Hello world!")), - complex: Some(SomeStruct { foo: 123 }), - }; - - let mut registry = get_registry(); - registry.register::(); - registry.register::>(); - - // === Normal === // - let input = r#"{ - "bevy_reflect::serde::de::tests::OptionTest": ( - none: None, - simple: Some("Hello world!"), - complex: Some(( - foo: 123, - )), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output, "failed to deserialize Options"); - - // === Implicit Some === // - let input = r#" - #![enable(implicit_some)] - { - "bevy_reflect::serde::de::tests::OptionTest": ( - none: None, - simple: "Hello world!", - complex: ( - foo: 123, - ), - ), - }"#; - - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let dynamic_output = reflect_deserializer - .deserialize(&mut ron_deserializer) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!( - expected, output, - "failed to deserialize Options with implicit Some" - ); - } - - #[test] - fn enum_should_deserialize() { - #[derive(Reflect)] - enum MyEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { value: String }, - } - - let mut registry = get_registry(); - registry.register::(); - - // === Unit Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Unit, -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Unit); - assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); - - // === NewType Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": NewType(123), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::NewType(123)); - assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); - - // === Tuple Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Tuple(1.23, 3.21), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Tuple(1.23, 3.21)); - assert!(expected - .reflect_partial_eq(output.as_partial_reflect()) - .unwrap()); - - // === Struct Variant === // - let input = r#"{ - "bevy_reflect::serde::de::tests::MyEnum": Struct( - value: "I <3 Enums", - ), -}"#; - let reflect_deserializer = ReflectDeserializer::new(®istry); - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let expected = DynamicEnum::from(MyEnum::Struct { - value: String::from("I <3 Enums"), - }); - assert!(expected - .reflect_partial_eq(output.as_partial_reflect()) - .unwrap()); - } - - // Regression test for https://github.com/bevyengine/bevy/issues/12462 - #[test] - fn should_reserialize() { - let registry = get_registry(); - let input1 = get_my_struct(); - - let serializer1 = ReflectSerializer::new(&input1, ®istry); - let serialized1 = ron::ser::to_string(&serializer1).unwrap(); - - let mut deserializer = ron::de::Deserializer::from_str(&serialized1).unwrap(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let input2 = reflect_deserializer.deserialize(&mut deserializer).unwrap(); - - let serializer2 = ReflectSerializer::new(input2.as_partial_reflect(), ®istry); - let serialized2 = ron::ser::to_string(&serializer2).unwrap(); - - assert_eq!(serialized1, serialized2); - } - - #[test] - fn should_deserialize_non_self_describing_binary() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, - 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, - 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, 0, 0, - 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, 0, 0, - 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, - 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, 255, - 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, 0, - 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, 0, - 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, - 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, - 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, - 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, - 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, - ]; - - let deserializer = ReflectDeserializer::new(®istry); - - let dynamic_output = bincode::DefaultOptions::new() - .with_fixint_encoding() - .deserialize_seed(deserializer, &input) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_deserialize_self_describing_binary() { - let expected = get_my_struct(); - let registry = get_registry(); - - let input = vec![ - 129, 217, 40, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, - 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, - 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, - 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, - 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, 145, - 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, - 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, - 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, - 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, - 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, - 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, - ]; - - let mut reader = std::io::BufReader::new(input.as_slice()); - - let deserializer = ReflectDeserializer::new(®istry); - let dynamic_output = deserializer - .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) - .unwrap(); - - let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); - assert_eq!(expected, output); - } - - #[test] - fn should_return_error_if_missing_type_data() { - let mut registry = TypeRegistry::new(); - registry.register::>(); - - let input = r#"{"core::ops::RangeInclusive":(start:0.0,end:1.0)}"#; - let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); - let reflect_deserializer = ReflectDeserializer::new(®istry); - let error = reflect_deserializer - .deserialize(&mut deserializer) - .unwrap_err(); - assert_eq!(error, ron::Error::Message("Type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); - } -} diff --git a/crates/bevy_reflect/src/serde/de/arrays.rs b/crates/bevy_reflect/src/serde/de/arrays.rs new file mode 100644 index 0000000000..6e2f331a65 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/arrays.rs @@ -0,0 +1,53 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{ArrayInfo, DynamicArray, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{Error, SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Array`] values. +/// +/// [`Array`]: crate::Array +pub(super) struct ArrayVisitor<'a> { + array_info: &'static ArrayInfo, + registry: &'a TypeRegistry, +} + +impl<'a> ArrayVisitor<'a> { + pub fn new(array_info: &'static ArrayInfo, registry: &'a TypeRegistry) -> Self { + Self { + array_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> { + type Value = DynamicArray; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected array value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); + let registration = try_get_registration(self.array_info.item_ty(), self.registry)?; + while let Some(value) = + seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? + { + vec.push(value); + } + + if vec.len() != self.array_info.capacity() { + return Err(Error::invalid_length( + vec.len(), + &self.array_info.capacity().to_string().as_str(), + )); + } + + Ok(DynamicArray::new(vec.into_boxed_slice())) + } +} diff --git a/crates/bevy_reflect/src/serde/de/deserializer.rs b/crates/bevy_reflect/src/serde/de/deserializer.rs new file mode 100644 index 0000000000..a4496dc72d --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/deserializer.rs @@ -0,0 +1,333 @@ +use crate::serde::de::arrays::ArrayVisitor; +use crate::serde::de::enums::EnumVisitor; +use crate::serde::de::lists::ListVisitor; +use crate::serde::de::maps::MapVisitor; +use crate::serde::de::options::OptionVisitor; +use crate::serde::de::sets::SetVisitor; +use crate::serde::de::structs::StructVisitor; +use crate::serde::de::tuple_structs::TupleStructVisitor; +use crate::serde::de::tuples::TupleVisitor; +use crate::serde::TypeRegistrationDeserializer; +use crate::{PartialReflect, ReflectDeserialize, TypeInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, IgnoredAny, MapAccess, Visitor}; +use std::fmt; + +/// A general purpose deserializer for reflected types. +/// +/// This is the deserializer counterpart to [`ReflectSerializer`]. +/// +/// See [`TypedReflectDeserializer`] for a deserializer that expects a known type. +/// +/// # Input +/// +/// This deserializer expects a map with a single entry, +/// where the key is the _full_ [type path] of the reflected type +/// and the value is the serialized data. +/// +/// # Output +/// +/// This deserializer will return a [`Box`] containing the deserialized data. +/// +/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, +/// this `Box` will contain the expected type. +/// For example, deserializing an `i32` will return a `Box` (as a `Box`). +/// +/// Otherwise, this `Box` will contain the dynamic equivalent. +/// For example, a deserialized struct might return a [`Box`] +/// and a deserialized `Vec` might return a [`Box`]. +/// +/// This means that if the actual type is needed, these dynamic representations will need to +/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. +/// +/// # Example +/// +/// ``` +/// # use serde::de::DeserializeSeed; +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::ReflectDeserializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = r#"{ +/// "my_crate::MyStruct": ( +/// value: 123 +/// ) +/// }"#; +/// +/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); +/// let reflect_deserializer = ReflectDeserializer::new(®istry); +/// +/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); +/// +/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, +/// // we know that its deserialized value will be a `DynamicStruct`, +/// // although it will represent `MyStruct`. +/// assert!(output.as_partial_reflect().represents::()); +/// +/// // We can convert back to `MyStruct` using `FromReflect`. +/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); +/// assert_eq!(value, MyStruct { value: 123 }); +/// +/// // We can also do this dynamically with `ReflectFromReflect`. +/// let type_id = output.get_represented_type_info().unwrap().type_id(); +/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); +/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); +/// assert!(value.is::()); +/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); +/// ``` +/// +/// [`ReflectSerializer`]: crate::serde::ReflectSerializer +/// [type path]: crate::TypePath::type_path +/// [`Box`]: crate::Reflect +/// [`ReflectKind::Value`]: crate::ReflectKind::Value +/// [`ReflectDeserialize`]: crate::ReflectDeserialize +/// [`Box`]: crate::DynamicStruct +/// [`Box`]: crate::DynamicList +/// [`FromReflect`]: crate::FromReflect +/// [`ReflectFromReflect`]: crate::ReflectFromReflect +pub struct ReflectDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> ReflectDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + Self { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for ReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct UntypedReflectDeserializerVisitor<'a> { + registry: &'a TypeRegistry, + } + + impl<'a, 'de> Visitor<'de> for UntypedReflectDeserializerVisitor<'a> { + type Value = Box; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter + .write_str("map containing `type` and `value` entries for the reflected value") + } + + fn visit_map(self, mut map: A) -> Result + where + A: MapAccess<'de>, + { + let registration = map + .next_key_seed(TypeRegistrationDeserializer::new(self.registry))? + .ok_or_else(|| Error::invalid_length(0, &"a single entry"))?; + + let value = map.next_value_seed(TypedReflectDeserializer { + registration, + registry: self.registry, + })?; + + if map.next_key::()?.is_some() { + return Err(Error::invalid_length(2, &"a single entry")); + } + + Ok(value) + } + } + + deserializer.deserialize_map(UntypedReflectDeserializerVisitor { + registry: self.registry, + }) + } +} + +/// A deserializer for reflected types whose [`TypeRegistration`] is known. +/// +/// This is the deserializer counterpart to [`TypedReflectSerializer`]. +/// +/// See [`ReflectDeserializer`] for a deserializer that expects an unknown type. +/// +/// # Input +/// +/// Since the type is already known, the input is just the serialized data. +/// +/// # Output +/// +/// This deserializer will return a [`Box`] containing the deserialized data. +/// +/// For value types (i.e. [`ReflectKind::Value`]) or types that register [`ReflectDeserialize`] type data, +/// this `Box` will contain the expected type. +/// For example, deserializing an `i32` will return a `Box` (as a `Box`). +/// +/// Otherwise, this `Box` will contain the dynamic equivalent. +/// For example, a deserialized struct might return a [`Box`] +/// and a deserialized `Vec` might return a [`Box`]. +/// +/// This means that if the actual type is needed, these dynamic representations will need to +/// be converted to the concrete type using [`FromReflect`] or [`ReflectFromReflect`]. +/// +/// # Example +/// +/// ``` +/// # use std::any::TypeId; +/// # use serde::de::DeserializeSeed; +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{DynamicStruct, TypeRegistry, serde::TypedReflectDeserializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = r#"( +/// value: 123 +/// )"#; +/// +/// let registration = registry.get(TypeId::of::()).unwrap(); +/// +/// let mut deserializer = ron::Deserializer::from_str(input).unwrap(); +/// let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); +/// +/// let output: Box = reflect_deserializer.deserialize(&mut deserializer).unwrap(); +/// +/// // Since `MyStruct` is not a value type and does not register `ReflectDeserialize`, +/// // we know that its deserialized value will be a `DynamicStruct`, +/// // although it will represent `MyStruct`. +/// assert!(output.as_partial_reflect().represents::()); +/// +/// // We can convert back to `MyStruct` using `FromReflect`. +/// let value: MyStruct = ::from_reflect(output.as_partial_reflect()).unwrap(); +/// assert_eq!(value, MyStruct { value: 123 }); +/// +/// // We can also do this dynamically with `ReflectFromReflect`. +/// let type_id = output.get_represented_type_info().unwrap().type_id(); +/// let reflect_from_reflect = registry.get_type_data::(type_id).unwrap(); +/// let value: Box = reflect_from_reflect.from_reflect(output.as_partial_reflect()).unwrap(); +/// assert!(value.is::()); +/// assert_eq!(value.take::().unwrap(), MyStruct { value: 123 }); +/// ``` +/// +/// [`TypedReflectSerializer`]: crate::serde::TypedReflectSerializer +/// [`Box`]: crate::Reflect +/// [`ReflectKind::Value`]: crate::ReflectKind::Value +/// [`ReflectDeserialize`]: crate::ReflectDeserialize +/// [`Box`]: crate::DynamicStruct +/// [`Box`]: crate::DynamicList +/// [`FromReflect`]: crate::FromReflect +/// [`ReflectFromReflect`]: crate::ReflectFromReflect +pub struct TypedReflectDeserializer<'a> { + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TypedReflectDeserializer<'a> { + pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { + Self { + registration, + registry, + } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { + type Value = Box; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let type_path = self.registration.type_info().type_path(); + + // Handle both Value case and types that have a custom `ReflectDeserialize` + if let Some(deserialize_reflect) = self.registration.data::() { + let value = deserialize_reflect.deserialize(deserializer)?; + return Ok(value.into_partial_reflect()); + } + + match self.registration.type_info() { + TypeInfo::Struct(struct_info) => { + let mut dynamic_struct = deserializer.deserialize_struct( + struct_info.type_path_table().ident().unwrap(), + struct_info.field_names(), + StructVisitor::new(struct_info, self.registration, self.registry), + )?; + dynamic_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_struct)) + } + TypeInfo::TupleStruct(tuple_struct_info) => { + let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( + tuple_struct_info.type_path_table().ident().unwrap(), + tuple_struct_info.field_len(), + TupleStructVisitor::new(tuple_struct_info, self.registration, self.registry), + )?; + dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple_struct)) + } + TypeInfo::List(list_info) => { + let mut dynamic_list = + deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?; + dynamic_list.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_list)) + } + TypeInfo::Array(array_info) => { + let mut dynamic_array = deserializer.deserialize_tuple( + array_info.capacity(), + ArrayVisitor::new(array_info, self.registry), + )?; + dynamic_array.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_array)) + } + TypeInfo::Map(map_info) => { + let mut dynamic_map = + deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?; + dynamic_map.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_map)) + } + TypeInfo::Set(set_info) => { + let mut dynamic_set = + deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?; + dynamic_set.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_set)) + } + TypeInfo::Tuple(tuple_info) => { + let mut dynamic_tuple = deserializer.deserialize_tuple( + tuple_info.field_len(), + TupleVisitor::new(tuple_info, self.registration, self.registry), + )?; + dynamic_tuple.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple)) + } + TypeInfo::Enum(enum_info) => { + let mut dynamic_enum = if enum_info.type_path_table().module_path() + == Some("core::option") + && enum_info.type_path_table().ident() == Some("Option") + { + deserializer.deserialize_option(OptionVisitor::new(enum_info, self.registry))? + } else { + deserializer.deserialize_enum( + enum_info.type_path_table().ident().unwrap(), + enum_info.variant_names(), + EnumVisitor::new(enum_info, self.registration, self.registry), + )? + }; + dynamic_enum.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_enum)) + } + TypeInfo::Value(_) => { + // This case should already be handled + Err(Error::custom(format_args!( + "Type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + ))) + } + } + } +} diff --git a/crates/bevy_reflect/src/serde/de/enums.rs b/crates/bevy_reflect/src/serde/de/enums.rs new file mode 100644 index 0000000000..9350ebce6f --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/enums.rs @@ -0,0 +1,198 @@ +use crate::serde::de::helpers::ExpectedValues; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; +use crate::serde::de::tuple_utils::{visit_tuple, TupleLikeInfo}; +use crate::serde::TypedReflectDeserializer; +use crate::{ + DynamicEnum, DynamicStruct, DynamicTuple, DynamicVariant, EnumInfo, StructVariantInfo, + TupleVariantInfo, TypeRegistration, TypeRegistry, VariantInfo, +}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, EnumAccess, Error, MapAccess, SeqAccess, VariantAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Enum`] values. +/// +/// [`Enum`]: crate::Enum +pub(super) struct EnumVisitor<'a> { + enum_info: &'static EnumInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> EnumVisitor<'a> { + pub fn new( + enum_info: &'static EnumInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + enum_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { + type Value = DynamicEnum; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected enum value") + } + + fn visit_enum(self, data: A) -> Result + where + A: EnumAccess<'de>, + { + let mut dynamic_enum = DynamicEnum::default(); + let (variant_info, variant) = data.variant_seed(VariantDeserializer { + enum_info: self.enum_info, + })?; + + let value: DynamicVariant = match variant_info { + VariantInfo::Unit(..) => variant.unit_variant()?.into(), + VariantInfo::Struct(struct_info) => variant + .struct_variant( + struct_info.field_names(), + StructVariantVisitor { + struct_info, + registration: self.registration, + registry: self.registry, + }, + )? + .into(), + VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { + let registration = try_get_registration( + *TupleLikeInfo::field_at(tuple_info, 0)?.ty(), + self.registry, + )?; + let value = variant.newtype_variant_seed(TypedReflectDeserializer::new( + registration, + self.registry, + ))?; + let mut dynamic_tuple = DynamicTuple::default(); + dynamic_tuple.insert_boxed(value); + dynamic_tuple.into() + } + VariantInfo::Tuple(tuple_info) => variant + .tuple_variant( + tuple_info.field_len(), + TupleVariantVisitor { + tuple_info, + registration: self.registration, + registry: self.registry, + }, + )? + .into(), + }; + let variant_name = variant_info.name(); + let variant_index = self + .enum_info + .index_of(variant_name) + .expect("variant should exist"); + dynamic_enum.set_variant_with_index(variant_index, variant_name, value); + Ok(dynamic_enum) + } +} + +struct VariantDeserializer { + enum_info: &'static EnumInfo, +} + +impl<'de> DeserializeSeed<'de> for VariantDeserializer { + type Value = &'static VariantInfo; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct VariantVisitor(&'static EnumInfo); + + impl<'de> Visitor<'de> for VariantVisitor { + type Value = &'static VariantInfo; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("expected either a variant index or variant name") + } + + fn visit_u32(self, variant_index: u32) -> Result + where + E: Error, + { + self.0.variant_at(variant_index as usize).ok_or_else(|| { + Error::custom(format_args!( + "no variant found at index `{}` on enum `{}`", + variant_index, + self.0.type_path() + )) + }) + } + + fn visit_str(self, variant_name: &str) -> Result + where + E: Error, + { + self.0.variant(variant_name).ok_or_else(|| { + let names = self.0.iter().map(VariantInfo::name); + Error::custom(format_args!( + "unknown variant `{}`, expected one of {:?}", + variant_name, + ExpectedValues::from_iter(names) + )) + }) + } + } + + deserializer.deserialize_identifier(VariantVisitor(self.enum_info)) + } +} + +struct StructVariantVisitor<'a> { + struct_info: &'static StructVariantInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for StructVariantVisitor<'a> { + type Value = DynamicStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected struct variant value") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + visit_struct(&mut map, self.struct_info, self.registration, self.registry) + } +} + +struct TupleVariantVisitor<'a> { + tuple_info: &'static TupleVariantInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a, 'de> Visitor<'de> for TupleVariantVisitor<'a> { + type Value = DynamicTuple; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple variant value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/de/helpers.rs b/crates/bevy_reflect/src/serde/de/helpers.rs new file mode 100644 index 0000000000..3243a6c011 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/helpers.rs @@ -0,0 +1,70 @@ +use core::fmt::{Debug, Display, Formatter}; +use serde::de::{Error, Visitor}; +use serde::Deserialize; +use std::fmt; + +/// A debug struct used for error messages that displays a list of expected values. +/// +/// # Example +/// +/// ```ignore (Can't import private struct from doctest) +/// let expected = vec!["foo", "bar", "baz"]; +/// assert_eq!("`foo`, `bar`, `baz`", format!("{}", ExpectedValues(expected))); +/// ``` +pub(super) struct ExpectedValues(pub Vec); + +impl FromIterator for ExpectedValues { + fn from_iter>(iter: I) -> Self { + Self(iter.into_iter().collect()) + } +} + +impl Debug for ExpectedValues { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let len = self.0.len(); + for (index, item) in self.0.iter().enumerate() { + write!(f, "`{item}`")?; + if index < len - 1 { + write!(f, ", ")?; + } + } + Ok(()) + } +} + +/// Represents a simple reflected identifier. +#[derive(Debug, Clone, Eq, PartialEq)] +pub(super) struct Ident(pub String); + +impl<'de> Deserialize<'de> for Ident { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct IdentVisitor; + + impl<'de> Visitor<'de> for IdentVisitor { + type Value = Ident; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("identifier") + } + + fn visit_str(self, value: &str) -> Result + where + E: Error, + { + Ok(Ident(value.to_string())) + } + + fn visit_string(self, value: String) -> Result + where + E: Error, + { + Ok(Ident(value)) + } + } + + deserializer.deserialize_identifier(IdentVisitor) + } +} diff --git a/crates/bevy_reflect/src/serde/de/lists.rs b/crates/bevy_reflect/src/serde/de/lists.rs new file mode 100644 index 0000000000..30e7d9282d --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/lists.rs @@ -0,0 +1,45 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicList, ListInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`List`] values. +/// +/// [`List`]: crate::List +pub(super) struct ListVisitor<'a> { + list_info: &'static ListInfo, + registry: &'a TypeRegistry, +} + +impl<'a> ListVisitor<'a> { + pub fn new(list_info: &'static ListInfo, registry: &'a TypeRegistry) -> Self { + Self { + list_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { + type Value = DynamicList; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected list value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + let mut list = DynamicList::default(); + let registration = try_get_registration(self.list_info.item_ty(), self.registry)?; + while let Some(value) = + seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? + { + list.push_box(value); + } + Ok(list) + } +} diff --git a/crates/bevy_reflect/src/serde/de/maps.rs b/crates/bevy_reflect/src/serde/de/maps.rs new file mode 100644 index 0000000000..0b185110d6 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/maps.rs @@ -0,0 +1,49 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicMap, Map, MapInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{MapAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Map`] values. +/// +/// [`Map`]: crate::Map +pub(super) struct MapVisitor<'a> { + map_info: &'static MapInfo, + registry: &'a TypeRegistry, +} + +impl<'a> MapVisitor<'a> { + pub fn new(map_info: &'static MapInfo, registry: &'a TypeRegistry) -> Self { + Self { map_info, registry } + } +} + +impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { + type Value = DynamicMap; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected map value") + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + let mut dynamic_map = DynamicMap::default(); + let key_registration = try_get_registration(self.map_info.key_ty(), self.registry)?; + let value_registration = try_get_registration(self.map_info.value_ty(), self.registry)?; + while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new( + key_registration, + self.registry, + ))? { + let value = map.next_value_seed(TypedReflectDeserializer::new( + value_registration, + self.registry, + ))?; + dynamic_map.insert_boxed(key, value); + } + + Ok(dynamic_map) + } +} diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs new file mode 100644 index 0000000000..40bf927774 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -0,0 +1,515 @@ +pub use deserializer::*; +pub use registrations::*; + +mod arrays; +mod deserializer; +mod enums; +mod helpers; +mod lists; +mod maps; +mod options; +mod registration_utils; +mod registrations; +mod sets; +mod struct_utils; +mod structs; +mod tuple_structs; +mod tuple_utils; +mod tuples; + +#[cfg(test)] +mod tests { + use bincode::Options; + use std::any::TypeId; + use std::f32::consts::PI; + use std::ops::RangeInclusive; + + use serde::de::DeserializeSeed; + use serde::Deserialize; + + use bevy_utils::{HashMap, HashSet}; + + use crate as bevy_reflect; + use crate::serde::{ReflectDeserializer, ReflectSerializer, TypedReflectDeserializer}; + use crate::{ + DynamicEnum, FromReflect, PartialReflect, Reflect, ReflectDeserialize, TypeRegistry, + }; + + #[derive(Reflect, Debug, PartialEq)] + struct MyStruct { + primitive_value: i8, + option_value: Option, + option_value_complex: Option, + tuple_value: (f32, usize), + list_value: Vec, + array_value: [i32; 5], + map_value: HashMap, + set_value: HashSet, + struct_value: SomeStruct, + tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum, + newtype_enum: SomeEnum, + tuple_enum: SomeEnum, + struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, + custom_deserialize: CustomDeserialize, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeStruct { + foo: i64, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeTupleStruct(String); + + #[derive(Reflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + + #[derive(Reflect, Debug, PartialEq, Deserialize)] + struct SomeDeserializableStruct { + foo: i64, + } + + /// Implements a custom deserialize using `#[reflect(Deserialize)]`. + /// + /// For testing purposes, this is just the auto-generated one from deriving. + #[derive(Reflect, Debug, PartialEq, Deserialize)] + #[reflect(Deserialize)] + struct CustomDeserialize { + value: usize, + #[serde(alias = "renamed")] + inner_struct: SomeDeserializableStruct, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { foo: String }, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + + fn get_registry() -> TypeRegistry { + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::<(f32, usize)>(); + registry.register::<[i32; 5]>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register::>(); + registry.register_type_data::, ReflectDeserialize>(); + registry + } + + fn get_my_struct() -> MyStruct { + let mut map = HashMap::new(); + map.insert(64, 32); + + let mut set = HashSet::new(); + set.insert(64); + + MyStruct { + primitive_value: 123, + option_value: Some(String::from("Hello world!")), + option_value_complex: Some(SomeStruct { foo: 123 }), + tuple_value: (PI, 1337), + list_value: vec![-2, -1, 0, 1, 2], + array_value: [-2, -1, 0, 1, 2], + map_value: map, + set_value: set, + struct_value: SomeStruct { foo: 999999999 }, + tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum::Unit, + newtype_enum: SomeEnum::NewType(123), + tuple_enum: SomeEnum::Tuple(1.23, 3.21), + struct_enum: SomeEnum::Struct { + foo: String::from("Struct variant value"), + }, + ignored_struct: SomeIgnoredStruct { ignored: 0 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(0), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::default(), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(0.0, 0.0), + custom_deserialize: CustomDeserialize { + value: 100, + inner_struct: SomeDeserializableStruct { foo: 101 }, + }, + } + } + + #[test] + fn should_deserialize() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = r#"{ + "bevy_reflect::serde::de::tests::MyStruct": ( + primitive_value: 123, + option_value: Some("Hello world!"), + option_value_complex: Some(( + foo: 123, + )), + tuple_value: (3.1415927, 1337), + list_value: [ + -2, + -1, + 0, + 1, + 2, + ], + array_value: (-2, -1, 0, 1, 2), + map_value: { + 64: 32, + }, + set_value: [ + 64, + ], + struct_value: ( + foo: 999999999, + ), + tuple_struct_value: ("Tuple Struct"), + unit_struct: (), + unit_enum: Unit, + newtype_enum: NewType(123), + tuple_enum: Tuple(1.23, 3.21), + struct_enum: Struct( + foo: "Struct variant value", + ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), + custom_deserialize: ( + value: 100, + renamed: ( + foo: 101, + ), + ), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_value() { + let input = r#"{ + "f32": 1.23, + }"#; + + let registry = get_registry(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + let output = dynamic_output + .try_take::() + .expect("underlying type should be f32"); + assert_eq!(1.23, output); + } + + #[test] + fn should_deserialized_typed() { + #[derive(Reflect, Debug, PartialEq)] + struct Foo { + bar: i32, + } + + let expected = Foo { bar: 123 }; + + let input = r#"( + bar: 123 + )"#; + + let mut registry = get_registry(); + registry.register::(); + let registration = registry.get(TypeId::of::()).unwrap(); + let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = + ::from_reflect(dynamic_output.as_ref().as_partial_reflect()) + .unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_option() { + #[derive(Reflect, Debug, PartialEq)] + struct OptionTest { + none: Option<()>, + simple: Option, + complex: Option, + } + + let expected = OptionTest { + none: None, + simple: Some(String::from("Hello world!")), + complex: Some(SomeStruct { foo: 123 }), + }; + + let mut registry = get_registry(); + registry.register::(); + registry.register::>(); + + // === Normal === // + let input = r#"{ + "bevy_reflect::serde::de::tests::OptionTest": ( + none: None, + simple: Some("Hello world!"), + complex: Some(( + foo: 123, + )), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output, "failed to deserialize Options"); + + // === Implicit Some === // + let input = r#" + #![enable(implicit_some)] + { + "bevy_reflect::serde::de::tests::OptionTest": ( + none: None, + simple: "Hello world!", + complex: ( + foo: 123, + ), + ), + }"#; + + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let dynamic_output = reflect_deserializer + .deserialize(&mut ron_deserializer) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!( + expected, output, + "failed to deserialize Options with implicit Some" + ); + } + + #[test] + fn enum_should_deserialize() { + #[derive(Reflect)] + enum MyEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { value: String }, + } + + let mut registry = get_registry(); + registry.register::(); + + // === Unit Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Unit, +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Unit); + assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); + + // === NewType Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": NewType(123), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::NewType(123)); + assert!(expected.reflect_partial_eq(output.as_ref()).unwrap()); + + // === Tuple Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Tuple(1.23, 3.21), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Tuple(1.23, 3.21)); + assert!(expected + .reflect_partial_eq(output.as_partial_reflect()) + .unwrap()); + + // === Struct Variant === // + let input = r#"{ + "bevy_reflect::serde::de::tests::MyEnum": Struct( + value: "I <3 Enums", + ), +}"#; + let reflect_deserializer = ReflectDeserializer::new(®istry); + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let output = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let expected = DynamicEnum::from(MyEnum::Struct { + value: String::from("I <3 Enums"), + }); + assert!(expected + .reflect_partial_eq(output.as_partial_reflect()) + .unwrap()); + } + + // Regression test for https://github.com/bevyengine/bevy/issues/12462 + #[test] + fn should_reserialize() { + let registry = get_registry(); + let input1 = get_my_struct(); + + let serializer1 = ReflectSerializer::new(&input1, ®istry); + let serialized1 = ron::ser::to_string(&serializer1).unwrap(); + + let mut deserializer = ron::de::Deserializer::from_str(&serialized1).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let input2 = reflect_deserializer.deserialize(&mut deserializer).unwrap(); + + let serializer2 = ReflectSerializer::new(input2.as_partial_reflect(), ®istry); + let serialized2 = ron::ser::to_string(&serializer2).unwrap(); + + assert_eq!(serialized1, serialized2); + } + + #[test] + fn should_deserialize_non_self_describing_binary() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = vec![ + 1, 0, 0, 0, 0, 0, 0, 0, 40, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, + 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, + 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, 0, 0, + 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, 0, 0, + 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, 255, + 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, 255, + 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, 0, + 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, 1, + 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, 3, + 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, + 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, + 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + ]; + + let deserializer = ReflectDeserializer::new(®istry); + + let dynamic_output = bincode::DefaultOptions::new() + .with_fixint_encoding() + .deserialize_seed(deserializer, &input) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_deserialize_self_describing_binary() { + let expected = get_my_struct(); + let registry = get_registry(); + + let input = vec![ + 129, 217, 40, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, + 101, 114, 100, 101, 58, 58, 100, 101, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, 121, + 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, 111, + 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, 0, + 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, 145, + 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, 105, + 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, 101, + 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, 99, + 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, + 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, + 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, + ]; + + let mut reader = std::io::BufReader::new(input.as_slice()); + + let deserializer = ReflectDeserializer::new(®istry); + let dynamic_output = deserializer + .deserialize(&mut rmp_serde::Deserializer::new(&mut reader)) + .unwrap(); + + let output = ::from_reflect(dynamic_output.as_ref()).unwrap(); + assert_eq!(expected, output); + } + + #[test] + fn should_return_error_if_missing_type_data() { + let mut registry = TypeRegistry::new(); + registry.register::>(); + + let input = r#"{"core::ops::RangeInclusive":(start:0.0,end:1.0)}"#; + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let error = reflect_deserializer + .deserialize(&mut deserializer) + .unwrap_err(); + assert_eq!(error, ron::Error::Message("Type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); + } +} diff --git a/crates/bevy_reflect/src/serde/de/options.rs b/crates/bevy_reflect/src/serde/de/options.rs new file mode 100644 index 0000000000..103121a4b1 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/options.rs @@ -0,0 +1,62 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicEnum, DynamicTuple, EnumInfo, TypeRegistry, VariantInfo}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Option`] values. +pub(super) struct OptionVisitor<'a> { + enum_info: &'static EnumInfo, + registry: &'a TypeRegistry, +} + +impl<'a> OptionVisitor<'a> { + pub fn new(enum_info: &'static EnumInfo, registry: &'a TypeRegistry) -> Self { + Self { + enum_info, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> { + type Value = DynamicEnum; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected option value of type ")?; + formatter.write_str(self.enum_info.type_path()) + } + + fn visit_none(self) -> Result + where + E: Error, + { + let mut option = DynamicEnum::default(); + option.set_variant("None", ()); + Ok(option) + } + + fn visit_some(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let variant_info = self.enum_info.variant("Some").unwrap(); + match variant_info { + VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { + let field = tuple_info.field_at(0).unwrap(); + let registration = try_get_registration(*field.ty(), self.registry)?; + let de = TypedReflectDeserializer::new(registration, self.registry); + let mut value = DynamicTuple::default(); + value.insert_boxed(de.deserialize(deserializer)?); + let mut option = DynamicEnum::default(); + option.set_variant("Some", value); + Ok(option) + } + info => Err(Error::custom(format_args!( + "invalid variant, expected `Some` but got `{}`", + info.name() + ))), + } + } +} diff --git a/crates/bevy_reflect/src/serde/de/registration_utils.rs b/crates/bevy_reflect/src/serde/de/registration_utils.rs new file mode 100644 index 0000000000..8a170d1732 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/registration_utils.rs @@ -0,0 +1,15 @@ +use crate::{Type, TypeRegistration, TypeRegistry}; +use serde::de::Error; + +/// Attempts to find the [`TypeRegistration`] for a given [type]. +/// +/// [type]: Type +pub(super) fn try_get_registration( + ty: Type, + registry: &TypeRegistry, +) -> Result<&TypeRegistration, E> { + let registration = registry + .get(ty.id()) + .ok_or_else(|| Error::custom(format_args!("no registration found for type `{ty:?}`")))?; + Ok(registration) +} diff --git a/crates/bevy_reflect/src/serde/de/registrations.rs b/crates/bevy_reflect/src/serde/de/registrations.rs new file mode 100644 index 0000000000..9ed10628f7 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/registrations.rs @@ -0,0 +1,52 @@ +use crate::{TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{DeserializeSeed, Error, Visitor}; +use std::fmt; + +/// A deserializer for type registrations. +/// +/// This will return a [`&TypeRegistration`] corresponding to the given type. +/// This deserializer expects a string containing the _full_ [type path] of the +/// type to find the `TypeRegistration` of. +/// +/// [`&TypeRegistration`]: TypeRegistration +/// [type path]: crate::TypePath::type_path +pub struct TypeRegistrationDeserializer<'a> { + registry: &'a TypeRegistry, +} + +impl<'a> TypeRegistrationDeserializer<'a> { + pub fn new(registry: &'a TypeRegistry) -> Self { + Self { registry } + } +} + +impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { + type Value = &'a TypeRegistration; + + fn deserialize(self, deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct TypeRegistrationVisitor<'a>(&'a TypeRegistry); + + impl<'de, 'a> Visitor<'de> for TypeRegistrationVisitor<'a> { + type Value = &'a TypeRegistration; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("string containing `type` entry for the reflected value") + } + + fn visit_str(self, type_path: &str) -> Result + where + E: Error, + { + self.0.get_with_type_path(type_path).ok_or_else(|| { + Error::custom(format_args!("No registration found for `{type_path}`")) + }) + } + } + + deserializer.deserialize_str(TypeRegistrationVisitor(self.registry)) + } +} diff --git a/crates/bevy_reflect/src/serde/de/sets.rs b/crates/bevy_reflect/src/serde/de/sets.rs new file mode 100644 index 0000000000..fcb72a8f77 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/sets.rs @@ -0,0 +1,44 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::TypedReflectDeserializer; +use crate::{DynamicSet, Set, SetInfo, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Set`] values. +/// +/// [`Set`]: crate::Set +pub(super) struct SetVisitor<'a> { + set_info: &'static SetInfo, + registry: &'a TypeRegistry, +} + +impl<'a> SetVisitor<'a> { + pub fn new(set_info: &'static SetInfo, registry: &'a TypeRegistry) -> Self { + Self { set_info, registry } + } +} + +impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { + type Value = DynamicSet; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected set value") + } + + fn visit_seq(self, mut set: V) -> Result + where + V: SeqAccess<'de>, + { + let mut dynamic_set = DynamicSet::default(); + let value_registration = try_get_registration(self.set_info.value_ty(), self.registry)?; + while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new( + value_registration, + self.registry, + ))? { + dynamic_set.insert_boxed(value); + } + + Ok(dynamic_set) + } +} diff --git a/crates/bevy_reflect/src/serde/de/struct_utils.rs b/crates/bevy_reflect/src/serde/de/struct_utils.rs new file mode 100644 index 0000000000..5035746a60 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/struct_utils.rs @@ -0,0 +1,168 @@ +use crate::serde::de::helpers::{ExpectedValues, Ident}; +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::{SerializationData, TypedReflectDeserializer}; +use crate::{ + DynamicStruct, NamedField, StructInfo, StructVariantInfo, TypeRegistration, TypeRegistry, +}; +use core::slice::Iter; +use serde::de::{Error, MapAccess, SeqAccess}; + +/// A helper trait for accessing type information from struct-like types. +pub(super) trait StructLikeInfo { + fn field(&self, name: &str) -> Result<&NamedField, E>; + fn field_at(&self, index: usize) -> Result<&NamedField, E>; + fn field_len(&self) -> usize; + fn iter_fields(&self) -> Iter<'_, NamedField>; +} + +impl StructLikeInfo for StructInfo { + fn field(&self, name: &str) -> Result<&NamedField, E> { + Self::field(self, name).ok_or_else(|| { + Error::custom(format_args!( + "no field named {} on struct {}", + name, + self.type_path(), + )) + }) + } + + fn field_at(&self, index: usize) -> Result<&NamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + Error::custom(format_args!( + "no field at index {} on struct {}", + index, + self.type_path(), + )) + }) + } + + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn iter_fields(&self) -> Iter<'_, NamedField> { + self.iter() + } +} + +impl StructLikeInfo for StructVariantInfo { + fn field(&self, name: &str) -> Result<&NamedField, E> { + Self::field(self, name).ok_or_else(|| { + Error::custom(format_args!( + "no field named {} on variant {}", + name, + self.name(), + )) + }) + } + + fn field_at(&self, index: usize) -> Result<&NamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + Error::custom(format_args!( + "no field at index {} on variant {}", + index, + self.name(), + )) + }) + } + + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn iter_fields(&self) -> Iter<'_, NamedField> { + self.iter() + } +} + +/// Deserializes a [struct-like] type from a mapping of fields, returning a [`DynamicStruct`]. +/// +/// [struct-like]: StructLikeInfo +pub(super) fn visit_struct<'de, T, V>( + map: &mut V, + info: &'static T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: StructLikeInfo, + V: MapAccess<'de>, +{ + let mut dynamic_struct = DynamicStruct::default(); + while let Some(Ident(key)) = map.next_key::()? { + let field = info.field::(&key).map_err(|_| { + let fields = info.iter_fields().map(NamedField::name); + Error::custom(format_args!( + "unknown field `{}`, expected one of {:?}", + key, + ExpectedValues::from_iter(fields) + )) + })?; + let registration = try_get_registration(*field.ty(), registry)?; + let value = map.next_value_seed(TypedReflectDeserializer::new(registration, registry))?; + dynamic_struct.insert_boxed(&key, value); + } + + if let Some(serialization_data) = registration.data::() { + for (skipped_index, skipped_field) in serialization_data.iter_skipped() { + let Ok(field) = info.field_at::(*skipped_index) else { + continue; + }; + dynamic_struct.insert_boxed( + field.name(), + skipped_field.generate_default().into_partial_reflect(), + ); + } + } + + Ok(dynamic_struct) +} + +/// Deserializes a [struct-like] type from a sequence of fields, returning a [`DynamicStruct`]. +/// +/// [struct-like]: StructLikeInfo +pub(super) fn visit_struct_seq<'de, T, V>( + seq: &mut V, + info: &T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: StructLikeInfo, + V: SeqAccess<'de>, +{ + let mut dynamic_struct = DynamicStruct::default(); + + let len = info.field_len(); + + if len == 0 { + // Handle unit structs + return Ok(dynamic_struct); + } + + let serialization_data = registration.data::(); + + for index in 0..len { + let name = info.field_at::(index)?.name(); + + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or_default() + { + if let Some(value) = serialization_data.unwrap().generate_default(index) { + dynamic_struct.insert_boxed(name, value.into_partial_reflect()); + } + continue; + } + + let value = seq + .next_element_seed(TypedReflectDeserializer::new( + try_get_registration(*info.field_at(index)?.ty(), registry)?, + registry, + ))? + .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; + dynamic_struct.insert_boxed(name, value); + } + + Ok(dynamic_struct) +} diff --git a/crates/bevy_reflect/src/serde/de/structs.rs b/crates/bevy_reflect/src/serde/de/structs.rs new file mode 100644 index 0000000000..85aa6f15b3 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/structs.rs @@ -0,0 +1,50 @@ +use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; +use crate::{DynamicStruct, StructInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{MapAccess, SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Struct`] values. +/// +/// [`Struct`]: crate::Struct +pub(super) struct StructVisitor<'a> { + struct_info: &'static StructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> StructVisitor<'a> { + pub fn new( + struct_info: &'static StructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + struct_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for StructVisitor<'a> { + type Value = DynamicStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected struct value") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + visit_struct_seq(&mut seq, self.struct_info, self.registration, self.registry) + } + + fn visit_map(self, mut map: V) -> Result + where + V: MapAccess<'de>, + { + visit_struct(&mut map, self.struct_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/de/tuple_structs.rs b/crates/bevy_reflect/src/serde/de/tuple_structs.rs new file mode 100644 index 0000000000..8bbe44a324 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuple_structs.rs @@ -0,0 +1,49 @@ +use crate::serde::de::tuple_utils::visit_tuple; +use crate::{DynamicTupleStruct, TupleStructInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`TupleStruct`] values. +/// +/// [`TupleStruct`]: crate::TupleStruct +pub(super) struct TupleStructVisitor<'a> { + tuple_struct_info: &'static TupleStructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TupleStructVisitor<'a> { + pub fn new( + tuple_struct_info: &'static TupleStructInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + tuple_struct_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for TupleStructVisitor<'a> { + type Value = DynamicTupleStruct; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple struct value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple( + &mut seq, + self.tuple_struct_info, + self.registration, + self.registry, + ) + .map(DynamicTupleStruct::from) + } +} diff --git a/crates/bevy_reflect/src/serde/de/tuple_utils.rs b/crates/bevy_reflect/src/serde/de/tuple_utils.rs new file mode 100644 index 0000000000..542b096bf5 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuple_utils.rs @@ -0,0 +1,102 @@ +use crate::serde::de::registration_utils::try_get_registration; +use crate::serde::{SerializationData, TypedReflectDeserializer}; +use crate::{ + DynamicTuple, TupleInfo, TupleStructInfo, TupleVariantInfo, TypeRegistration, TypeRegistry, + UnnamedField, +}; +use serde::de::{Error, SeqAccess}; + +pub(super) trait TupleLikeInfo { + fn field_at(&self, index: usize) -> Result<&UnnamedField, E>; + fn field_len(&self) -> usize; +} + +impl TupleLikeInfo for TupleInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + Error::custom(format_args!( + "no field at index {} on tuple {}", + index, + self.type_path(), + )) + }) + } +} + +impl TupleLikeInfo for TupleStructInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + Error::custom(format_args!( + "no field at index {} on tuple struct {}", + index, + self.type_path(), + )) + }) + } +} + +impl TupleLikeInfo for TupleVariantInfo { + fn field_len(&self) -> usize { + Self::field_len(self) + } + + fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { + Self::field_at(self, index).ok_or_else(|| { + Error::custom(format_args!( + "no field at index {} on tuple variant {}", + index, + self.name(), + )) + }) + } +} + +/// Deserializes a [tuple-like] type from a sequence of elements, returning a [`DynamicTuple`]. +/// +/// [tuple-like]: TupleLikeInfo +pub(super) fn visit_tuple<'de, T, V>( + seq: &mut V, + info: &T, + registration: &TypeRegistration, + registry: &TypeRegistry, +) -> Result +where + T: TupleLikeInfo, + V: SeqAccess<'de>, +{ + let mut tuple = DynamicTuple::default(); + + let len = info.field_len(); + + if len == 0 { + // Handle empty tuple/tuple struct + return Ok(tuple); + } + + let serialization_data = registration.data::(); + + for index in 0..len { + if let Some(value) = serialization_data.and_then(|data| data.generate_default(index)) { + tuple.insert_boxed(value.into_partial_reflect()); + continue; + } + + let value = seq + .next_element_seed(TypedReflectDeserializer::new( + try_get_registration(*info.field_at(index)?.ty(), registry)?, + registry, + ))? + .ok_or_else(|| Error::invalid_length(index, &len.to_string().as_str()))?; + tuple.insert_boxed(value); + } + + Ok(tuple) +} diff --git a/crates/bevy_reflect/src/serde/de/tuples.rs b/crates/bevy_reflect/src/serde/de/tuples.rs new file mode 100644 index 0000000000..de18cbda61 --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/tuples.rs @@ -0,0 +1,43 @@ +use crate::serde::de::tuple_utils::visit_tuple; +use crate::{DynamicTuple, TupleInfo, TypeRegistration, TypeRegistry}; +use core::fmt::Formatter; +use serde::de::{SeqAccess, Visitor}; +use std::fmt; + +/// A [`Visitor`] for deserializing [`Tuple`] values. +/// +/// [`Tuple`]: crate::Tuple +pub(super) struct TupleVisitor<'a> { + tuple_info: &'static TupleInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, +} + +impl<'a> TupleVisitor<'a> { + pub fn new( + tuple_info: &'static TupleInfo, + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { + Self { + tuple_info, + registration, + registry, + } + } +} + +impl<'a, 'de> Visitor<'de> for TupleVisitor<'a> { + type Value = DynamicTuple; + + fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { + formatter.write_str("reflected tuple value") + } + + fn visit_seq(self, mut seq: V) -> Result + where + V: SeqAccess<'de>, + { + visit_tuple(&mut seq, self.tuple_info, self.registration, self.registry) + } +} diff --git a/crates/bevy_reflect/src/serde/ser.rs b/crates/bevy_reflect/src/serde/ser.rs deleted file mode 100644 index 8615171238..0000000000 --- a/crates/bevy_reflect/src/serde/ser.rs +++ /dev/null @@ -1,1012 +0,0 @@ -use crate::{ - Array, Enum, List, Map, PartialReflect, ReflectRef, ReflectSerialize, Set, Struct, Tuple, - TupleStruct, TypeInfo, TypeRegistry, VariantInfo, VariantType, -}; -use serde::ser::{ - Error, SerializeStruct, SerializeStructVariant, SerializeTuple, SerializeTupleStruct, - SerializeTupleVariant, -}; -use serde::{ - ser::{SerializeMap, SerializeSeq}, - Serialize, -}; - -use super::SerializationData; - -pub enum Serializable<'a> { - Owned(Box), - Borrowed(&'a dyn erased_serde::Serialize), -} - -impl<'a> Serializable<'a> { - #[allow(clippy::should_implement_trait)] - pub fn borrow(&self) -> &dyn erased_serde::Serialize { - match self { - Serializable::Borrowed(serialize) => serialize, - Serializable::Owned(serialize) => serialize, - } - } -} - -fn get_serializable<'a, E: Error>( - reflect_value: &'a dyn PartialReflect, - type_registry: &TypeRegistry, -) -> Result, E> { - let Some(reflect_value) = reflect_value.try_as_reflect() else { - return Err(Error::custom(format_args!( - "Type '{}' does not implement `Reflect`", - reflect_value.reflect_type_path() - ))); - }; - let info = reflect_value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "Type '{}' does not represent any type", - reflect_value.reflect_type_path(), - )) - })?; - - let registration = type_registry.get(info.type_id()).ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` is not registered in the type registry", - info.type_path(), - )) - })?; - - let reflect_serialize = registration.data::().ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", - info.type_path(), - )) - })?; - - Ok(reflect_serialize.get_serializable(reflect_value)) -} - -/// A general purpose serializer for reflected types. -/// -/// This is the serializer counterpart to [`ReflectDeserializer`]. -/// -/// See [`TypedReflectSerializer`] for a serializer that serializes a known type. -/// -/// # Output -/// -/// This serializer will output a map with a single entry, -/// where the key is the _full_ [type path] of the reflected type -/// and the value is the serialized data. -/// -/// # Example -/// -/// ``` -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{TypeRegistry, serde::ReflectSerializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = MyStruct { value: 123 }; -/// -/// let reflect_serializer = ReflectSerializer::new(&input, ®istry); -/// let output = ron::to_string(&reflect_serializer).unwrap(); -/// -/// assert_eq!(output, r#"{"my_crate::MyStruct":(value:123)}"#); -/// ``` -/// -/// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer -/// [type path]: crate::TypePath::type_path -pub struct ReflectSerializer<'a> { - pub value: &'a dyn PartialReflect, - pub registry: &'a TypeRegistry, -} - -impl<'a> ReflectSerializer<'a> { - pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - ReflectSerializer { value, registry } - } -} - -impl<'a> Serialize for ReflectSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(1))?; - state.serialize_entry( - self.value - .get_represented_type_info() - .ok_or_else(|| { - if self.value.is_dynamic() { - Error::custom(format_args!( - "cannot serialize dynamic value without represented type: {}", - self.value.reflect_type_path() - )) - } else { - Error::custom(format_args!( - "cannot get type info for {}", - self.value.reflect_type_path() - )) - } - })? - .type_path(), - &TypedReflectSerializer::new(self.value, self.registry), - )?; - state.end() - } -} - -/// A serializer for reflected types whose type will be known during deserialization. -/// -/// This is the serializer counterpart to [`TypedReflectDeserializer`]. -/// -/// See [`ReflectSerializer`] for a serializer that serializes an unknown type. -/// -/// # Output -/// -/// Since the type is expected to be known during deserialization, -/// this serializer will not output any additional type information, -/// such as the [type path]. -/// -/// Instead, it will output just the serialized data. -/// -/// # Example -/// -/// ``` -/// # use bevy_reflect::prelude::*; -/// # use bevy_reflect::{TypeRegistry, serde::TypedReflectSerializer}; -/// #[derive(Reflect, PartialEq, Debug)] -/// #[type_path = "my_crate"] -/// struct MyStruct { -/// value: i32 -/// } -/// -/// let mut registry = TypeRegistry::default(); -/// registry.register::(); -/// -/// let input = MyStruct { value: 123 }; -/// -/// let reflect_serializer = TypedReflectSerializer::new(&input, ®istry); -/// let output = ron::to_string(&reflect_serializer).unwrap(); -/// -/// assert_eq!(output, r#"(value:123)"#); -/// ``` -/// -/// [`TypedReflectDeserializer`]: crate::serde::TypedReflectDeserializer -/// [type path]: crate::TypePath::type_path -pub struct TypedReflectSerializer<'a> { - pub value: &'a dyn PartialReflect, - pub registry: &'a TypeRegistry, -} - -impl<'a> TypedReflectSerializer<'a> { - pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - TypedReflectSerializer { value, registry } - } -} - -impl<'a> Serialize for TypedReflectSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - // Handle both Value case and types that have a custom `Serialize` - let serializable = get_serializable::(self.value, self.registry); - if let Ok(serializable) = serializable { - return serializable.borrow().serialize(serializer); - } - - match self.value.reflect_ref() { - ReflectRef::Struct(value) => StructSerializer { - struct_value: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::TupleStruct(value) => TupleStructSerializer { - tuple_struct: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Tuple(value) => TupleSerializer { - tuple: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::List(value) => ListSerializer { - list: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Array(value) => ArraySerializer { - array: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Map(value) => MapSerializer { - map: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Set(value) => SetSerializer { - set: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Enum(value) => EnumSerializer { - enum_value: value, - registry: self.registry, - } - .serialize(serializer), - ReflectRef::Value(_) => Err(serializable.err().unwrap()), - } - } -} - -pub struct ReflectValueSerializer<'a> { - pub registry: &'a TypeRegistry, - pub value: &'a dyn PartialReflect, -} - -impl<'a> Serialize for ReflectValueSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - get_serializable::(self.value, self.registry)? - .borrow() - .serialize(serializer) - } -} - -pub struct StructSerializer<'a> { - pub struct_value: &'a dyn Struct, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for StructSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self - .struct_value - .get_represented_type_info() - .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.struct_value.reflect_type_path() - )) - })?; - - let struct_info = match type_info { - TypeInfo::Struct(struct_info) => struct_info, - info => { - return Err(Error::custom(format_args!( - "expected struct type but received {info:?}" - ))); - } - }; - - let serialization_data = self - .registry - .get(type_info.type_id()) - .and_then(|registration| registration.data::()); - let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); - let mut state = serializer.serialize_struct( - struct_info.type_path_table().ident().unwrap(), - self.struct_value.field_len() - ignored_len, - )?; - - for (index, value) in self.struct_value.iter_fields().enumerate() { - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or(false) - { - continue; - } - let key = struct_info.field_at(index).unwrap().name(); - state.serialize_field(key, &TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct TupleStructSerializer<'a> { - pub tuple_struct: &'a dyn TupleStruct, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for TupleStructSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self - .tuple_struct - .get_represented_type_info() - .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.tuple_struct.reflect_type_path() - )) - })?; - - let tuple_struct_info = match type_info { - TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, - info => { - return Err(Error::custom(format_args!( - "expected tuple struct type but received {info:?}" - ))); - } - }; - - let serialization_data = self - .registry - .get(type_info.type_id()) - .and_then(|registration| registration.data::()); - let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); - let mut state = serializer.serialize_tuple_struct( - tuple_struct_info.type_path_table().ident().unwrap(), - self.tuple_struct.field_len() - ignored_len, - )?; - - for (index, value) in self.tuple_struct.iter_fields().enumerate() { - if serialization_data - .map(|data| data.is_field_skipped(index)) - .unwrap_or(false) - { - continue; - } - state.serialize_field(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct EnumSerializer<'a> { - pub enum_value: &'a dyn Enum, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for EnumSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", - self.enum_value.reflect_type_path() - )) - })?; - - let enum_info = match type_info { - TypeInfo::Enum(enum_info) => enum_info, - info => { - return Err(Error::custom(format_args!( - "expected enum type but received {info:?}" - ))); - } - }; - - let enum_name = enum_info.type_path_table().ident().unwrap(); - let variant_index = self.enum_value.variant_index() as u32; - let variant_info = enum_info - .variant_at(variant_index as usize) - .ok_or_else(|| { - Error::custom(format_args!( - "variant at index `{variant_index}` does not exist", - )) - })?; - let variant_name = variant_info.name(); - let variant_type = self.enum_value.variant_type(); - let field_len = self.enum_value.field_len(); - - match variant_type { - VariantType::Unit => { - if type_info.type_path_table().module_path() == Some("core::option") - && type_info.type_path_table().ident() == Some("Option") - { - serializer.serialize_none() - } else { - serializer.serialize_unit_variant(enum_name, variant_index, variant_name) - } - } - VariantType::Struct => { - let struct_info = match variant_info { - VariantInfo::Struct(struct_info) => struct_info, - info => { - return Err(Error::custom(format_args!( - "expected struct variant type but received {info:?}", - ))); - } - }; - - let mut state = serializer.serialize_struct_variant( - enum_name, - variant_index, - variant_name, - field_len, - )?; - for (index, field) in self.enum_value.iter_fields().enumerate() { - let field_info = struct_info.field_at(index).unwrap(); - state.serialize_field( - field_info.name(), - &TypedReflectSerializer::new(field.value(), self.registry), - )?; - } - state.end() - } - VariantType::Tuple if field_len == 1 => { - let field = self.enum_value.field_at(0).unwrap(); - - if type_info.type_path_table().module_path() == Some("core::option") - && type_info.type_path_table().ident() == Some("Option") - { - serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry)) - } else { - serializer.serialize_newtype_variant( - enum_name, - variant_index, - variant_name, - &TypedReflectSerializer::new(field, self.registry), - ) - } - } - VariantType::Tuple => { - let mut state = serializer.serialize_tuple_variant( - enum_name, - variant_index, - variant_name, - field_len, - )?; - for field in self.enum_value.iter_fields() { - state.serialize_field(&TypedReflectSerializer::new( - field.value(), - self.registry, - ))?; - } - state.end() - } - } - } -} - -pub struct TupleSerializer<'a> { - pub tuple: &'a dyn Tuple, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for TupleSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_tuple(self.tuple.field_len())?; - - for value in self.tuple.iter_fields() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct MapSerializer<'a> { - pub map: &'a dyn Map, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for MapSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_map(Some(self.map.len()))?; - for (key, value) in self.map.iter() { - state.serialize_entry( - &TypedReflectSerializer::new(key, self.registry), - &TypedReflectSerializer::new(value, self.registry), - )?; - } - state.end() - } -} - -pub struct SetSerializer<'a> { - pub set: &'a dyn Set, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for SetSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_seq(Some(self.set.len()))?; - for value in self.set.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct ListSerializer<'a> { - pub list: &'a dyn List, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ListSerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_seq(Some(self.list.len()))?; - for value in self.list.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -pub struct ArraySerializer<'a> { - pub array: &'a dyn Array, - pub registry: &'a TypeRegistry, -} - -impl<'a> Serialize for ArraySerializer<'a> { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut state = serializer.serialize_tuple(self.array.len())?; - for value in self.array.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; - } - state.end() - } -} - -#[cfg(test)] -mod tests { - use crate::serde::ReflectSerializer; - use crate::{self as bevy_reflect, PartialReflect, Struct}; - use crate::{Reflect, ReflectSerialize, TypeRegistry}; - use bevy_utils::{HashMap, HashSet}; - use ron::extensions::Extensions; - use ron::ser::PrettyConfig; - use serde::Serialize; - use std::f32::consts::PI; - use std::ops::RangeInclusive; - - #[derive(Reflect, Debug, PartialEq)] - struct MyStruct { - primitive_value: i8, - option_value: Option, - option_value_complex: Option, - tuple_value: (f32, usize), - list_value: Vec, - array_value: [i32; 5], - map_value: HashMap, - set_value: HashSet, - struct_value: SomeStruct, - tuple_struct_value: SomeTupleStruct, - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum, - newtype_enum: SomeEnum, - tuple_enum: SomeEnum, - struct_enum: SomeEnum, - ignored_struct: SomeIgnoredStruct, - ignored_tuple_struct: SomeIgnoredTupleStruct, - ignored_struct_variant: SomeIgnoredEnum, - ignored_tuple_variant: SomeIgnoredEnum, - custom_serialize: CustomSerialize, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeStruct { - foo: i64, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeTupleStruct(String); - - #[derive(Reflect, Debug, PartialEq)] - struct SomeUnitStruct; - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredStruct { - #[reflect(ignore)] - ignored: i32, - } - - #[derive(Reflect, Debug, PartialEq)] - struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); - - #[derive(Reflect, Debug, PartialEq)] - enum SomeEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { foo: String }, - } - - #[derive(Reflect, Debug, PartialEq)] - enum SomeIgnoredEnum { - Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), - Struct { - #[reflect(ignore)] - foo: String, - }, - } - - #[derive(Reflect, Debug, PartialEq, Serialize)] - struct SomeSerializableStruct { - foo: i64, - } - - /// Implements a custom serialize using `#[reflect(Serialize)]`. - /// - /// For testing purposes, this just uses the generated one from deriving Serialize. - #[derive(Reflect, Debug, PartialEq, Serialize)] - #[reflect(Serialize)] - struct CustomSerialize { - value: usize, - #[serde(rename = "renamed")] - inner_struct: SomeSerializableStruct, - } - - fn get_registry() -> TypeRegistry { - let mut registry = TypeRegistry::default(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register::(); - registry.register_type_data::(); - registry.register::(); - registry.register::>(); - registry.register_type_data::, ReflectSerialize>(); - registry - } - - fn get_my_struct() -> MyStruct { - let mut map = HashMap::new(); - map.insert(64, 32); - - let mut set = HashSet::new(); - set.insert(64); - - MyStruct { - primitive_value: 123, - option_value: Some(String::from("Hello world!")), - option_value_complex: Some(SomeStruct { foo: 123 }), - tuple_value: (PI, 1337), - list_value: vec![-2, -1, 0, 1, 2], - array_value: [-2, -1, 0, 1, 2], - map_value: map, - set_value: set, - struct_value: SomeStruct { foo: 999999999 }, - tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), - unit_struct: SomeUnitStruct, - unit_enum: SomeEnum::Unit, - newtype_enum: SomeEnum::NewType(123), - tuple_enum: SomeEnum::Tuple(1.23, 3.21), - struct_enum: SomeEnum::Struct { - foo: String::from("Struct variant value"), - }, - ignored_struct: SomeIgnoredStruct { ignored: 123 }, - ignored_tuple_struct: SomeIgnoredTupleStruct(123), - ignored_struct_variant: SomeIgnoredEnum::Struct { - foo: String::from("Struct Variant"), - }, - ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), - custom_serialize: CustomSerialize { - value: 100, - inner_struct: SomeSerializableStruct { foo: 101 }, - }, - } - } - - #[test] - fn should_serialize() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyStruct": ( - primitive_value: 123, - option_value: Some("Hello world!"), - option_value_complex: Some(( - foo: 123, - )), - tuple_value: (3.1415927, 1337), - list_value: [ - -2, - -1, - 0, - 1, - 2, - ], - array_value: (-2, -1, 0, 1, 2), - map_value: { - 64: 32, - }, - set_value: [ - 64, - ], - struct_value: ( - foo: 999999999, - ), - tuple_struct_value: ("Tuple Struct"), - unit_struct: (), - unit_enum: Unit, - newtype_enum: NewType(123), - tuple_enum: Tuple(1.23, 3.21), - struct_enum: Struct( - foo: "Struct variant value", - ), - ignored_struct: (), - ignored_tuple_struct: (), - ignored_struct_variant: Struct(), - ignored_tuple_variant: Tuple(), - custom_serialize: ( - value: 100, - renamed: ( - foo: 101, - ), - ), - ), -}"#; - assert_eq!(expected, output); - } - - #[test] - fn should_serialize_option() { - #[derive(Reflect, Debug, PartialEq)] - struct OptionTest { - none: Option<()>, - simple: Option, - complex: Option, - } - - let value = OptionTest { - none: None, - simple: Some(String::from("Hello world!")), - complex: Some(SomeStruct { foo: 123 }), - }; - - let registry = get_registry(); - let serializer = ReflectSerializer::new(&value, ®istry); - - // === Normal === // - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::OptionTest": ( - none: None, - simple: Some("Hello world!"), - complex: Some(( - foo: 123, - )), - ), -}"#; - - assert_eq!(expected, output); - - // === Implicit Some === // - let config = PrettyConfig::default() - .new_line(String::from("\n")) - .extensions(Extensions::IMPLICIT_SOME) - .indentor(String::from(" ")); - - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"#![enable(implicit_some)] -{ - "bevy_reflect::serde::ser::tests::OptionTest": ( - none: None, - simple: "Hello world!", - complex: ( - foo: 123, - ), - ), -}"#; - - assert_eq!(expected, output); - } - - #[test] - fn enum_should_serialize() { - #[derive(Reflect)] - enum MyEnum { - Unit, - NewType(usize), - Tuple(f32, f32), - Struct { value: String }, - } - - let mut registry = get_registry(); - registry.register::(); - - let config = PrettyConfig::default().new_line(String::from("\n")); - - // === Unit Variant === // - let value = MyEnum::Unit; - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Unit, -}"#; - assert_eq!(expected, output); - - // === NewType Variant === // - let value = MyEnum::NewType(123); - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": NewType(123), -}"#; - assert_eq!(expected, output); - - // === Tuple Variant === // - let value = MyEnum::Tuple(1.23, 3.21); - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Tuple(1.23, 3.21), -}"#; - assert_eq!(expected, output); - - // === Struct Variant === // - let value = MyEnum::Struct { - value: String::from("I <3 Enums"), - }; - let serializer = ReflectSerializer::new(&value, ®istry); - let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::MyEnum": Struct( - value: "I <3 Enums", - ), -}"#; - assert_eq!(expected, output); - } - - #[test] - fn should_serialize_non_self_describing_binary() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - let bytes = bincode::serialize(&serializer).unwrap(); - - let expected: Vec = vec![ - 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, - 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, - 101, 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, - 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, - 0, 0, 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, - 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, - 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, - 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, - 0, 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, - 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, - 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, - 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, - 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, - ]; - - assert_eq!(expected, bytes); - } - - #[test] - fn should_serialize_self_describing_binary() { - let input = get_my_struct(); - let registry = get_registry(); - - let serializer = ReflectSerializer::new(&input, ®istry); - let bytes: Vec = rmp_serde::to_vec(&serializer).unwrap(); - - let expected: Vec = vec![ - 129, 217, 41, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, - 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, - 121, 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, - 111, 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, - 0, 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, - 145, 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, - 105, 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, - 101, 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, - 99, 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, - 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, - 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, - ]; - - assert_eq!(expected, bytes); - } - - #[test] - fn should_serialize_dynamic_option() { - #[derive(Default, Reflect)] - struct OtherStruct { - some: Option, - none: Option, - } - - let value = OtherStruct { - some: Some(SomeStruct { foo: 999999999 }), - none: None, - }; - let dynamic = value.clone_dynamic(); - let reflect = dynamic.as_partial_reflect(); - - let registry = get_registry(); - - let serializer = ReflectSerializer::new(reflect, ®istry); - - let mut buf = Vec::new(); - - let format = serde_json::ser::PrettyFormatter::with_indent(b" "); - let mut ser = serde_json::Serializer::with_formatter(&mut buf, format); - - serializer.serialize(&mut ser).unwrap(); - - let output = std::str::from_utf8(&buf).unwrap(); - let expected = r#"{ - "bevy_reflect::serde::ser::tests::OtherStruct": { - "some": { - "foo": 999999999 - }, - "none": null - } -}"#; - - assert_eq!(expected, output); - } - - #[test] - fn should_return_error_if_missing_registration() { - let value = RangeInclusive::::new(0.0, 1.0); - let registry = TypeRegistry::new(); - - let serializer = ReflectSerializer::new(&value, ®istry); - let error = ron::ser::to_string(&serializer).unwrap_err(); - assert_eq!( - error, - ron::Error::Message( - "Type `core::ops::RangeInclusive` is not registered in the type registry" - .to_string() - ) - ); - } - - #[test] - fn should_return_error_if_missing_type_data() { - let value = RangeInclusive::::new(0.0, 1.0); - let mut registry = TypeRegistry::new(); - registry.register::>(); - - let serializer = ReflectSerializer::new(&value, ®istry); - let error = ron::ser::to_string(&serializer).unwrap_err(); - assert_eq!( - error, - ron::Error::Message( - "Type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() - ) - ); - } -} diff --git a/crates/bevy_reflect/src/serde/ser/arrays.rs b/crates/bevy_reflect/src/serde/ser/arrays.rs new file mode 100644 index 0000000000..eb84919dfd --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/arrays.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Array, TypeRegistry}; +use serde::ser::SerializeTuple; +use serde::Serialize; + +/// A serializer for [`Array`] values. +pub(super) struct ArraySerializer<'a> { + array: &'a dyn Array, + registry: &'a TypeRegistry, +} + +impl<'a> ArraySerializer<'a> { + pub fn new(array: &'a dyn Array, registry: &'a TypeRegistry) -> Self { + Self { array, registry } + } +} + +impl<'a> Serialize for ArraySerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_tuple(self.array.len())?; + for value in self.array.iter() { + state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/enums.rs b/crates/bevy_reflect/src/serde/ser/enums.rs new file mode 100644 index 0000000000..da151cc894 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/enums.rs @@ -0,0 +1,123 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Enum, TypeInfo, TypeRegistry, VariantInfo, VariantType}; +use serde::ser::{Error, SerializeStructVariant, SerializeTupleVariant}; +use serde::Serialize; + +/// A serializer for [`Enum`] values. +pub(super) struct EnumSerializer<'a> { + enum_value: &'a dyn Enum, + registry: &'a TypeRegistry, +} + +impl<'a> EnumSerializer<'a> { + pub fn new(enum_value: &'a dyn Enum, registry: &'a TypeRegistry) -> Self { + Self { + enum_value, + registry, + } + } +} + +impl<'a> Serialize for EnumSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.enum_value.reflect_type_path() + )) + })?; + + let enum_info = match type_info { + TypeInfo::Enum(enum_info) => enum_info, + info => { + return Err(Error::custom(format_args!( + "expected enum type but received {info:?}" + ))); + } + }; + + let enum_name = enum_info.type_path_table().ident().unwrap(); + let variant_index = self.enum_value.variant_index() as u32; + let variant_info = enum_info + .variant_at(variant_index as usize) + .ok_or_else(|| { + Error::custom(format_args!( + "variant at index `{variant_index}` does not exist", + )) + })?; + let variant_name = variant_info.name(); + let variant_type = self.enum_value.variant_type(); + let field_len = self.enum_value.field_len(); + + match variant_type { + VariantType::Unit => { + if type_info.type_path_table().module_path() == Some("core::option") + && type_info.type_path_table().ident() == Some("Option") + { + serializer.serialize_none() + } else { + serializer.serialize_unit_variant(enum_name, variant_index, variant_name) + } + } + VariantType::Struct => { + let struct_info = match variant_info { + VariantInfo::Struct(struct_info) => struct_info, + info => { + return Err(Error::custom(format_args!( + "expected struct variant type but received {info:?}", + ))); + } + }; + + let mut state = serializer.serialize_struct_variant( + enum_name, + variant_index, + variant_name, + field_len, + )?; + for (index, field) in self.enum_value.iter_fields().enumerate() { + let field_info = struct_info.field_at(index).unwrap(); + state.serialize_field( + field_info.name(), + &TypedReflectSerializer::new(field.value(), self.registry), + )?; + } + state.end() + } + VariantType::Tuple if field_len == 1 => { + let field = self.enum_value.field_at(0).unwrap(); + + if type_info.type_path_table().module_path() == Some("core::option") + && type_info.type_path_table().ident() == Some("Option") + { + serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry)) + } else { + serializer.serialize_newtype_variant( + enum_name, + variant_index, + variant_name, + &TypedReflectSerializer::new(field, self.registry), + ) + } + } + VariantType::Tuple => { + let mut state = serializer.serialize_tuple_variant( + enum_name, + variant_index, + variant_name, + field_len, + )?; + for field in self.enum_value.iter_fields() { + state.serialize_field(&TypedReflectSerializer::new( + field.value(), + self.registry, + ))?; + } + state.end() + } + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/lists.rs b/crates/bevy_reflect/src/serde/ser/lists.rs new file mode 100644 index 0000000000..6e6b98958b --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/lists.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{List, TypeRegistry}; +use serde::ser::SerializeSeq; +use serde::Serialize; + +/// A serializer for [`List`] values. +pub(super) struct ListSerializer<'a> { + list: &'a dyn List, + registry: &'a TypeRegistry, +} + +impl<'a> ListSerializer<'a> { + pub fn new(list: &'a dyn List, registry: &'a TypeRegistry) -> Self { + Self { list, registry } + } +} + +impl<'a> Serialize for ListSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.list.len()))?; + for value in self.list.iter() { + state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/maps.rs b/crates/bevy_reflect/src/serde/ser/maps.rs new file mode 100644 index 0000000000..589cfae3d1 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/maps.rs @@ -0,0 +1,32 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Map, TypeRegistry}; +use serde::ser::SerializeMap; +use serde::Serialize; + +/// A serializer for [`Map`] values. +pub(super) struct MapSerializer<'a> { + map: &'a dyn Map, + registry: &'a TypeRegistry, +} + +impl<'a> MapSerializer<'a> { + pub fn new(map: &'a dyn Map, registry: &'a TypeRegistry) -> Self { + Self { map, registry } + } +} + +impl<'a> Serialize for MapSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(self.map.len()))?; + for (key, value) in self.map.iter() { + state.serialize_entry( + &TypedReflectSerializer::new(key, self.registry), + &TypedReflectSerializer::new(value, self.registry), + )?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs new file mode 100644 index 0000000000..85876fabf2 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -0,0 +1,456 @@ +pub use serializable::*; +pub use serializer::*; + +mod arrays; +mod enums; +mod lists; +mod maps; +mod serializable; +mod serializer; +mod sets; +mod structs; +mod tuple_structs; +mod tuples; + +#[cfg(test)] +mod tests { + use crate::serde::ReflectSerializer; + use crate::{self as bevy_reflect, PartialReflect, Struct}; + use crate::{Reflect, ReflectSerialize, TypeRegistry}; + use bevy_utils::{HashMap, HashSet}; + use ron::extensions::Extensions; + use ron::ser::PrettyConfig; + use serde::Serialize; + use std::f32::consts::PI; + use std::ops::RangeInclusive; + + #[derive(Reflect, Debug, PartialEq)] + struct MyStruct { + primitive_value: i8, + option_value: Option, + option_value_complex: Option, + tuple_value: (f32, usize), + list_value: Vec, + array_value: [i32; 5], + map_value: HashMap, + set_value: HashSet, + struct_value: SomeStruct, + tuple_struct_value: SomeTupleStruct, + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum, + newtype_enum: SomeEnum, + tuple_enum: SomeEnum, + struct_enum: SomeEnum, + ignored_struct: SomeIgnoredStruct, + ignored_tuple_struct: SomeIgnoredTupleStruct, + ignored_struct_variant: SomeIgnoredEnum, + ignored_tuple_variant: SomeIgnoredEnum, + custom_serialize: CustomSerialize, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeStruct { + foo: i64, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeTupleStruct(String); + + #[derive(Reflect, Debug, PartialEq)] + struct SomeUnitStruct; + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredStruct { + #[reflect(ignore)] + ignored: i32, + } + + #[derive(Reflect, Debug, PartialEq)] + struct SomeIgnoredTupleStruct(#[reflect(ignore)] i32); + + #[derive(Reflect, Debug, PartialEq)] + enum SomeEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { foo: String }, + } + + #[derive(Reflect, Debug, PartialEq)] + enum SomeIgnoredEnum { + Tuple(#[reflect(ignore)] f32, #[reflect(ignore)] f32), + Struct { + #[reflect(ignore)] + foo: String, + }, + } + + #[derive(Reflect, Debug, PartialEq, Serialize)] + struct SomeSerializableStruct { + foo: i64, + } + + /// Implements a custom serialize using `#[reflect(Serialize)]`. + /// + /// For testing purposes, this just uses the generated one from deriving Serialize. + #[derive(Reflect, Debug, PartialEq, Serialize)] + #[reflect(Serialize)] + struct CustomSerialize { + value: usize, + #[serde(rename = "renamed")] + inner_struct: SomeSerializableStruct, + } + + fn get_registry() -> TypeRegistry { + let mut registry = TypeRegistry::default(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register_type_data::(); + registry.register::(); + registry.register::>(); + registry.register_type_data::, ReflectSerialize>(); + registry + } + + fn get_my_struct() -> MyStruct { + let mut map = HashMap::new(); + map.insert(64, 32); + + let mut set = HashSet::new(); + set.insert(64); + + MyStruct { + primitive_value: 123, + option_value: Some(String::from("Hello world!")), + option_value_complex: Some(SomeStruct { foo: 123 }), + tuple_value: (PI, 1337), + list_value: vec![-2, -1, 0, 1, 2], + array_value: [-2, -1, 0, 1, 2], + map_value: map, + set_value: set, + struct_value: SomeStruct { foo: 999999999 }, + tuple_struct_value: SomeTupleStruct(String::from("Tuple Struct")), + unit_struct: SomeUnitStruct, + unit_enum: SomeEnum::Unit, + newtype_enum: SomeEnum::NewType(123), + tuple_enum: SomeEnum::Tuple(1.23, 3.21), + struct_enum: SomeEnum::Struct { + foo: String::from("Struct variant value"), + }, + ignored_struct: SomeIgnoredStruct { ignored: 123 }, + ignored_tuple_struct: SomeIgnoredTupleStruct(123), + ignored_struct_variant: SomeIgnoredEnum::Struct { + foo: String::from("Struct Variant"), + }, + ignored_tuple_variant: SomeIgnoredEnum::Tuple(1.23, 3.45), + custom_serialize: CustomSerialize { + value: 100, + inner_struct: SomeSerializableStruct { foo: 101 }, + }, + } + } + + #[test] + fn should_serialize() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyStruct": ( + primitive_value: 123, + option_value: Some("Hello world!"), + option_value_complex: Some(( + foo: 123, + )), + tuple_value: (3.1415927, 1337), + list_value: [ + -2, + -1, + 0, + 1, + 2, + ], + array_value: (-2, -1, 0, 1, 2), + map_value: { + 64: 32, + }, + set_value: [ + 64, + ], + struct_value: ( + foo: 999999999, + ), + tuple_struct_value: ("Tuple Struct"), + unit_struct: (), + unit_enum: Unit, + newtype_enum: NewType(123), + tuple_enum: Tuple(1.23, 3.21), + struct_enum: Struct( + foo: "Struct variant value", + ), + ignored_struct: (), + ignored_tuple_struct: (), + ignored_struct_variant: Struct(), + ignored_tuple_variant: Tuple(), + custom_serialize: ( + value: 100, + renamed: ( + foo: 101, + ), + ), + ), +}"#; + assert_eq!(expected, output); + } + + #[test] + fn should_serialize_option() { + #[derive(Reflect, Debug, PartialEq)] + struct OptionTest { + none: Option<()>, + simple: Option, + complex: Option, + } + + let value = OptionTest { + none: None, + simple: Some(String::from("Hello world!")), + complex: Some(SomeStruct { foo: 123 }), + }; + + let registry = get_registry(); + let serializer = ReflectSerializer::new(&value, ®istry); + + // === Normal === // + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::OptionTest": ( + none: None, + simple: Some("Hello world!"), + complex: Some(( + foo: 123, + )), + ), +}"#; + + assert_eq!(expected, output); + + // === Implicit Some === // + let config = PrettyConfig::default() + .new_line(String::from("\n")) + .extensions(Extensions::IMPLICIT_SOME) + .indentor(String::from(" ")); + + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"#![enable(implicit_some)] +{ + "bevy_reflect::serde::ser::tests::OptionTest": ( + none: None, + simple: "Hello world!", + complex: ( + foo: 123, + ), + ), +}"#; + + assert_eq!(expected, output); + } + + #[test] + fn enum_should_serialize() { + #[derive(Reflect)] + enum MyEnum { + Unit, + NewType(usize), + Tuple(f32, f32), + Struct { value: String }, + } + + let mut registry = get_registry(); + registry.register::(); + + let config = PrettyConfig::default().new_line(String::from("\n")); + + // === Unit Variant === // + let value = MyEnum::Unit; + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Unit, +}"#; + assert_eq!(expected, output); + + // === NewType Variant === // + let value = MyEnum::NewType(123); + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": NewType(123), +}"#; + assert_eq!(expected, output); + + // === Tuple Variant === // + let value = MyEnum::Tuple(1.23, 3.21); + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config.clone()).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Tuple(1.23, 3.21), +}"#; + assert_eq!(expected, output); + + // === Struct Variant === // + let value = MyEnum::Struct { + value: String::from("I <3 Enums"), + }; + let serializer = ReflectSerializer::new(&value, ®istry); + let output = ron::ser::to_string_pretty(&serializer, config).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::MyEnum": Struct( + value: "I <3 Enums", + ), +}"#; + assert_eq!(expected, output); + } + + #[test] + fn should_serialize_non_self_describing_binary() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + let bytes = bincode::serialize(&serializer).unwrap(); + + let expected: Vec = vec![ + 1, 0, 0, 0, 0, 0, 0, 0, 41, 0, 0, 0, 0, 0, 0, 0, 98, 101, 118, 121, 95, 114, 101, 102, + 108, 101, 99, 116, 58, 58, 115, 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, + 101, 115, 116, 115, 58, 58, 77, 121, 83, 116, 114, 117, 99, 116, 123, 1, 12, 0, 0, 0, + 0, 0, 0, 0, 72, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100, 33, 1, 123, 0, 0, 0, + 0, 0, 0, 0, 219, 15, 73, 64, 57, 5, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 254, 255, + 255, 255, 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 254, 255, 255, 255, + 255, 255, 255, 255, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 32, + 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 64, 255, 201, 154, 59, 0, 0, 0, 0, 12, 0, + 0, 0, 0, 0, 0, 0, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 0, 0, 0, 0, + 1, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 164, 112, 157, 63, 164, 112, 77, 64, + 3, 0, 0, 0, 20, 0, 0, 0, 0, 0, 0, 0, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, + 97, 110, 116, 32, 118, 97, 108, 117, 101, 1, 0, 0, 0, 0, 0, 0, 0, 100, 0, 0, 0, 0, 0, + 0, 0, 101, 0, 0, 0, 0, 0, 0, 0, + ]; + + assert_eq!(expected, bytes); + } + + #[test] + fn should_serialize_self_describing_binary() { + let input = get_my_struct(); + let registry = get_registry(); + + let serializer = ReflectSerializer::new(&input, ®istry); + let bytes: Vec = rmp_serde::to_vec(&serializer).unwrap(); + + let expected: Vec = vec![ + 129, 217, 41, 98, 101, 118, 121, 95, 114, 101, 102, 108, 101, 99, 116, 58, 58, 115, + 101, 114, 100, 101, 58, 58, 115, 101, 114, 58, 58, 116, 101, 115, 116, 115, 58, 58, 77, + 121, 83, 116, 114, 117, 99, 116, 220, 0, 20, 123, 172, 72, 101, 108, 108, 111, 32, 119, + 111, 114, 108, 100, 33, 145, 123, 146, 202, 64, 73, 15, 219, 205, 5, 57, 149, 254, 255, + 0, 1, 2, 149, 254, 255, 0, 1, 2, 129, 64, 32, 145, 64, 145, 206, 59, 154, 201, 255, + 145, 172, 84, 117, 112, 108, 101, 32, 83, 116, 114, 117, 99, 116, 144, 164, 85, 110, + 105, 116, 129, 167, 78, 101, 119, 84, 121, 112, 101, 123, 129, 165, 84, 117, 112, 108, + 101, 146, 202, 63, 157, 112, 164, 202, 64, 77, 112, 164, 129, 166, 83, 116, 114, 117, + 99, 116, 145, 180, 83, 116, 114, 117, 99, 116, 32, 118, 97, 114, 105, 97, 110, 116, 32, + 118, 97, 108, 117, 101, 144, 144, 129, 166, 83, 116, 114, 117, 99, 116, 144, 129, 165, + 84, 117, 112, 108, 101, 144, 146, 100, 145, 101, + ]; + + assert_eq!(expected, bytes); + } + + #[test] + fn should_serialize_dynamic_option() { + #[derive(Default, Reflect)] + struct OtherStruct { + some: Option, + none: Option, + } + + let value = OtherStruct { + some: Some(SomeStruct { foo: 999999999 }), + none: None, + }; + let dynamic = value.clone_dynamic(); + let reflect = dynamic.as_partial_reflect(); + + let registry = get_registry(); + + let serializer = ReflectSerializer::new(reflect, ®istry); + + let mut buf = Vec::new(); + + let format = serde_json::ser::PrettyFormatter::with_indent(b" "); + let mut ser = serde_json::Serializer::with_formatter(&mut buf, format); + + serializer.serialize(&mut ser).unwrap(); + + let output = std::str::from_utf8(&buf).unwrap(); + let expected = r#"{ + "bevy_reflect::serde::ser::tests::OtherStruct": { + "some": { + "foo": 999999999 + }, + "none": null + } +}"#; + + assert_eq!(expected, output); + } + + #[test] + fn should_return_error_if_missing_registration() { + let value = RangeInclusive::::new(0.0, 1.0); + let registry = TypeRegistry::new(); + + let serializer = ReflectSerializer::new(&value, ®istry); + let error = ron::ser::to_string(&serializer).unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "Type `core::ops::RangeInclusive` is not registered in the type registry" + .to_string() + ) + ); + } + + #[test] + fn should_return_error_if_missing_type_data() { + let value = RangeInclusive::::new(0.0, 1.0); + let mut registry = TypeRegistry::new(); + registry.register::>(); + + let serializer = ReflectSerializer::new(&value, ®istry); + let error = ron::ser::to_string(&serializer).unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "Type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() + ) + ); + } +} diff --git a/crates/bevy_reflect/src/serde/ser/serializable.rs b/crates/bevy_reflect/src/serde/ser/serializable.rs new file mode 100644 index 0000000000..9c285816f2 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/serializable.rs @@ -0,0 +1,66 @@ +use crate::{PartialReflect, ReflectSerialize, TypeRegistry}; +use serde::ser::Error; +use std::ops::Deref; + +/// A type-erased serializable value. +pub enum Serializable<'a> { + Owned(Box), + Borrowed(&'a dyn erased_serde::Serialize), +} + +impl<'a> Serializable<'a> { + /// Attempts to create a [`Serializable`] from a [`PartialReflect`] value. + /// + /// Returns an error if any of the following conditions are met: + /// - The underlying type of `value` does not implement [`Reflect`]. + /// - The underlying type of `value` does not represent any type (via [`PartialReflect::get_represented_type_info`]). + /// - The represented type of `value` is not registered in the `type_registry`. + /// - The represented type of `value` did not register the [`ReflectSerialize`] type data. + /// + /// [`Reflect`]: crate::Reflect + pub fn try_from_reflect_value( + value: &'a dyn PartialReflect, + type_registry: &TypeRegistry, + ) -> Result, E> { + let value = value.try_as_reflect().ok_or_else(|| { + Error::custom(format_args!( + "Type '{}' does not implement `Reflect`", + value.reflect_type_path() + )) + })?; + + let info = value.get_represented_type_info().ok_or_else(|| { + Error::custom(format_args!( + "Type '{}' does not represent any type", + value.reflect_type_path(), + )) + })?; + + let registration = type_registry.get(info.type_id()).ok_or_else(|| { + Error::custom(format_args!( + "Type `{}` is not registered in the type registry", + info.type_path(), + )) + })?; + + let reflect_serialize = registration.data::().ok_or_else(|| { + Error::custom(format_args!( + "Type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + info.type_path(), + )) + })?; + + Ok(reflect_serialize.get_serializable(value)) + } +} + +impl<'a> Deref for Serializable<'a> { + type Target = dyn erased_serde::Serialize + 'a; + + fn deref(&self) -> &Self::Target { + match self { + Serializable::Borrowed(serialize) => serialize, + Serializable::Owned(serialize) => serialize, + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/serializer.rs b/crates/bevy_reflect/src/serde/ser/serializer.rs new file mode 100644 index 0000000000..eb3afe96f8 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/serializer.rs @@ -0,0 +1,179 @@ +use crate::serde::ser::arrays::ArraySerializer; +use crate::serde::ser::enums::EnumSerializer; +use crate::serde::ser::lists::ListSerializer; +use crate::serde::ser::maps::MapSerializer; +use crate::serde::ser::sets::SetSerializer; +use crate::serde::ser::structs::StructSerializer; +use crate::serde::ser::tuple_structs::TupleStructSerializer; +use crate::serde::ser::tuples::TupleSerializer; +use crate::serde::Serializable; +use crate::{PartialReflect, ReflectRef, TypeRegistry}; +use serde::ser::{Error, SerializeMap}; +use serde::Serialize; + +/// A general purpose serializer for reflected types. +/// +/// This is the serializer counterpart to [`ReflectDeserializer`]. +/// +/// See [`TypedReflectSerializer`] for a serializer that serializes a known type. +/// +/// # Output +/// +/// This serializer will output a map with a single entry, +/// where the key is the _full_ [type path] of the reflected type +/// and the value is the serialized data. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{TypeRegistry, serde::ReflectSerializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = MyStruct { value: 123 }; +/// +/// let reflect_serializer = ReflectSerializer::new(&input, ®istry); +/// let output = ron::to_string(&reflect_serializer).unwrap(); +/// +/// assert_eq!(output, r#"{"my_crate::MyStruct":(value:123)}"#); +/// ``` +/// +/// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer +/// [type path]: crate::TypePath::type_path +pub struct ReflectSerializer<'a> { + value: &'a dyn PartialReflect, + registry: &'a TypeRegistry, +} + +impl<'a> ReflectSerializer<'a> { + pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + ReflectSerializer { value, registry } + } +} + +impl<'a> Serialize for ReflectSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_map(Some(1))?; + state.serialize_entry( + self.value + .get_represented_type_info() + .ok_or_else(|| { + if self.value.is_dynamic() { + Error::custom(format_args!( + "cannot serialize dynamic value without represented type: {}", + self.value.reflect_type_path() + )) + } else { + Error::custom(format_args!( + "cannot get type info for {}", + self.value.reflect_type_path() + )) + } + })? + .type_path(), + &TypedReflectSerializer::new(self.value, self.registry), + )?; + state.end() + } +} + +/// A serializer for reflected types whose type will be known during deserialization. +/// +/// This is the serializer counterpart to [`TypedReflectDeserializer`]. +/// +/// See [`ReflectSerializer`] for a serializer that serializes an unknown type. +/// +/// # Output +/// +/// Since the type is expected to be known during deserialization, +/// this serializer will not output any additional type information, +/// such as the [type path]. +/// +/// Instead, it will output just the serialized data. +/// +/// # Example +/// +/// ``` +/// # use bevy_reflect::prelude::*; +/// # use bevy_reflect::{TypeRegistry, serde::TypedReflectSerializer}; +/// #[derive(Reflect, PartialEq, Debug)] +/// #[type_path = "my_crate"] +/// struct MyStruct { +/// value: i32 +/// } +/// +/// let mut registry = TypeRegistry::default(); +/// registry.register::(); +/// +/// let input = MyStruct { value: 123 }; +/// +/// let reflect_serializer = TypedReflectSerializer::new(&input, ®istry); +/// let output = ron::to_string(&reflect_serializer).unwrap(); +/// +/// assert_eq!(output, r#"(value:123)"#); +/// ``` +/// +/// [`TypedReflectDeserializer`]: crate::serde::TypedReflectDeserializer +/// [type path]: crate::TypePath::type_path +pub struct TypedReflectSerializer<'a> { + value: &'a dyn PartialReflect, + registry: &'a TypeRegistry, +} + +impl<'a> TypedReflectSerializer<'a> { + pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + TypedReflectSerializer { value, registry } + } +} + +impl<'a> Serialize for TypedReflectSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + // Handle both Value case and types that have a custom `Serialize` + let serializable = + Serializable::try_from_reflect_value::(self.value, self.registry); + if let Ok(serializable) = serializable { + return serializable.serialize(serializer); + } + + match self.value.reflect_ref() { + ReflectRef::Struct(value) => { + StructSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::TupleStruct(value) => { + TupleStructSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Tuple(value) => { + TupleSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::List(value) => { + ListSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Array(value) => { + ArraySerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Map(value) => { + MapSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Set(value) => { + SetSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Enum(value) => { + EnumSerializer::new(value, self.registry).serialize(serializer) + } + ReflectRef::Value(_) => Err(serializable.err().unwrap()), + } + } +} diff --git a/crates/bevy_reflect/src/serde/ser/sets.rs b/crates/bevy_reflect/src/serde/ser/sets.rs new file mode 100644 index 0000000000..9a5abab572 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/sets.rs @@ -0,0 +1,29 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Set, TypeRegistry}; +use serde::ser::SerializeSeq; +use serde::Serialize; + +/// A serializer for [`Set`] values. +pub(super) struct SetSerializer<'a> { + set: &'a dyn Set, + registry: &'a TypeRegistry, +} + +impl<'a> SetSerializer<'a> { + pub fn new(set: &'a dyn Set, registry: &'a TypeRegistry) -> Self { + Self { set, registry } + } +} + +impl<'a> Serialize for SetSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_seq(Some(self.set.len()))?; + for value in self.set.iter() { + state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/structs.rs b/crates/bevy_reflect/src/serde/ser/structs.rs new file mode 100644 index 0000000000..2c31280f05 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/structs.rs @@ -0,0 +1,67 @@ +use crate::serde::{SerializationData, TypedReflectSerializer}; +use crate::{Struct, TypeInfo, TypeRegistry}; +use serde::ser::{Error, SerializeStruct}; +use serde::Serialize; + +/// A serializer for [`Struct`] values. +pub(super) struct StructSerializer<'a> { + struct_value: &'a dyn Struct, + registry: &'a TypeRegistry, +} + +impl<'a> StructSerializer<'a> { + pub fn new(struct_value: &'a dyn Struct, registry: &'a TypeRegistry) -> Self { + Self { + struct_value, + registry, + } + } +} + +impl<'a> Serialize for StructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self + .struct_value + .get_represented_type_info() + .ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.struct_value.reflect_type_path() + )) + })?; + + let struct_info = match type_info { + TypeInfo::Struct(struct_info) => struct_info, + info => { + return Err(Error::custom(format_args!( + "expected struct type but received {info:?}" + ))); + } + }; + + let serialization_data = self + .registry + .get(type_info.type_id()) + .and_then(|registration| registration.data::()); + let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); + let mut state = serializer.serialize_struct( + struct_info.type_path_table().ident().unwrap(), + self.struct_value.field_len() - ignored_len, + )?; + + for (index, value) in self.struct_value.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or(false) + { + continue; + } + let key = struct_info.field_at(index).unwrap().name(); + state.serialize_field(key, &TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/tuple_structs.rs b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs new file mode 100644 index 0000000000..e60c9ad66c --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs @@ -0,0 +1,66 @@ +use crate::serde::{SerializationData, TypedReflectSerializer}; +use crate::{TupleStruct, TypeInfo, TypeRegistry}; +use serde::ser::{Error, SerializeTupleStruct}; +use serde::Serialize; + +/// A serializer for [`TupleStruct`] values. +pub(super) struct TupleStructSerializer<'a> { + tuple_struct: &'a dyn TupleStruct, + registry: &'a TypeRegistry, +} + +impl<'a> TupleStructSerializer<'a> { + pub fn new(tuple_struct: &'a dyn TupleStruct, registry: &'a TypeRegistry) -> Self { + Self { + tuple_struct, + registry, + } + } +} + +impl<'a> Serialize for TupleStructSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let type_info = self + .tuple_struct + .get_represented_type_info() + .ok_or_else(|| { + Error::custom(format_args!( + "cannot get type info for {}", + self.tuple_struct.reflect_type_path() + )) + })?; + + let tuple_struct_info = match type_info { + TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, + info => { + return Err(Error::custom(format_args!( + "expected tuple struct type but received {info:?}" + ))); + } + }; + + let serialization_data = self + .registry + .get(type_info.type_id()) + .and_then(|registration| registration.data::()); + let ignored_len = serialization_data.map(SerializationData::len).unwrap_or(0); + let mut state = serializer.serialize_tuple_struct( + tuple_struct_info.type_path_table().ident().unwrap(), + self.tuple_struct.field_len() - ignored_len, + )?; + + for (index, value) in self.tuple_struct.iter_fields().enumerate() { + if serialization_data + .map(|data| data.is_field_skipped(index)) + .unwrap_or(false) + { + continue; + } + state.serialize_field(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} diff --git a/crates/bevy_reflect/src/serde/ser/tuples.rs b/crates/bevy_reflect/src/serde/ser/tuples.rs new file mode 100644 index 0000000000..c3b90d5f56 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/tuples.rs @@ -0,0 +1,30 @@ +use crate::serde::TypedReflectSerializer; +use crate::{Tuple, TypeRegistry}; +use serde::ser::SerializeTuple; +use serde::Serialize; + +/// A serializer for [`Tuple`] values. +pub(super) struct TupleSerializer<'a> { + tuple: &'a dyn Tuple, + registry: &'a TypeRegistry, +} + +impl<'a> TupleSerializer<'a> { + pub fn new(tuple: &'a dyn Tuple, registry: &'a TypeRegistry) -> Self { + Self { tuple, registry } + } +} + +impl<'a> Serialize for TupleSerializer<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut state = serializer.serialize_tuple(self.tuple.field_len())?; + + for value in self.tuple.iter_fields() { + state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + } + state.end() + } +} From 4b78ba01628a6cee938f016e0c5eb71b3584df0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Drago=C8=99=20Tiselice?= Date: Mon, 9 Sep 2024 17:14:50 +0200 Subject: [PATCH 002/115] Replaced implicit emissive weight with default. (#13871) Since `StandardMaterial::emissive_exposure_weight` does not get packed into the gbuffer in the deferred case, unpacking uses an implicit default value for emissive's alpha channel. This resulted in divergent behavior between the forward and deferred renderers when using standard materials with default emissive_exposure_weight, this value defaulting to `0.0` in the forward case and `1.0` in the other. This patch changes the implicit value in the deferred case to `0.0` in order to match the behavior of the forward renderer. However, this still does not solve the case where `emissive_exposure_weight` is not `0.0`. --- crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl index f317f15b5a..b892181377 100644 --- a/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl +++ b/crates/bevy_pbr/src/deferred/pbr_deferred_functions.wgsl @@ -76,10 +76,10 @@ fn pbr_input_from_deferred_gbuffer(frag_coord: vec4, gbuffer: vec4) -> let emissive = rgb9e5::rgb9e5_to_vec3_(gbuffer.g); if ((pbr.material.flags & STANDARD_MATERIAL_FLAGS_UNLIT_BIT) != 0u) { pbr.material.base_color = vec4(emissive, 1.0); - pbr.material.emissive = vec4(vec3(0.0), 1.0); + pbr.material.emissive = vec4(vec3(0.0), 0.0); } else { pbr.material.base_color = vec4(pow(base_rough.rgb, vec3(2.2)), 1.0); - pbr.material.emissive = vec4(emissive, 1.0); + pbr.material.emissive = vec4(emissive, 0.0); } #ifdef WEBGL2 // More crunched for webgl so we can also fit depth. let props = deferred_types::unpack_unorm3x4_plus_unorm_20_(gbuffer.b); From a9d2a9ea37d10b085244546ad24b7460ef98cbd5 Mon Sep 17 00:00:00 2001 From: Chris Russell <8494645+chescock@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:23:12 -0400 Subject: [PATCH 003/115] Make QueryFilter an unsafe trait (#14790) # Objective It's possible to create UB using an implementation of `QueryFilter` that performs mutable access, but that does not violate any documented safety invariants. This code: ```rust #[derive(Component)] struct Foo(usize); // This derive is a simple way to get a valid WorldQuery impl. The QueryData impl isn't used. #[derive(QueryData)] #[query_data(mutable)] struct BadFilter<'w> { foo: &'w mut Foo, } impl QueryFilter for BadFilter<'_> { const IS_ARCHETYPAL: bool = false; unsafe fn filter_fetch( fetch: &mut Self::Fetch<'_>, entity: Entity, table_row: TableRow, ) -> bool { // SAFETY: fetch and filter_fetch have the same safety requirements let f: &mut usize = &mut unsafe { Self::fetch(fetch, entity, table_row) }.foo.0; println!("Got &mut at {f:p}"); true } } let mut world = World::new(); world.spawn(Foo(0)); world.run_system_once(|query: Query<&Foo, BadFilter>| { let f: &usize = &query.iter().next().unwrap().0; println!("Got & at {f:p}"); query.iter().next().unwrap(); println!("Still have & at {f:p}"); }); ``` prints: ``` Got &mut at 0x1924b92dfb0 Got & at 0x1924b92dfb0 Got &mut at 0x1924b92dfb0 Still have & at 0x1924b92dfb0 ``` Which means it had an `&` and `&mut` alive at the same time. The only `unsafe` there is around `Self::fetch`, but I believe that call correctly upholds the safety invariant, and matches what `Added` and `Changed` do. ## Solution Make `QueryFilter` an unsafe trait and document the requirement that the `WorldQuery` implementation be read-only. ## Migration Guide `QueryFilter` is now an `unsafe trait`. If you were manually implementing it, you will need to verify that the `WorldQuery` implementation is read-only and then add the `unsafe` keyword to the `impl`. --- crates/bevy_ecs/macros/src/query_filter.rs | 3 ++- crates/bevy_ecs/src/query/filter.rs | 25 ++++++++++++++++------ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/macros/src/query_filter.rs b/crates/bevy_ecs/macros/src/query_filter.rs index 2a2916905a..378e26df10 100644 --- a/crates/bevy_ecs/macros/src/query_filter.rs +++ b/crates/bevy_ecs/macros/src/query_filter.rs @@ -120,7 +120,8 @@ pub fn derive_query_filter_impl(input: TokenStream) -> TokenStream { ); let filter_impl = quote! { - impl #user_impl_generics #path::query::QueryFilter + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl #user_impl_generics #path::query::QueryFilter for #struct_name #user_ty_generics #user_where_clauses { const IS_ARCHETYPAL: bool = true #(&& <#field_types>::IS_ARCHETYPAL)*; diff --git a/crates/bevy_ecs/src/query/filter.rs b/crates/bevy_ecs/src/query/filter.rs index affdaf2828..0385191053 100644 --- a/crates/bevy_ecs/src/query/filter.rs +++ b/crates/bevy_ecs/src/query/filter.rs @@ -70,12 +70,17 @@ use std::{cell::UnsafeCell, marker::PhantomData}; /// [`matches_component_set`]: Self::matches_component_set /// [`Query`]: crate::system::Query /// [`State`]: Self::State +/// +/// # Safety +/// +/// The [`WorldQuery`] implementation must not take any mutable access. +/// This is the same safety requirement as [`ReadOnlyQueryData`](crate::query::ReadOnlyQueryData). #[diagnostic::on_unimplemented( message = "`{Self}` is not a valid `Query` filter", label = "invalid `Query` filter", note = "a `QueryFilter` typically uses a combination of `With` and `Without` statements" )] -pub trait QueryFilter: WorldQuery { +pub unsafe trait QueryFilter: WorldQuery { /// Returns true if (and only if) this Filter relies strictly on archetypes to limit which /// components are accessed by the Query. /// @@ -201,7 +206,8 @@ unsafe impl WorldQuery for With { } } -impl QueryFilter for With { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for With { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -311,7 +317,8 @@ unsafe impl WorldQuery for Without { } } -impl QueryFilter for Without { +// SAFETY: WorldQuery impl performs no access at all +unsafe impl QueryFilter for Without { const IS_ARCHETYPAL: bool = true; #[inline(always)] @@ -490,7 +497,8 @@ macro_rules! impl_or_query_filter { } } - impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($filter: QueryFilter),*> QueryFilter for Or<($($filter,)*)> { const IS_ARCHETYPAL: bool = true $(&& $filter::IS_ARCHETYPAL)*; #[inline(always)] @@ -512,7 +520,8 @@ macro_rules! impl_tuple_query_filter { #[allow(non_snake_case)] #[allow(clippy::unused_unit)] $(#[$meta])* - impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { + // SAFETY: This only performs access that subqueries perform, and they impl `QueryFilter` and so perform no mutable access. + unsafe impl<$($name: QueryFilter),*> QueryFilter for ($($name,)*) { const IS_ARCHETYPAL: bool = true $(&& $name::IS_ARCHETYPAL)*; #[inline(always)] @@ -734,7 +743,8 @@ unsafe impl WorldQuery for Added { } } -impl QueryFilter for Added { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Added { const IS_ARCHETYPAL: bool = false; #[inline(always)] unsafe fn filter_fetch( @@ -949,7 +959,8 @@ unsafe impl WorldQuery for Changed { } } -impl QueryFilter for Changed { +// SAFETY: WorldQuery impl performs only read access on ticks +unsafe impl QueryFilter for Changed { const IS_ARCHETYPAL: bool = false; #[inline(always)] From e939d6c33f75676a358d3605bdd5b97b130b555a Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:24:17 -0500 Subject: [PATCH 004/115] Remove remnant `EntityHash` and related types from `bevy_utils` (#15039) # Objective `EntityHash` and related types were moved from `bevy_utils` to `bevy_ecs` in #11498, but seemed to have been accidentally reintroduced a week later in #11707. ## Solution Remove the old leftover code. --- ## Migration Guide - Uses of `bevy::utils::{EntityHash, EntityHasher, EntityHashMap, EntityHashSet}` now have to be imported from `bevy::ecs::entity`. --- crates/bevy_ecs/src/observer/mod.rs | 9 ++- crates/bevy_reflect/src/impls/std.rs | 1 - .../src/batching/gpu_preprocessing.rs | 5 +- .../bevy_render/src/view/visibility/range.rs | 10 +-- crates/bevy_utils/src/lib.rs | 79 ------------------- examples/2d/mesh2d_manual.rs | 4 +- 6 files changed, 14 insertions(+), 94 deletions(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index c6ace80ca5..4711957af9 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -7,11 +7,12 @@ mod trigger_event; pub use runner::*; pub use trigger_event::*; +use crate::entity::EntityHashMap; use crate::observer::entity_observer::ObservedBy; use crate::{archetype::ArchetypeFlags, system::IntoObserverSystem, world::*}; use crate::{component::ComponentId, prelude::*, world::DeferredWorld}; use bevy_ptr::Ptr; -use bevy_utils::{EntityHashMap, HashMap}; +use bevy_utils::HashMap; use std::{fmt::Debug, marker::PhantomData}; /// Type containing triggered [`Event`] information for a given run of an [`Observer`]. This contains the @@ -152,7 +153,7 @@ pub struct ObserverTrigger { } // Map between an observer entity and its runner -type ObserverMap = EntityHashMap; +type ObserverMap = EntityHashMap; /// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger targeted at a specific component. #[derive(Default, Debug)] @@ -160,7 +161,7 @@ pub struct CachedComponentObservers { // Observers listening to triggers targeting this component map: ObserverMap, // Observers listening to triggers targeting this component on a specific entity - entity_map: EntityHashMap, + entity_map: EntityHashMap, } /// Collection of [`ObserverRunner`] for [`Observer`] registered to a particular trigger. @@ -171,7 +172,7 @@ pub struct CachedObservers { // Observers listening for this trigger fired at a specific component component_observers: HashMap, // Observers listening for this trigger fired at a specific entity - entity_observers: EntityHashMap, + entity_observers: EntityHashMap, } /// Metadata for observers. Stores a cache mapping trigger ids to the registered observers. diff --git a/crates/bevy_reflect/src/impls/std.rs b/crates/bevy_reflect/src/impls/std.rs index c9f27a4cb3..85f63be654 100644 --- a/crates/bevy_reflect/src/impls/std.rs +++ b/crates/bevy_reflect/src/impls/std.rs @@ -1029,7 +1029,6 @@ macro_rules! impl_reflect_for_hashset { } impl_type_path!(::bevy_utils::NoOpHash); -impl_type_path!(::bevy_utils::EntityHash); impl_type_path!(::bevy_utils::FixedState); impl_reflect_for_hashset!(::std::collections::HashSet); diff --git a/crates/bevy_render/src/batching/gpu_preprocessing.rs b/crates/bevy_render/src/batching/gpu_preprocessing.rs index 7d5ce067f5..bd515c0840 100644 --- a/crates/bevy_render/src/batching/gpu_preprocessing.rs +++ b/crates/bevy_render/src/batching/gpu_preprocessing.rs @@ -3,14 +3,13 @@ use bevy_app::{App, Plugin}; use bevy_derive::{Deref, DerefMut}; use bevy_ecs::{ - entity::Entity, + entity::{Entity, EntityHashMap}, query::{Has, With}, schedule::IntoSystemConfigs as _, system::{Query, Res, ResMut, Resource, StaticSystemParam}, world::{FromWorld, World}, }; use bevy_encase_derive::ShaderType; -use bevy_utils::EntityHashMap; use bytemuck::{Pod, Zeroable}; use nonmax::NonMaxU32; use smallvec::smallvec; @@ -99,7 +98,7 @@ where /// corresponds to each instance. /// /// This is keyed off each view. Each view has a separate buffer. - pub work_item_buffers: EntityHashMap, + pub work_item_buffers: EntityHashMap, /// The uniform data inputs for the current frame. /// diff --git a/crates/bevy_render/src/view/visibility/range.rs b/crates/bevy_render/src/view/visibility/range.rs index d1e1d6546f..d9792d0296 100644 --- a/crates/bevy_render/src/view/visibility/range.rs +++ b/crates/bevy_render/src/view/visibility/range.rs @@ -9,7 +9,7 @@ use std::{ use bevy_app::{App, Plugin, PostUpdate}; use bevy_ecs::{ component::Component, - entity::Entity, + entity::{Entity, EntityHashMap}, query::{Changed, With}, schedule::IntoSystemConfigs as _, system::{Query, Res, ResMut, Resource}, @@ -17,7 +17,7 @@ use bevy_ecs::{ use bevy_math::{vec4, FloatOrd, Vec4}; use bevy_reflect::Reflect; use bevy_transform::components::GlobalTransform; -use bevy_utils::{prelude::default, EntityHashMap, HashMap}; +use bevy_utils::{prelude::default, HashMap}; use nonmax::NonMaxU16; use wgpu::{BufferBindingType, BufferUsages}; @@ -191,7 +191,7 @@ impl VisibilityRange { #[derive(Resource)] pub struct RenderVisibilityRanges { /// Information corresponding to each entity. - entities: EntityHashMap, + entities: EntityHashMap, /// Maps a [`VisibilityRange`] to its index within the `buffer`. /// @@ -309,13 +309,13 @@ impl RenderVisibilityRanges { #[derive(Resource, Default)] pub struct VisibleEntityRanges { /// Stores which bit index each view corresponds to. - views: EntityHashMap, + views: EntityHashMap, /// Stores a bitmask in which each view has a single bit. /// /// A 0 bit for a view corresponds to "out of range"; a 1 bit corresponds to /// "in range". - entities: EntityHashMap, + entities: EntityHashMap, } impl VisibleEntityRanges { diff --git a/crates/bevy_utils/src/lib.rs b/crates/bevy_utils/src/lib.rs index b53bcd3e2e..4c04b793df 100644 --- a/crates/bevy_utils/src/lib.rs +++ b/crates/bevy_utils/src/lib.rs @@ -276,85 +276,6 @@ impl PreHashMapExt for PreHashMap Self::Hasher { - EntityHasher::default() - } -} - -/// A very fast hash that is only designed to work on generational indices -/// like `Entity`. It will panic if attempting to hash a type containing -/// non-u64 fields. -/// -/// This is heavily optimized for typical cases, where you have mostly live -/// entities, and works particularly well for contiguous indices. -/// -/// If you have an unusual case -- say all your indices are multiples of 256 -/// or most of the entities are dead generations -- then you might want also to -/// try [`AHasher`] for a slower hash computation but fewer lookup conflicts. -#[derive(Debug, Default)] -pub struct EntityHasher { - hash: u64, -} - -impl Hasher for EntityHasher { - #[inline] - fn finish(&self) -> u64 { - self.hash - } - - fn write(&mut self, _bytes: &[u8]) { - panic!("can only hash u64 using EntityHasher"); - } - - #[inline] - fn write_u64(&mut self, bits: u64) { - // SwissTable (and thus `hashbrown`) cares about two things from the hash: - // - H1: low bits (masked by `2ⁿ-1`) to pick the slot in which to store the item - // - H2: high 7 bits are used to SIMD optimize hash collision probing - // For more see - - // This hash function assumes that the entity ids are still well-distributed, - // so for H1 leaves the entity id alone in the low bits so that id locality - // will also give memory locality for things spawned together. - // For H2, take advantage of the fact that while multiplication doesn't - // spread entropy to the low bits, it's incredibly good at spreading it - // upward, which is exactly where we need it the most. - - // While this does include the generation in the output, it doesn't do so - // *usefully*. H1 won't care until you have over 3 billion entities in - // the table, and H2 won't care until something hits generation 33 million. - // Thus the comment suggesting that this is best for live entities, - // where there won't be generation conflicts where it would matter. - - // The high 32 bits of this are ⅟φ for Fibonacci hashing. That works - // particularly well for hashing for the same reason as described in - // - // It loses no information because it has a modular inverse. - // (Specifically, `0x144c_bc89_u32 * 0x9e37_79b9_u32 == 1`.) - // - // The low 32 bits make that part of the just product a pass-through. - const UPPER_PHI: u64 = 0x9e37_79b9_0000_0001; - - // This is `(MAGIC * index + generation) << 32 + index`, in a single instruction. - self.hash = bits.wrapping_mul(UPPER_PHI); - } -} - -/// A [`HashMap`] pre-configured to use [`EntityHash`] hashing. -/// Iteration order only depends on the order of insertions and deletions. -pub type EntityHashMap = hashbrown::HashMap; - -/// A [`HashSet`] pre-configured to use [`EntityHash`] hashing. -/// Iteration order only depends on the order of insertions and deletions. -pub type EntityHashSet = hashbrown::HashSet; - /// A specialized hashmap type with Key of [`TypeId`] /// Iteration order only depends on the order of insertions and deletions. pub type TypeIdMap = hashbrown::HashMap; diff --git a/examples/2d/mesh2d_manual.rs b/examples/2d/mesh2d_manual.rs index b298a12fec..4361c17b13 100644 --- a/examples/2d/mesh2d_manual.rs +++ b/examples/2d/mesh2d_manual.rs @@ -8,6 +8,7 @@ use bevy::{ color::palettes::basic::YELLOW, core_pipeline::core_2d::{Transparent2d, CORE_2D_DEPTH_FORMAT}, + ecs::entity::EntityHashMap, math::FloatOrd, prelude::*, render::{ @@ -33,7 +34,6 @@ use bevy::{ Mesh2dPipelineKey, Mesh2dTransforms, MeshFlags, RenderMesh2dInstance, SetMesh2dBindGroup, SetMesh2dViewBindGroup, WithMesh2d, }, - utils::EntityHashMap, }; use std::f32::consts::PI; @@ -291,7 +291,7 @@ pub const COLORED_MESH2D_SHADER_HANDLE: Handle = /// Our custom pipeline needs its own instance storage #[derive(Resource, Deref, DerefMut, Default)] -pub struct RenderColoredMesh2dInstances(EntityHashMap); +pub struct RenderColoredMesh2dInstances(EntityHashMap); impl Plugin for ColoredMesh2dPlugin { fn build(&self, app: &mut App) { From 5eca832ceeca728521743025eaaf6260aa0956f2 Mon Sep 17 00:00:00 2001 From: charlotte Date: Mon, 9 Sep 2024 08:28:31 -0700 Subject: [PATCH 005/115] Add convenience methods for constructing and setting storage buffer data (#15044) Adds some methods to assist in building `ShaderStorageBuffer` without using `bytemuck`. We keep the `&[u8]` constructors since this is still modeled as a thin wrapper around the buffer descriptor, but should make it easier to interact with at the cost of an extra allocation in the `ShaderType` path for the buffer writer. Follow up from #14663 --- crates/bevy_render/src/storage.rs | 25 ++++++++++++++++++++++ examples/shader/storage_buffer.rs | 35 +++++++++++++------------------ 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/crates/bevy_render/src/storage.rs b/crates/bevy_render/src/storage.rs index 4225ee7e28..4c6baed318 100644 --- a/crates/bevy_render/src/storage.rs +++ b/crates/bevy_render/src/storage.rs @@ -8,6 +8,8 @@ use bevy_ecs::system::SystemParamItem; use bevy_reflect::prelude::ReflectDefault; use bevy_reflect::Reflect; use bevy_utils::default; +use encase::internal::WriteInto; +use encase::ShaderType; use wgpu::util::BufferInitDescriptor; /// Adds [`ShaderStorageBuffer`] as an asset that is extracted and uploaded to the GPU. @@ -72,6 +74,29 @@ impl ShaderStorageBuffer { storage.asset_usage = asset_usage; storage } + + /// Sets the data of the storage buffer to the given [`ShaderType`]. + pub fn set_data(&mut self, value: T) + where + T: ShaderType + WriteInto, + { + let size = value.size().get() as usize; + let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); + wrapper.write(&value).unwrap(); + self.data = Some(wrapper.into_inner()); + } +} + +impl From for ShaderStorageBuffer +where + T: ShaderType + WriteInto, +{ + fn from(value: T) -> Self { + let size = value.size().get() as usize; + let mut wrapper = encase::StorageBuffer::>::new(Vec::with_capacity(size)); + wrapper.write(&value).unwrap(); + Self::new(wrapper.as_ref(), RenderAssetUsages::default()) + } } /// A storage buffer that is prepared as a [`RenderAsset`] and uploaded to the GPU. diff --git a/examples/shader/storage_buffer.rs b/examples/shader/storage_buffer.rs index 7661a103a7..827240adbd 100644 --- a/examples/shader/storage_buffer.rs +++ b/examples/shader/storage_buffer.rs @@ -4,7 +4,6 @@ use bevy::{ reflect::TypePath, render::render_resource::{AsBindGroup, ShaderRef}, }; -use bevy_render::render_asset::RenderAssetUsages; use bevy_render::storage::ShaderStorageBuffer; const SHADER_ASSET_PATH: &str = "shaders/storage_buffer.wgsl"; @@ -33,10 +32,7 @@ fn setup( [0.0, 1.0, 1.0, 1.0], ]; - let colors = buffers.add(ShaderStorageBuffer::new( - bytemuck::cast_slice(color_data.as_slice()), - RenderAssetUsages::default(), - )); + let colors = buffers.add(ShaderStorageBuffer::from(color_data)); // Create the custom material with the storage buffer let custom_material = CustomMaterial { colors }; @@ -72,22 +68,19 @@ fn update( ) { let material = materials.get_mut(&material_handle.0).unwrap(); let buffer = buffers.get_mut(&material.colors).unwrap(); - buffer.data = Some( - bytemuck::cast_slice( - (0..5) - .map(|i| { - let t = time.elapsed_seconds() * 5.0; - [ - (t + i as f32).sin() / 2.0 + 0.5, - (t + i as f32 + 2.0).sin() / 2.0 + 0.5, - (t + i as f32 + 4.0).sin() / 2.0 + 0.5, - 1.0, - ] - }) - .collect::>() - .as_slice(), - ) - .to_vec(), + buffer.set_data( + (0..5) + .map(|i| { + let t = time.elapsed_seconds() * 5.0; + [ + (t + i as f32).sin() / 2.0 + 0.5, + (t + i as f32 + 2.0).sin() / 2.0 + 0.5, + (t + i as f32 + 4.0).sin() / 2.0 + 0.5, + 1.0, + ] + }) + .collect::>() + .as_slice(), ); } From fab0e5d085448a38a4d2b853e35db5909b57aaf6 Mon Sep 17 00:00:00 2001 From: LP Date: Mon, 9 Sep 2024 11:31:30 -0400 Subject: [PATCH 006/115] Sorts the scene entries by path before serializing. (#15047) # Objective Fixes: https://github.com/bevyengine/bevy/issues/14515 ## Solution Sorts the iterator with itertools' sorted_by function. This is required given that 'self.entries' is an immutable &[Box Serialize for EntitySerializer<'a> { /// Used to serialize scene resources in [`SceneSerializer`] and entity components in [`EntitySerializer`]. /// Note that having several entries of the same type in `entries` will lead to an error when using the RON format and /// deserializing through [`SceneMapDeserializer`]. +/// +/// Note: The entries are sorted by type path before they're serialized. pub struct SceneMapSerializer<'a> { /// List of boxed values of unique type to serialize. pub entries: &'a [Box], @@ -167,10 +169,25 @@ impl<'a> Serialize for SceneMapSerializer<'a> { S: Serializer, { let mut state = serializer.serialize_map(Some(self.entries.len()))?; - for reflect in self.entries { + let sorted_entries = { + let mut entries = self + .entries + .iter() + .map(|entry| { + ( + entry.get_represented_type_info().unwrap().type_path(), + entry.as_partial_reflect(), + ) + }) + .collect::>(); + entries.sort_by_key(|(type_path, _partial_reflect)| *type_path); + entries + }; + + for (type_path, partial_reflect) in sorted_entries { state.serialize_entry( - reflect.get_represented_type_info().unwrap().type_path(), - &TypedReflectSerializer::new(reflect.as_partial_reflect(), self.registry), + type_path, + &TypedReflectSerializer::new(partial_reflect, self.registry), )?; } state.end() @@ -598,15 +615,15 @@ mod tests { ), 4294967297: ( components: { - "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), + "bevy_scene::serde::tests::Foo": (123), }, ), 4294967298: ( components: { - "bevy_scene::serde::tests::Foo": (123), "bevy_scene::serde::tests::Bar": (345), "bevy_scene::serde::tests::Baz": (789), + "bevy_scene::serde::tests::Foo": (123), }, ), }, From 3d30b0f9acd0e2ac7ce7c43cd498815956dbe202 Mon Sep 17 00:00:00 2001 From: Alice Cecile Date: Mon, 9 Sep 2024 11:33:29 -0400 Subject: [PATCH 007/115] Add basic docs to AssetMode (#15057) # Objective We should attempt to document the entirety of bevy_assets. `AssetMode` is missing docs explaining what it is, how it's used and why it exists. ## Solution Add docs, focusing on the context in https://github.com/bevyengine/bevy/issues/10157. --- crates/bevy_asset/src/lib.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/bevy_asset/src/lib.rs b/crates/bevy_asset/src/lib.rs index f7787fb809..b42b4436f9 100644 --- a/crates/bevy_asset/src/lib.rs +++ b/crates/bevy_asset/src/lib.rs @@ -100,6 +100,12 @@ pub struct AssetPlugin { pub meta_check: AssetMetaCheck, } +/// Controls whether or not assets are pre-processed before being loaded. +/// +/// This setting is controlled by setting [`AssetPlugin::mode`]. +/// +/// When building on web, asset preprocessing can cause problems due to the lack of filesystem access. +/// See [bevy#10157](https://github.com/bevyengine/bevy/issues/10157) for context. #[derive(Debug)] pub enum AssetMode { /// Loads assets from their [`AssetSource`]'s default [`AssetReader`] without any "preprocessing". From 7b217a976ce353914083141015891b51f4a09abb Mon Sep 17 00:00:00 2001 From: Ben Frankel Date: Mon, 9 Sep 2024 18:36:09 +0300 Subject: [PATCH 008/115] Remove deprecated `SpriteSheetBundle` and `AtlasImageBundle` (#15062) # Objective Remove bundles that were deprecated in 0.14. ## Testing `rg SpriteSheetBundle` and `rg AtlasImageBundle` show no results. --- crates/bevy_sprite/src/bundle.rs | 40 ++------------------- crates/bevy_sprite/src/lib.rs | 4 --- crates/bevy_ui/src/node_bundles.rs | 57 ++---------------------------- 3 files changed, 4 insertions(+), 97 deletions(-) diff --git a/crates/bevy_sprite/src/bundle.rs b/crates/bevy_sprite/src/bundle.rs index a9a1736fa0..ca962c40b1 100644 --- a/crates/bevy_sprite/src/bundle.rs +++ b/crates/bevy_sprite/src/bundle.rs @@ -1,6 +1,4 @@ -#![allow(deprecated)] - -use crate::{Sprite, TextureAtlas}; +use crate::Sprite; use bevy_asset::Handle; use bevy_ecs::bundle::Bundle; use bevy_render::{ @@ -15,7 +13,7 @@ use bevy_transform::components::{GlobalTransform, Transform}; /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](crate::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](crate::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Clone, Debug, Default)] pub struct SpriteBundle { /// Specifies the rendering properties of the sprite, such as color tint and flip. @@ -33,37 +31,3 @@ pub struct SpriteBundle { /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering pub view_visibility: ViewVisibility, } - -/// A [`Bundle`] of components for drawing a single sprite from a sprite sheet (also referred -/// to as a `TextureAtlas`) or for animated sprites. -/// -/// Note: -/// This bundle is identical to [`SpriteBundle`] with an additional [`TextureAtlas`] component. -/// -/// Check the following examples for usage: -/// - [`animated sprite sheet example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_sheet.rs) -/// - [`sprite animation event example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/sprite_animation.rs) -/// - [`texture atlas example`](https://github.com/bevyengine/bevy/blob/latest/examples/2d/texture_atlas.rs) -#[deprecated( - since = "0.14.0", - note = "Use `TextureAtlas` alongside a `SpriteBundle` instead" -)] -#[derive(Bundle, Clone, Debug, Default)] -pub struct SpriteSheetBundle { - /// Specifies the rendering properties of the sprite, such as color tint and flip. - pub sprite: Sprite, - /// The local transform of the sprite, relative to its parent. - pub transform: Transform, - /// The absolute transform of the sprite. This should generally not be written to directly. - pub global_transform: GlobalTransform, - /// The sprite sheet base texture - pub texture: Handle, - /// The sprite sheet texture atlas, allowing to draw a custom section of `texture`. - pub atlas: TextureAtlas, - /// User indication of whether an entity is visible - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, -} diff --git a/crates/bevy_sprite/src/lib.rs b/crates/bevy_sprite/src/lib.rs index 152b612bd1..b638cd42a1 100644 --- a/crates/bevy_sprite/src/lib.rs +++ b/crates/bevy_sprite/src/lib.rs @@ -23,10 +23,6 @@ mod texture_slice; /// /// This includes the most common types in this crate, re-exported for your convenience. pub mod prelude { - #[allow(deprecated)] - #[doc(hidden)] - pub use crate::bundle::SpriteSheetBundle; - #[doc(hidden)] pub use crate::{ bundle::SpriteBundle, diff --git a/crates/bevy_ui/src/node_bundles.rs b/crates/bevy_ui/src/node_bundles.rs index b70010f327..a093cf13cf 100644 --- a/crates/bevy_ui/src/node_bundles.rs +++ b/crates/bevy_ui/src/node_bundles.rs @@ -1,5 +1,3 @@ -#![allow(deprecated)] - //! This module contains basic node bundles used to build UIs #[cfg(feature = "bevy_text")] @@ -14,7 +12,6 @@ use bevy_asset::Handle; use bevy_color::Color; use bevy_ecs::bundle::Bundle; use bevy_render::view::{InheritedVisibility, ViewVisibility, Visibility}; -use bevy_sprite::TextureAtlas; #[cfg(feature = "bevy_text")] use bevy_text::{ BreakLineOn, CosmicBuffer, JustifyText, Text, TextLayoutInfo, TextSection, TextStyle, @@ -67,7 +64,7 @@ pub struct NodeBundle { /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Debug, Default)] pub struct ImageBundle { /// Describes the logical size of the node @@ -110,56 +107,6 @@ pub struct ImageBundle { pub z_index: ZIndex, } -/// A UI node that is a texture atlas sprite -/// -/// # Extra behaviours -/// -/// You may add the following components to enable additional behaviours -/// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// -/// This bundle is identical to [`ImageBundle`] with an additional [`TextureAtlas`] component. -#[deprecated( - since = "0.14.0", - note = "Use `TextureAtlas` alongside `ImageBundle` instead" -)] -#[derive(Bundle, Debug, Default)] -pub struct AtlasImageBundle { - /// Describes the logical size of the node - pub node: Node, - /// Styles which control the layout (size and position) of the node and its children - /// In some cases these styles also affect how the node drawn/painted. - pub style: Style, - /// The calculated size based on the given image - pub calculated_size: ContentSize, - /// The image of the node - pub image: UiImage, - /// A handle to the texture atlas to use for this Ui Node - pub texture_atlas: TextureAtlas, - /// Whether this node should block interaction with lower nodes - pub focus_policy: FocusPolicy, - /// The size of the image in pixels - /// - /// This component is set automatically - pub image_size: UiImageSize, - /// The transform of the node - /// - /// This component is automatically managed by the UI layout system. - /// To alter the position of the `AtlasImageBundle`, use the properties of the [`Style`] component. - pub transform: Transform, - /// The global transform of the node - /// - /// This component is automatically updated by the [`TransformPropagate`](`bevy_transform::TransformSystem::TransformPropagate`) systems. - pub global_transform: GlobalTransform, - /// Describes the visibility properties of the node - pub visibility: Visibility, - /// Inherited visibility of an entity. - pub inherited_visibility: InheritedVisibility, - /// Algorithmically-computed indication of whether an entity is visible and should be extracted for rendering - pub view_visibility: ViewVisibility, - /// Indicates the depth at which the node should appear in the UI - pub z_index: ZIndex, -} - #[cfg(feature = "bevy_text")] /// A UI node that is text /// @@ -269,7 +216,7 @@ where /// /// You may add one or both of the following components to enable additional behaviours: /// - [`ImageScaleMode`](bevy_sprite::ImageScaleMode) to enable either slicing or tiling of the texture -/// - [`TextureAtlas`] to draw a specific section of the texture +/// - [`TextureAtlas`](bevy_sprite::TextureAtlas) to draw a specific section of the texture #[derive(Bundle, Clone, Debug)] pub struct ButtonBundle { /// Describes the logical size of the node From bca228fdaa2945b9c4528e5514ed8732d3c70b24 Mon Sep 17 00:00:00 2001 From: CrazyboyQCD <53971641+CrazyboyQCD@users.noreply.github.com> Date: Mon, 9 Sep 2024 23:40:00 +0800 Subject: [PATCH 009/115] Simplify `pick_rounded_rect` (#15065) # Objective Simplify `pick_rounded_rect` with multiple `if` statements to make it more readable and efficient([Godbolt link](https://godbolt.org/z/W5vPEvT5c)). Co-authored-by: WX\shixi --- crates/bevy_ui/src/focus.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/bevy_ui/src/focus.rs b/crates/bevy_ui/src/focus.rs index 387d47c06c..c7feeca10f 100644 --- a/crates/bevy_ui/src/focus.rs +++ b/crates/bevy_ui/src/focus.rs @@ -352,12 +352,12 @@ pub(crate) fn pick_rounded_rect( size: Vec2, border_radius: ResolvedBorderRadius, ) -> bool { - let s = point.signum(); - let r = (border_radius.top_left * (1. - s.x) * (1. - s.y) - + border_radius.top_right * (1. + s.x) * (1. - s.y) - + border_radius.bottom_right * (1. + s.x) * (1. + s.y) - + border_radius.bottom_left * (1. - s.x) * (1. + s.y)) - / 4.; + let [top, bottom] = if point.x < 0. { + [border_radius.top_left, border_radius.bottom_left] + } else { + [border_radius.top_right, border_radius.bottom_right] + }; + let r = if point.y < 0. { top } else { bottom }; let corner_to_point = point.abs() - 0.5 * size; let q = corner_to_point + r; From 8460cfa6ab1fb88d037b3999f6660d0723baa9f6 Mon Sep 17 00:00:00 2001 From: charlotte Date: Mon, 9 Sep 2024 08:48:33 -0700 Subject: [PATCH 010/115] Fix `AsBindGroup` sampler validation. (#15071) Kind of confused why this wasn't breaking for me pre-`0.15-dev` since nothing obvious seems to have changed in `wgpu` upstream, but this fixes it and ensures that we return the correct sample type re: the actual device. --- crates/bevy_render/macros/src/as_bind_group.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bevy_render/macros/src/as_bind_group.rs b/crates/bevy_render/macros/src/as_bind_group.rs index 9e4a4ab9fd..6de62672a6 100644 --- a/crates/bevy_render/macros/src/as_bind_group.rs +++ b/crates/bevy_render/macros/src/as_bind_group.rs @@ -366,7 +366,7 @@ pub fn derive_as_bind_group(ast: syn::DeriveInput) -> Result { if let Some(handle) = handle { let image = images.get(handle).ok_or_else(|| #render_path::render_resource::AsBindGroupError::RetryNextUpdate)?; - let Some(sample_type) = image.texture_format.sample_type(None, None) else { + let Some(sample_type) = image.texture_format.sample_type(None, Some(render_device.features())) else { return Err(#render_path::render_resource::AsBindGroupError::InvalidSamplerType( #binding_index, "None".to_string(), From 82e416dc48f7a76c928ef15dd64b0a2ff858e90c Mon Sep 17 00:00:00 2001 From: Alix Bott Date: Mon, 9 Sep 2024 17:51:28 +0200 Subject: [PATCH 011/115] Split OrthographicProjection::default into 2d & 3d (Adopted) (#15073) Adopted PR from dmlary, all credit to them! https://github.com/bevyengine/bevy/pull/9915 Original description: # Objective The default value for `near` in `OrthographicProjection` should be different for 2d & 3d. For 2d using `near = -1000` allows bevy users to build up scenes using background `z = 0`, and foreground elements `z > 0` similar to css. However in 3d `near = -1000` results in objects behind the camera being rendered. Using `near = 0` works for 3d, but forces 2d users to assign `z <= 0` for rendered elements, putting the background at some arbitrary negative value. There is no common value for `near` that doesn't result in a footgun or usability issue for either 2d or 3d, so they should have separate values. There was discussion about other options in the discord [0](https://discord.com/channels/691052431525675048/1154114310042292325), but splitting `default()` into `default_2d()` and `default_3d()` seemed like the lowest cost approach. Related/past work https://github.com/bevyengine/bevy/issues/9138, https://github.com/bevyengine/bevy/pull/9214, https://github.com/bevyengine/bevy/pull/9310, https://github.com/bevyengine/bevy/pull/9537 (thanks to @Selene-Amanita for the list) ## Solution This commit splits `OrthographicProjection::default` into `default_2d` and `default_3d`. ## Migration Guide - In initialization of `OrthographicProjection`, change `..default()` to `..OrthographicProjection::default_2d()` or `..OrthographicProjection::default_3d()` Example: ```diff --- a/examples/3d/orthographic.rs +++ b/examples/3d/orthographic.rs @@ -20,7 +20,7 @@ fn setup( projection: OrthographicProjection { scale: 3.0, scaling_mode: ScalingMode::FixedVertical(2.0), - ..default() + ..OrthographicProjection::default_3d() } .into(), transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), ``` --------- Co-authored-by: David M. Lary Co-authored-by: Jan Hohenheim --- .../src/core_2d/camera_2d.rs | 12 ++----- .../src/ui_debug_overlay/mod.rs | 2 +- crates/bevy_gltf/src/loader.rs | 2 +- crates/bevy_render/src/camera/projection.rs | 31 ++++++++++++++++--- examples/3d/orthographic.rs | 2 +- examples/3d/pbr.rs | 2 +- examples/stress_tests/many_lights.rs | 2 +- 7 files changed, 33 insertions(+), 20 deletions(-) diff --git a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs index 857c220216..b45f668c99 100644 --- a/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs +++ b/crates/bevy_core_pipeline/src/core_2d/camera_2d.rs @@ -23,10 +23,6 @@ pub struct Camera2d; pub struct Camera2dBundle { pub camera: Camera, pub camera_render_graph: CameraRenderGraph, - /// Note: default value for `OrthographicProjection.near` is `0.0` - /// which makes objects on the screen plane invisible to 2D camera. - /// `Camera2dBundle::default()` sets `near` to negative value, - /// so be careful when initializing this field manually. pub projection: OrthographicProjection, pub visible_entities: VisibleEntities, pub frustum: Frustum, @@ -41,11 +37,7 @@ pub struct Camera2dBundle { impl Default for Camera2dBundle { fn default() -> Self { - let projection = OrthographicProjection { - far: 1000., - near: -1000., - ..Default::default() - }; + let projection = OrthographicProjection::default_2d(); let transform = Transform::default(); let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); Self { @@ -77,7 +69,7 @@ impl Camera2dBundle { // the camera's translation by far and use a right handed coordinate system let projection = OrthographicProjection { far, - ..Default::default() + ..OrthographicProjection::default_2d() }; let transform = Transform::from_xyz(0.0, 0.0, far - 0.1); let frustum = projection.compute_frustum(&GlobalTransform::from(transform)); diff --git a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs index 6382cfa903..88af56210a 100644 --- a/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs +++ b/crates/bevy_dev_tools/src/ui_debug_overlay/mod.rs @@ -92,7 +92,7 @@ fn update_debug_camera( projection: OrthographicProjection { far: 1000.0, viewport_origin: Vec2::new(0.0, 0.0), - ..default() + ..OrthographicProjection::default_3d() }, camera: Camera { order: LAYOUT_DEBUG_CAMERA_ORDER, diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index 010e80f659..e2c193e110 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1256,7 +1256,7 @@ fn load_node( far: orthographic.zfar(), scaling_mode: ScalingMode::FixedHorizontal(1.0), scale: xmag, - ..Default::default() + ..OrthographicProjection::default_3d() }; Projection::Orthographic(orthographic_projection) diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 0a6c3ca00a..2aa5f8acc0 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -237,7 +237,7 @@ impl Default for PerspectiveProjection { /// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode}; /// let projection = Projection::Orthographic(OrthographicProjection { /// scaling_mode: ScalingMode::FixedVertical(2.0), -/// ..OrthographicProjection::default() +/// ..OrthographicProjection::default_2d() /// }); /// ``` #[derive(Debug, Clone, Copy, Reflect, Serialize, Deserialize)] @@ -334,11 +334,11 @@ impl DivAssign for ScalingMode { /// # use bevy_render::camera::{OrthographicProjection, Projection, ScalingMode}; /// let projection = Projection::Orthographic(OrthographicProjection { /// scaling_mode: ScalingMode::WindowSize(100.0), -/// ..OrthographicProjection::default() +/// ..OrthographicProjection::default_2d() /// }); /// ``` #[derive(Component, Debug, Clone, Reflect)] -#[reflect(Component, Default)] +#[reflect(Component)] pub struct OrthographicProjection { /// The distance of the near clipping plane in world units. /// @@ -479,8 +479,29 @@ impl CameraProjection for OrthographicProjection { } } -impl Default for OrthographicProjection { - fn default() -> Self { +impl FromWorld for OrthographicProjection { + fn from_world(_world: &mut World) -> Self { + OrthographicProjection::default_3d() + } +} + +impl OrthographicProjection { + /// Returns the default orthographic projection for a 2D context. + /// + /// The near plane is set to a negative value so that the camera can still + /// render the scene when using positive z coordinates to order foreground elements. + pub fn default_2d() -> Self { + OrthographicProjection { + near: -1000.0, + ..OrthographicProjection::default_3d() + } + } + + /// Returns the default orthographic projection for a 3D context. + /// + /// The near plane is set to 0.0 so that the camera doesn't render + /// objects that are behind it. + pub fn default_3d() -> Self { OrthographicProjection { scale: 1.0, near: 0.0, diff --git a/examples/3d/orthographic.rs b/examples/3d/orthographic.rs index a56e4f972f..f257ab35f8 100644 --- a/examples/3d/orthographic.rs +++ b/examples/3d/orthographic.rs @@ -20,7 +20,7 @@ fn setup( projection: OrthographicProjection { // 6 world units per window height. scaling_mode: ScalingMode::FixedVertical(6.0), - ..default() + ..OrthographicProjection::default_3d() } .into(), transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index 3c7a762119..a20adb8edf 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -121,7 +121,7 @@ fn setup( transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y), projection: OrthographicProjection { scale: 0.01, - ..default() + ..OrthographicProjection::default_3d() } .into(), ..default() diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 196bea143d..33244a99b9 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -96,7 +96,7 @@ fn setup( projection: OrthographicProjection { scale: 20.0, scaling_mode: ScalingMode::FixedHorizontal(1.0), - ..default() + ..OrthographicProjection::default_3d() } .into(), ..default() From ce32b5ca06c834458a01f49e3929ca8ea33a17f6 Mon Sep 17 00:00:00 2001 From: UkoeHB <37489173+UkoeHB@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:58:09 -0500 Subject: [PATCH 012/115] Add set_state extension method to Commands (#15083) # Objective - Improve the ergonomics of managing states. ## Solution - Add `set_state` extension method to `Commands` so you don't need to type out `ResMut>` to update a state. It also reduces system parameter list size when you already have `Commands`. - I only updated a couple examples to showcase how it can be used. There *is* a potential perf cost to introducing `Commands` so this method shouldn't necessarily be used everywhere. ## Testing - Tested the updated examples: `game_menu` and `alien_cake_addict`. --- ## Showcase Add `Commands::set_state` method for easily updating states. Set directly: ```rust fn go_to_game(mut game_state: ResMut>) { game_state.set(GameState::Play); } ``` Set with commands (**NEW**): ```rust fn go_to_game(mut commands: Commands) { commands.set_state(GameState::Play); } ``` --- crates/bevy_state/src/commands.rs | 30 ++++++++++++++++++++++++++++++ crates/bevy_state/src/lib.rs | 4 ++++ 2 files changed, 34 insertions(+) create mode 100644 crates/bevy_state/src/commands.rs diff --git a/crates/bevy_state/src/commands.rs b/crates/bevy_state/src/commands.rs new file mode 100644 index 0000000000..b392d6b394 --- /dev/null +++ b/crates/bevy_state/src/commands.rs @@ -0,0 +1,30 @@ +use bevy_ecs::{system::Commands, world::World}; +use bevy_utils::tracing::debug; + +use crate::state::{FreelyMutableState, NextState}; + +/// Extension trait for [`Commands`] adding `bevy_state` helpers. +pub trait CommandsStatesExt { + /// Sets the next state the app should move to. + /// + /// Internally this schedules a command that updates the [`NextState`](crate::prelude::NextState) + /// resource with `state`. + /// + /// Note that commands introduce sync points to the ECS schedule, so modifying `NextState` + /// directly may be more efficient depending on your use-case. + fn set_state(&mut self, state: S); +} + +impl CommandsStatesExt for Commands<'_, '_> { + fn set_state(&mut self, state: S) { + self.add(move |w: &mut World| { + let mut next = w.resource_mut::>(); + if let NextState::Pending(prev) = &*next { + if *prev != state { + debug!("overwriting next state {:?} with {:?}", prev, state); + } + } + next.set(state); + }); + } +} diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index e8dc8264ef..88598db0b9 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -34,6 +34,8 @@ #[cfg(feature = "bevy_app")] /// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with state installation methods pub mod app; +/// Provides extension methods for [`Commands`](bevy_ecs::prelude::Commands). +pub mod commands; /// Provides definitions for the runtime conditions that interact with the state system pub mod condition; /// Provides definitions for the basic traits required by the state system @@ -55,6 +57,8 @@ pub mod prelude { #[doc(hidden)] pub use crate::app::AppExtStates; #[doc(hidden)] + pub use crate::commands::CommandsStatesExt; + #[doc(hidden)] pub use crate::condition::*; #[cfg(feature = "bevy_reflect")] #[doc(hidden)] From dac4a5bbb4336527ffd0880233b92f5bc4f93c54 Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Tue, 10 Sep 2024 02:01:14 +1000 Subject: [PATCH 013/115] Depreciate `LoadAndSave` Asset Processor (#15090) # Objective - Fixes #15060 ## Solution - Added `IdentityAssetTransformer` which is an `AssetTransformer` which infallibly returns the input `Asset` unmodified. - Replaced `LoadAndSave` and `LoadAndSaveSettings` with type definitions linking back to `LoadTransformAndSave` and `LoadTransformAndSaveSettings` respectively. - Marked `LoadAndSave` and `LoadAndSaveSettings` as depreciated with a migration guide included, hinting to the user to use the underlying type instead. ## Testing - Ran CI locally --- ## Migration Guide - Replace `LoadAndSave` with `LoadTransformAndSave::Asset>, S>` - Replace `LoadAndSaveSettings` with `LoadTransformAndSaveSettings` --- crates/bevy_asset/src/processor/process.rs | 74 +++++++--------------- crates/bevy_asset/src/transformer.rs | 36 +++++++++++ crates/bevy_render/src/texture/mod.rs | 15 +++-- 3 files changed, 70 insertions(+), 55 deletions(-) diff --git a/crates/bevy_asset/src/processor/process.rs b/crates/bevy_asset/src/processor/process.rs index 349c047005..92a13f21ff 100644 --- a/crates/bevy_asset/src/processor/process.rs +++ b/crates/bevy_asset/src/processor/process.rs @@ -1,4 +1,5 @@ use crate::io::SliceReader; +use crate::transformer::IdentityAssetTransformer; use crate::{ io::{ AssetReaderError, AssetWriterError, MissingAssetWriterError, @@ -47,6 +48,11 @@ pub trait Process: Send + Sync + Sized + 'static { /// an [`AssetSaver`] that allows you save any `S` asset. However you can /// also implement [`Process`] directly if [`LoadTransformAndSave`] feels limiting or unnecessary. /// +/// If your [`Process`] does not need to transform the [`Asset`], you can use [`IdentityAssetTransformer`] as `T`. +/// This will directly return the input [`Asset`], allowing your [`Process`] to directly load and then save an [`Asset`]. +/// However, this pattern should only be used for cases such as file format conversion. +/// Otherwise, consider refactoring your [`AssetLoader`] and [`AssetSaver`] to isolate the transformation step into an explicit [`AssetTransformer`]. +/// /// This uses [`LoadTransformAndSaveSettings`] to configure the processor. /// /// [`Asset`]: crate::Asset @@ -60,6 +66,18 @@ pub struct LoadTransformAndSave< marker: PhantomData L>, } +impl> From + for LoadTransformAndSave, S> +{ + fn from(value: S) -> Self { + LoadTransformAndSave { + transformer: IdentityAssetTransformer::new(), + saver: value, + marker: PhantomData, + } + } +} + /// Settings for the [`LoadTransformAndSave`] [`Process::Settings`] implementation. /// /// `LoaderSettings` corresponds to [`AssetLoader::Settings`], `TransformerSettings` corresponds to [`AssetTransformer::Settings`], @@ -98,30 +116,16 @@ impl< /// This uses [`LoadAndSaveSettings`] to configure the processor. /// /// [`Asset`]: crate::Asset -pub struct LoadAndSave> { - saver: S, - marker: PhantomData L>, -} - -impl> From for LoadAndSave { - fn from(value: S) -> Self { - LoadAndSave { - saver: value, - marker: PhantomData, - } - } -} +#[deprecated = "Use `LoadTransformAndSave::Asset>, S>` instead"] +pub type LoadAndSave = + LoadTransformAndSave::Asset>, S>; /// Settings for the [`LoadAndSave`] [`Process::Settings`] implementation. /// /// `LoaderSettings` corresponds to [`AssetLoader::Settings`] and `SaverSettings` corresponds to [`AssetSaver::Settings`]. -#[derive(Serialize, Deserialize, Default)] -pub struct LoadAndSaveSettings { - /// The [`AssetLoader::Settings`] for [`LoadAndSave`]. - pub loader_settings: LoaderSettings, - /// The [`AssetSaver::Settings`] for [`LoadAndSave`]. - pub saver_settings: SaverSettings, -} +#[deprecated = "Use `LoadTransformAndSaveSettings` instead"] +pub type LoadAndSaveSettings = + LoadTransformAndSaveSettings; /// An error that is encountered during [`Process::process`]. #[derive(Error, Debug)] @@ -213,36 +217,6 @@ where } } -impl> Process - for LoadAndSave -{ - type Settings = LoadAndSaveSettings; - type OutputLoader = Saver::OutputLoader; - - async fn process<'a>( - &'a self, - context: &'a mut ProcessContext<'_>, - meta: AssetMeta<(), Self>, - writer: &'a mut Writer, - ) -> Result<::Settings, ProcessError> { - let AssetAction::Process { settings, .. } = meta.asset else { - return Err(ProcessError::WrongMetaType); - }; - let loader_meta = AssetMeta::::new(AssetAction::Load { - loader: std::any::type_name::().to_string(), - settings: settings.loader_settings, - }); - let loaded_asset = context.load_source_asset(loader_meta).await?; - let saved_asset = SavedAsset::::from_loaded(&loaded_asset).unwrap(); - let output_settings = self - .saver - .save(writer, saved_asset, &settings.saver_settings) - .await - .map_err(|error| ProcessError::AssetSaveError(error.into()))?; - Ok(output_settings) - } -} - /// A type-erased variant of [`Process`] that enables interacting with processor implementations without knowing /// their type. pub trait ErasedProcessor: Send + Sync { diff --git a/crates/bevy_asset/src/transformer.rs b/crates/bevy_asset/src/transformer.rs index 1b2cd92991..21abb3331b 100644 --- a/crates/bevy_asset/src/transformer.rs +++ b/crates/bevy_asset/src/transformer.rs @@ -4,7 +4,9 @@ use bevy_utils::{ConditionalSendFuture, HashMap}; use serde::{Deserialize, Serialize}; use std::{ borrow::Borrow, + convert::Infallible, hash::Hash, + marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -241,3 +243,37 @@ impl<'a, A: Asset> TransformedSubAsset<'a, A> { self.labeled_assets.keys().map(|s| &**s) } } + +/// An identity [`AssetTransformer`] which infallibly returns the input [`Asset`] on transformation.] +pub struct IdentityAssetTransformer { + _phantom: PhantomData A>, +} + +impl IdentityAssetTransformer { + pub const fn new() -> Self { + Self { + _phantom: PhantomData, + } + } +} + +impl Default for IdentityAssetTransformer { + fn default() -> Self { + Self::new() + } +} + +impl AssetTransformer for IdentityAssetTransformer { + type AssetInput = A; + type AssetOutput = A; + type Settings = (); + type Error = Infallible; + + async fn transform<'a>( + &'a self, + asset: TransformedAsset, + _settings: &'a Self::Settings, + ) -> Result, Self::Error> { + Ok(asset) + } +} diff --git a/crates/bevy_render/src/texture/mod.rs b/crates/bevy_render/src/texture/mod.rs index 9b1a592773..566579d7c8 100644 --- a/crates/bevy_render/src/texture/mod.rs +++ b/crates/bevy_render/src/texture/mod.rs @@ -107,11 +107,16 @@ impl Plugin for ImagePlugin { .world() .get_resource::() { - processor.register_processor::>( - CompressedImageSaver.into(), - ); - processor - .set_default_processor::>("png"); + processor.register_processor::, + CompressedImageSaver, + >>(CompressedImageSaver.into()); + processor.set_default_processor::, + CompressedImageSaver, + >>("png"); } if let Some(render_app) = app.get_sub_app_mut(RenderApp) { From 29c632b5242980cd69895140c0060acfb93918cf Mon Sep 17 00:00:00 2001 From: Han Damin Date: Tue, 10 Sep 2024 01:04:41 +0900 Subject: [PATCH 014/115] Add common aspect ratio constants and improve documentation (#15091) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hello, I'd like to contribute to this project by adding some useful constants and improving the documentation for the AspectRatio struct. Here's a summary of the changes I've made: 1. Added new constants for common aspect ratios: - SIXTEEN_NINE (16:9) - FOUR_THREE (4:3) - ULTRAWIDE (21:9) 2. Enhanced the overall documentation: - Improved module-level documentation with an overview and use cases - Expanded explanation of the AspectRatio struct with examples - Added detailed descriptions and examples for all methods (both existing and new) - Included explanations for the newly introduced constant values - Added clarifications for From trait implementations These changes aim to make the AspectRatio API more user-friendly and easier to understand. The new constants provide convenient access to commonly used aspect ratios, which I believe will be helpful in many scenarios. --------- Co-authored-by: Gonçalo Rica Pais da Silva Co-authored-by: Lixou <82600264+DasLixou@users.noreply.github.com> --- .../bevy_core_pipeline/src/bloom/settings.rs | 4 +- crates/bevy_math/src/aspect_ratio.rs | 84 +++++++++++++++++-- crates/bevy_pbr/src/cluster/mod.rs | 5 +- crates/bevy_render/src/camera/projection.rs | 4 +- crates/bevy_render/src/texture/image.rs | 4 +- 5 files changed, 87 insertions(+), 14 deletions(-) diff --git a/crates/bevy_core_pipeline/src/bloom/settings.rs b/crates/bevy_core_pipeline/src/bloom/settings.rs index 8fe6628214..4e8e0ff1f7 100644 --- a/crates/bevy_core_pipeline/src/bloom/settings.rs +++ b/crates/bevy_core_pipeline/src/bloom/settings.rs @@ -229,7 +229,9 @@ impl ExtractComponent for BloomSettings { viewport: UVec4::new(origin.x, origin.y, size.x, size.y).as_vec4() / UVec4::new(target_size.x, target_size.y, target_size.x, target_size.y) .as_vec4(), - aspect: AspectRatio::from_pixels(size.x, size.y).into(), + aspect: AspectRatio::try_from_pixels(size.x, size.y) + .expect("Valid screen size values for Bloom settings") + .ratio(), uv_offset: settings.uv_offset, }; diff --git a/crates/bevy_math/src/aspect_ratio.rs b/crates/bevy_math/src/aspect_ratio.rs index 97960015a5..5b0fc47946 100644 --- a/crates/bevy_math/src/aspect_ratio.rs +++ b/crates/bevy_math/src/aspect_ratio.rs @@ -1,6 +1,7 @@ //! Provides a simple aspect ratio struct to help with calculations. use crate::Vec2; +use thiserror::Error; #[cfg(feature = "bevy_reflect")] use bevy_reflect::Reflect; @@ -11,23 +12,74 @@ use bevy_reflect::Reflect; pub struct AspectRatio(f32); impl AspectRatio { - /// Create a new `AspectRatio` from a given `width` and `height`. + /// Standard 16:9 aspect ratio + pub const SIXTEEN_NINE: Self = Self(16.0 / 9.0); + /// Standard 4:3 aspect ratio + pub const FOUR_THREE: Self = Self(4.0 / 3.0); + /// Standard 21:9 ultrawide aspect ratio + pub const ULTRAWIDE: Self = Self(21.0 / 9.0); + + /// Attempts to create a new [`AspectRatio`] from a given width and height. + /// + /// # Errors + /// + /// Returns an `Err` with `AspectRatioError` if: + /// - Either width or height is zero (`AspectRatioError::Zero`) + /// - Either width or height is infinite (`AspectRatioError::Infinite`) + /// - Either width or height is NaN (`AspectRatioError::NaN`) #[inline] - pub fn new(width: f32, height: f32) -> Self { - Self(width / height) + pub fn try_new(width: f32, height: f32) -> Result { + match (width, height) { + (w, h) if w == 0.0 || h == 0.0 => Err(AspectRatioError::Zero), + (w, h) if w.is_infinite() || h.is_infinite() => Err(AspectRatioError::Infinite), + (w, h) if w.is_nan() || h.is_nan() => Err(AspectRatioError::NaN), + _ => Ok(Self(width / height)), + } } - /// Create a new `AspectRatio` from a given amount of `x` pixels and `y` pixels. + /// Attempts to create a new [`AspectRatio`] from a given amount of x pixels and y pixels. #[inline] - pub fn from_pixels(x: u32, y: u32) -> Self { - Self::new(x as f32, y as f32) + pub fn try_from_pixels(x: u32, y: u32) -> Result { + Self::try_new(x as f32, y as f32) + } + + /// Returns the aspect ratio as a f32 value. + #[inline] + pub fn ratio(&self) -> f32 { + self.0 + } + + /// Returns the inverse of this aspect ratio (height/width). + #[inline] + pub fn inverse(&self) -> Self { + Self(1.0 / self.0) + } + + /// Returns true if the aspect ratio represents a landscape orientation. + #[inline] + pub fn is_landscape(&self) -> bool { + self.0 > 1.0 + } + + /// Returns true if the aspect ratio represents a portrait orientation. + #[inline] + pub fn is_portrait(&self) -> bool { + self.0 < 1.0 + } + + /// Returns true if the aspect ratio is exactly square. + #[inline] + pub fn is_square(&self) -> bool { + self.0 == 1.0 } } -impl From for AspectRatio { +impl TryFrom for AspectRatio { + type Error = AspectRatioError; + #[inline] - fn from(value: Vec2) -> Self { - Self::new(value.x, value.y) + fn try_from(value: Vec2) -> Result { + Self::try_new(value.x, value.y) } } @@ -37,3 +89,17 @@ impl From for f32 { aspect_ratio.0 } } + +/// An Error type for when [`super::AspectRatio`] is provided invalid width or height values +#[derive(Error, Debug, PartialEq, Eq, Clone, Copy)] +pub enum AspectRatioError { + /// Error due to width or height having zero as a value. + #[error("AspectRatio error: width or height is zero")] + Zero, + /// Error due towidth or height being infinite. + #[error("AspectRatio error: width or height is infinite")] + Infinite, + /// Error due to width or height being Not a Number (NaN). + #[error("AspectRatio error: width or height is NaN")] + NaN, +} diff --git a/crates/bevy_pbr/src/cluster/mod.rs b/crates/bevy_pbr/src/cluster/mod.rs index 7da6c5da02..5ebb19dc3d 100644 --- a/crates/bevy_pbr/src/cluster/mod.rs +++ b/crates/bevy_pbr/src/cluster/mod.rs @@ -257,8 +257,9 @@ impl ClusterConfig { ClusterConfig::FixedZ { total, z_slices, .. } => { - let aspect_ratio: f32 = - AspectRatio::from_pixels(screen_size.x, screen_size.y).into(); + let aspect_ratio: f32 = AspectRatio::try_from_pixels(screen_size.x, screen_size.y) + .expect("Failed to calculate aspect ratio for Cluster: screen dimensions must be positive, non-zero values") + .ratio(); let mut z_slices = *z_slices; if *total < z_slices { warn!("ClusterConfig has more z-slices than total clusters!"); diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index 2aa5f8acc0..bb6b047690 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -190,7 +190,9 @@ impl CameraProjection for PerspectiveProjection { } fn update(&mut self, width: f32, height: f32) { - self.aspect_ratio = AspectRatio::new(width, height).into(); + self.aspect_ratio = AspectRatio::try_new(width, height) + .expect("Failed to update PerspectiveProjection: width and height must be positive, non-zero values") + .ratio(); } fn far(&self) -> f32 { diff --git a/crates/bevy_render/src/texture/image.rs b/crates/bevy_render/src/texture/image.rs index 8d92395b63..55b1560a90 100644 --- a/crates/bevy_render/src/texture/image.rs +++ b/crates/bevy_render/src/texture/image.rs @@ -666,7 +666,9 @@ impl Image { /// Returns the aspect ratio (width / height) of a 2D image. #[inline] pub fn aspect_ratio(&self) -> AspectRatio { - AspectRatio::from_pixels(self.width(), self.height()) + AspectRatio::try_from_pixels(self.width(), self.height()).expect( + "Failed to calculate aspect ratio: Image dimensions must be positive, non-zero values", + ) } /// Returns the size of a 2D image as f32. From 9b006fdf751e2f9bfdc3a66c98b29ee95aa7f0c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=A0=94=E7=A9=B6=E7=A4=BE=E4=BA=A4?= Date: Tue, 10 Sep 2024 00:11:16 +0800 Subject: [PATCH 015/115] bevy_pbr: Make choosing of diffuse indirect lighting explicit. (#15093) # Objective Make choosing of diffuse indirect lighting explicit, instead of using numerical conditions like `all(indirect_light == vec3(0.0f))`, as using that may lead to unwanted light leakage. ## Solution Use an explicit `found_diffuse_indirect` condition to indicate the found indirect lighting source. ## Testing I have tested examples `lightmaps`, `irradiance_volumes` and `reflection_probes`, there are no visual changes. For further testing, consider a "cave" scene with lightmaps and irradiance volumes. In the cave there are some purly dark occluded area, those dark area will sample the irradiance volume, and that is easy to leak light. --- crates/bevy_pbr/src/render/pbr_functions.wgsl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 35e683c4ff..268a50163f 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -545,19 +545,20 @@ fn apply_pbr_lighting( // example, both lightmaps and irradiance volumes are present. var indirect_light = vec3(0.0f); + var found_diffuse_indirect = false; #ifdef LIGHTMAP - if (all(indirect_light == vec3(0.0f))) { - indirect_light += in.lightmap_light * diffuse_color; - } + indirect_light += in.lightmap_light * diffuse_color; + found_diffuse_indirect = true; #endif -#ifdef IRRADIANCE_VOLUME { +#ifdef IRRADIANCE_VOLUME // Irradiance volume light (indirect) - if (all(indirect_light == vec3(0.0f))) { + if (!found_diffuse_indirect) { let irradiance_volume_light = irradiance_volume::irradiance_volume_light( in.world_position.xyz, in.N); indirect_light += irradiance_volume_light * diffuse_color * diffuse_occlusion; + found_diffuse_indirect = true; } #endif @@ -574,7 +575,7 @@ fn apply_pbr_lighting( let environment_light = environment_map::environment_map_light( environment_map_lighting_input, - any(indirect_light != vec3(0.0f)) + found_diffuse_indirect ); // If screen space reflections are going to be used for this material, don't @@ -589,7 +590,7 @@ fn apply_pbr_lighting( if (!use_ssr) { let environment_light = environment_map::environment_map_light( &lighting_input, - any(indirect_light != vec3(0.0f)) + found_diffuse_indirect ); indirect_light += environment_light.diffuse * diffuse_occlusion + From 66b5128b6f3f50a505b241797d409873a340ed1d Mon Sep 17 00:00:00 2001 From: Marco Meijer <46689298+MarcoMeijer@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:16:33 +0200 Subject: [PATCH 016/115] Add rect field to UI image (#15095) # Objective Fixes #14424 ## Solution Add a rect field to UiImage, and update the extraction of ui images and slices. ## Testing I tested all possible combinations of having a rect, using a texture atlas, setting image scale mode to sliced and image scale mode to tiled. See the showcase section. --- ## Showcase Screenshot 2024-09-08 at 16 23 05
Click to view showcase ```rust use bevy::prelude::*; fn main() { App::new() .add_plugins(DefaultPlugins.set(ImagePlugin::default_nearest())) .add_systems(Startup, create_ui) .run(); } fn create_ui( mut commands: Commands, assets: Res, mut texture_atlas_layouts: ResMut>, ) { let texture = assets.load("textures/fantasy_ui_borders/numbered_slices.png"); let layout = TextureAtlasLayout::from_grid(UVec2::splat(16), 3, 3, None, None); let texture_atlas_layout = texture_atlas_layouts.add(layout); commands.spawn(Camera2dBundle::default()); let style = Style { width: Val::Px(96.), height: Val::Px(96.), ..default() }; commands .spawn(NodeBundle { ..default() }) .with_children(|parent| { // nothing parent.spawn(ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }); // with rect parent.spawn(ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }); // with rect and texture atlas parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, )); // with texture atlas parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, )); // with texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(16.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with rect and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(2.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with rect, texture atlas and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(1.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with texture atlas and texture slicer parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, ImageScaleMode::Sliced(TextureSlicer { border: BorderRect::square(2.), center_scale_mode: SliceScaleMode::Stretch, sides_scale_mode: SliceScaleMode::Stretch, max_corner_scale: 1., }), )); // with tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with rect and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 16., 16.)), style: style.clone(), ..default() }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with rect, texture atlas and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()).with_rect(Rect::new(0., 0., 8., 8.)), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 1, }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); // with texture atlas and tiled parent.spawn(( ImageBundle { image: UiImage::new(texture.clone()), style: style.clone(), ..default() }, TextureAtlas { layout: texture_atlas_layout.clone(), index: 2, }, ImageScaleMode::Tiled { tile_x: true, tile_y: true, stretch_value: 1., }, )); }); } ```
--- crates/bevy_ui/src/render/mod.rs | 42 +++++++++++-------- .../src/render/ui_texture_slice_pipeline.rs | 19 ++++++--- crates/bevy_ui/src/ui_node.rs | 14 +++++++ 3 files changed, 52 insertions(+), 23 deletions(-) diff --git a/crates/bevy_ui/src/render/mod.rs b/crates/bevy_ui/src/render/mod.rs index cd9c66727a..668d722e2f 100644 --- a/crates/bevy_ui/src/render/mod.rs +++ b/crates/bevy_ui/src/render/mod.rs @@ -329,25 +329,31 @@ pub fn extract_uinode_images( continue; } - let (rect, atlas_scaling) = match atlas { - Some(atlas) => { - let Some(layout) = texture_atlases.get(&atlas.layout) else { - // Atlas not present in assets resource (should this warn the user?) - continue; - }; - let mut atlas_rect = layout.textures[atlas.index].as_rect(); - let atlas_scaling = uinode.size() / atlas_rect.size(); - atlas_rect.min *= atlas_scaling; - atlas_rect.max *= atlas_scaling; - (atlas_rect, Some(atlas_scaling)) + let atlas_rect = atlas + .and_then(|s| s.texture_rect(&texture_atlases)) + .map(|r| r.as_rect()); + + let mut rect = match (atlas_rect, image.rect) { + (None, None) => Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + (None, Some(image_rect)) => image_rect, + (Some(atlas_rect), None) => atlas_rect, + (Some(atlas_rect), Some(mut image_rect)) => { + image_rect.min += atlas_rect.min; + image_rect.max += atlas_rect.min; + image_rect } - None => ( - Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - None, - ), + }; + + let atlas_scaling = if atlas_rect.is_some() || image.rect.is_some() { + let atlas_scaling = uinode.size() / rect.size(); + rect.min *= atlas_scaling; + rect.max *= atlas_scaling; + Some(atlas_scaling) + } else { + None }; let ui_logical_viewport_size = camera_query diff --git a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs index ce60087cf2..c712d78386 100644 --- a/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_texture_slice_pipeline.rs @@ -275,11 +275,20 @@ pub fn extract_ui_texture_slices( continue; } - let atlas_rect = atlas.and_then(|atlas| { - texture_atlases - .get(&atlas.layout) - .map(|layout| layout.textures[atlas.index].as_rect()) - }); + let atlas_rect = atlas + .and_then(|s| s.texture_rect(&texture_atlases)) + .map(|r| r.as_rect()); + + let atlas_rect = match (atlas_rect, image.rect) { + (None, None) => None, + (None, Some(image_rect)) => Some(image_rect), + (Some(atlas_rect), None) => Some(atlas_rect), + (Some(atlas_rect), Some(mut image_rect)) => { + image_rect.min += atlas_rect.min; + image_rect.max += atlas_rect.min; + Some(image_rect) + } + }; extracted_ui_slicers.slices.insert( commands.spawn_empty().id(), diff --git a/crates/bevy_ui/src/ui_node.rs b/crates/bevy_ui/src/ui_node.rs index eb6e6fed99..f43c45bf6c 100644 --- a/crates/bevy_ui/src/ui_node.rs +++ b/crates/bevy_ui/src/ui_node.rs @@ -1837,6 +1837,12 @@ pub struct UiImage { pub flip_x: bool, /// Whether the image should be flipped along its y-axis pub flip_y: bool, + /// An optional rectangle representing the region of the image to render, instead of rendering + /// the full image. This is an easy one-off alternative to using a [`TextureAtlas`](bevy_sprite::TextureAtlas). + /// + /// When used with a [`TextureAtlas`](bevy_sprite::TextureAtlas), the rect + /// is offset by the atlas's minimal (top-left) corner position. + pub rect: Option, } impl Default for UiImage { @@ -1856,6 +1862,7 @@ impl Default for UiImage { texture: TRANSPARENT_IMAGE_HANDLE, flip_x: false, flip_y: false, + rect: None, } } } @@ -1879,6 +1886,7 @@ impl UiImage { color, flip_x: false, flip_y: false, + rect: None, } } @@ -1902,6 +1910,12 @@ impl UiImage { self.flip_y = true; self } + + #[must_use] + pub const fn with_rect(mut self, rect: Rect) -> Self { + self.rect = Some(rect); + self + } } impl From> for UiImage { From 4de9edeaa65227ad0bc37024c0c7f3ae9032a006 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 9 Sep 2024 17:18:37 +0100 Subject: [PATCH 017/115] Retrieve the `stack_index` from `Node` in `extract_ui_material_nodes` instead of walking `UiStack` (#15104) # Objective `ExtractedUiMaterialNode` is still walking the whole `UiStack`. more info: https://github.com/bevyengine/bevy/pull/9853 ## Solution Retrieve the `stack_index` from the `Node` component instead. Also changed the `stack_index` field of `ExtractedUiMaterialNode` to `u32`. --- .../src/render/ui_material_pipeline.rs | 104 ++++++++---------- 1 file changed, 48 insertions(+), 56 deletions(-) diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index bcdccf15fc..ef81d3c33f 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -327,7 +327,7 @@ impl RenderCommand

for DrawUiMaterialNode { } pub struct ExtractedUiMaterialNode { - pub stack_index: usize, + pub stack_index: u32, pub transform: Mat4, pub rect: Rect, pub border: [f32; 4], @@ -355,7 +355,6 @@ impl Default for ExtractedUiMaterialNodes { pub fn extract_ui_material_nodes( mut extracted_uinodes: ResMut>, materials: Extract>>, - ui_stack: Extract>, default_ui_camera: Extract, uinode_query: Extract< Query< @@ -386,61 +385,54 @@ pub fn extract_ui_material_nodes( // If there is only one camera, we use it as default let default_single_camera = default_ui_camera.get(); - for (stack_index, entity) in ui_stack.uinodes.iter().enumerate() { - if let Ok((entity, uinode, style, transform, handle, view_visibility, clip, camera)) = - uinode_query.get(*entity) - { - let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) - else { - continue; - }; - - // skip invisible nodes - if !view_visibility.get() { - continue; - } - - // Skip loading materials - if !materials.contains(handle) { - continue; - } - - // Both vertical and horizontal percentage border values are calculated based on the width of the parent node - // - let parent_width = uinode.size().x; - let left = - resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size) - / uinode.size().x; - let right = resolve_border_thickness( - style.border.right, - parent_width, - ui_logical_viewport_size, - ) / uinode.size().x; - let top = - resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size) - / uinode.size().y; - let bottom = resolve_border_thickness( - style.border.bottom, - parent_width, - ui_logical_viewport_size, - ) / uinode.size().y; - - extracted_uinodes.uinodes.insert( - entity, - ExtractedUiMaterialNode { - stack_index, - transform: transform.compute_matrix(), - material: handle.id(), - rect: Rect { - min: Vec2::ZERO, - max: uinode.calculated_size, - }, - border: [left, right, top, bottom], - clip: clip.map(|clip| clip.clip), - camera_entity, - }, - ); + for (entity, uinode, style, transform, handle, view_visibility, clip, camera) in + uinode_query.iter() + { + let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else { + continue; }; + + // skip invisible nodes + if !view_visibility.get() { + continue; + } + + // Skip loading materials + if !materials.contains(handle) { + continue; + } + + // Both vertical and horizontal percentage border values are calculated based on the width of the parent node + // + let parent_width = uinode.size().x; + let left = + resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size) + / uinode.size().x; + let right = + resolve_border_thickness(style.border.right, parent_width, ui_logical_viewport_size) + / uinode.size().x; + let top = + resolve_border_thickness(style.border.top, parent_width, ui_logical_viewport_size) + / uinode.size().y; + let bottom = + resolve_border_thickness(style.border.bottom, parent_width, ui_logical_viewport_size) + / uinode.size().y; + + extracted_uinodes.uinodes.insert( + entity, + ExtractedUiMaterialNode { + stack_index: uinode.stack_index, + transform: transform.compute_matrix(), + material: handle.id(), + rect: Rect { + min: Vec2::ZERO, + max: uinode.calculated_size, + }, + border: [left, right, top, bottom], + clip: clip.map(|clip| clip.clip), + camera_entity, + }, + ); } } From eaa87b8c34ecdd09ba22b77e8bde996566ec9254 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 16:22:29 +0000 Subject: [PATCH 018/115] Bump crate-ci/typos from 1.24.3 to 1.24.5 (#15111) Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.24.3 to 1.24.5.

Release notes

Sourced from crate-ci/typos's releases.

v1.24.5

[1.24.5] - 2024-09-04

Features

  • (action) Support windows

v1.24.4

[1.24.4] - 2024-09-03

Fixes

  • Offer a correction for grather
Changelog

Sourced from crate-ci/typos's changelog.

[1.24.5] - 2024-09-04

Features

  • (action) Support windows

[1.24.4] - 2024-09-03

Fixes

  • Offer a correction for grather
Commits
  • 945d407 chore: Release
  • e972e95 docs: Update changelog
  • fcfade4 Merge pull request #1095 from zyf722/actions-windows
  • 264f549 feat(action): Add Windows support to actions
  • 853bbe8 chore: Release
  • b5f5660 docs: Update changelog
  • 39f678e Merge pull request #1093 from kachick/grather-suggest-candidates
  • bb6905f Merge pull request #1092 from crate-ci/renovate/embarkstudios-cargo-deny-acti...
  • 786c825 fix(dict): Add suggestions for the typo "grather"
  • 3400581 chore(deps): Update EmbarkStudios/cargo-deny-action action to v2
  • See full diff in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=crate-ci/typos&package-manager=github_actions&previous-version=1.24.3&new-version=1.24.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 500eb34bb9..952877b9f1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -219,7 +219,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Check for typos - uses: crate-ci/typos@v1.24.3 + uses: crate-ci/typos@v1.24.5 - name: Typos info if: failure() run: | From 85e41ddaced1dec943c4f5fc3cbad858ac6e94ac Mon Sep 17 00:00:00 2001 From: Zachary Harrold Date: Tue, 10 Sep 2024 02:23:14 +1000 Subject: [PATCH 019/115] Add `observer` to `Trigger` (#15066) # Objective - Fixes #15061 ## Solution - Added `observer` to `Trigger`, which returns the entity observing the triggered event. ## Testing - CI passed locally. --- crates/bevy_ecs/src/observer/mod.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/crates/bevy_ecs/src/observer/mod.rs b/crates/bevy_ecs/src/observer/mod.rs index 4711957af9..2571157302 100644 --- a/crates/bevy_ecs/src/observer/mod.rs +++ b/crates/bevy_ecs/src/observer/mod.rs @@ -56,11 +56,37 @@ impl<'w, E, B: Bundle> Trigger<'w, E, B> { Ptr::from(&self.event) } - /// Returns the entity that triggered the observer, could be [`Entity::PLACEHOLDER`]. + /// Returns the [`Entity`] that triggered the observer, could be [`Entity::PLACEHOLDER`]. pub fn entity(&self) -> Entity { self.trigger.entity } + /// Returns the [`Entity`] that observed the triggered event. + /// This allows you to despawn the observer, ceasing observation. + /// + /// # Examples + /// + /// ```rust + /// # use bevy_ecs::prelude::{Commands, Trigger}; + /// # + /// # struct MyEvent { + /// # done: bool, + /// # } + /// # + /// /// Handle `MyEvent` and if it is done, stop observation. + /// fn my_observer(trigger: Trigger, mut commands: Commands) { + /// if trigger.event().done { + /// commands.entity(trigger.observer()).despawn(); + /// return; + /// } + /// + /// // ... + /// } + /// ``` + pub fn observer(&self) -> Entity { + self.trigger.observer + } + /// Enables or disables event propagation, allowing the same event to trigger observers on a chain of different entities. /// /// The path an event will propagate along is specified by its associated [`Traversal`] component. By default, events From 5adacf014c9510285365b3325c9e6faa34d7fcce Mon Sep 17 00:00:00 2001 From: Tim Date: Mon, 9 Sep 2024 16:24:39 +0000 Subject: [PATCH 020/115] Use associated type bounds for `iter_many` and friends (#15040) # Objective Make the bounds for these query methods less intimidating. Continuation of #14107 My last pr was back in february :skull: --- crates/bevy_ecs/src/query/state.rs | 25 ++++++++----------------- crates/bevy_ecs/src/system/query.rs | 21 ++++++--------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/crates/bevy_ecs/src/query/state.rs b/crates/bevy_ecs/src/query/state.rs index 93d7155149..9b4e108624 100644 --- a/crates/bevy_ecs/src/query/state.rs +++ b/crates/bevy_ecs/src/query/state.rs @@ -1174,14 +1174,11 @@ impl QueryState { /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many<'w, 's, EntityList: IntoIterator>( + pub fn iter_many<'w, 's, EntityList: IntoIterator>>( &'s mut self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { self.update_archetypes(world); // SAFETY: query is read only unsafe { @@ -1209,14 +1206,11 @@ impl QueryState { /// - [`iter_many`](Self::iter_many) to update archetypes. /// - [`iter_manual`](Self::iter_manual) to iterate over all query items. #[inline] - pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_manual<'w, 's, EntityList: IntoIterator>>( &'s self, world: &'w World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'w, 's, D::ReadOnly, F, EntityList::IntoIter> { self.validate_world(world.id()); // SAFETY: query is read only, world id is validated unsafe { @@ -1234,14 +1228,11 @@ impl QueryState { /// Items are returned in the order of the list of entities. /// Entities that don't match the query are skipped. #[inline] - pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>( + pub fn iter_many_mut<'w, 's, EntityList: IntoIterator>>( &'s mut self, world: &'w mut World, entities: EntityList, - ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> { self.update_archetypes(world); let change_tick = world.change_tick(); let last_change_tick = world.last_change_tick(); @@ -1334,7 +1325,7 @@ impl QueryState { /// This does not validate that `world.id()` matches `self.world_id`. Calling this on a `world` /// with a mismatched [`WorldId`] is unsound. #[inline] - pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList: IntoIterator>( + pub(crate) unsafe fn iter_many_unchecked_manual<'w, 's, EntityList>( &'s self, entities: EntityList, world: UnsafeWorldCell<'w>, @@ -1342,7 +1333,7 @@ impl QueryState { this_run: Tick, ) -> QueryManyIter<'w, 's, D, F, EntityList::IntoIter> where - EntityList::Item: Borrow, + EntityList: IntoIterator>, { QueryManyIter::new(world, self, entities, last_run, this_run) } diff --git a/crates/bevy_ecs/src/system/query.rs b/crates/bevy_ecs/src/system/query.rs index ffe595b2cf..ddff01e19f 100644 --- a/crates/bevy_ecs/src/system/query.rs +++ b/crates/bevy_ecs/src/system/query.rs @@ -616,13 +616,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// /// - [`iter_many_mut`](Self::iter_many_mut) to get mutable query items. #[inline] - pub fn iter_many( + pub fn iter_many>>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'_, 's, D::ReadOnly, F, EntityList::IntoIter> { // SAFETY: // - `self.world` has permission to access the required components. // - The query is read-only, so it can be aliased even if it was originally mutable. @@ -670,13 +667,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # bevy_ecs::system::assert_is_system(system); /// ``` #[inline] - pub fn iter_many_mut( + pub fn iter_many_mut>>( &mut self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { // SAFETY: `self.world` has permission to access the required components. unsafe { self.state.iter_many_unchecked_manual( @@ -752,13 +746,10 @@ impl<'w, 's, D: QueryData, F: QueryFilter> Query<'w, 's, D, F> { /// # See also /// /// - [`iter_many_mut`](Self::iter_many_mut) to safely access the query items. - pub unsafe fn iter_many_unsafe( + pub unsafe fn iter_many_unsafe>>( &self, entities: EntityList, - ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> - where - EntityList::Item: Borrow, - { + ) -> QueryManyIter<'_, 's, D, F, EntityList::IntoIter> { // SAFETY: // - `self.world` has permission to access the required components. // - The caller ensures that this operation will not result in any aliased mutable accesses. From 245d03a78ad3988f23ef467ad0c49fe0c7349fa6 Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Mon, 9 Sep 2024 09:26:17 -0700 Subject: [PATCH 021/115] bevy_reflect: Update `on_unimplemented` attributes (#15110) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective Some of the new compile error messages are a little unclear (at least to me). For example: ``` error[E0277]: `tests::foo::Bar` can not be created through reflection --> crates/bevy_reflect/src/lib.rs:679:18 | 679 | #[derive(Reflect)] | ^^^^^^^ the trait `from_reflect::FromReflect` is not implemented for `tests::foo::Bar` | = note: consider annotating `tests::foo::Bar` with `#[derive(Reflect)]` or `#[derive(FromReflect)]` ``` While the annotation makes it clear that `FromReflect` is missing, it's not very clear from the main error message. My IDE lists errors with only their message immediately present:

Image of said IDE listing errors with only their
message immediately present. These errors are as follows:
\

This makes it hard to tell at a glance why my code isn't compiling. ## Solution Updated all `on_unimplemented` attributes in `bevy_reflect` to mention the relevant trait—either the actual trait or the one users actually need to implement—as well as a small snippet of what not implementing them means. For example, failing to implement `TypePath` now mentions missing a `TypePath` implementation. And failing to implement `DynamicTypePath` now also mentions missing a `TypePath` implementation, since that's the actual trait users need to implement (i.e. they shouldn't implement `DynamicTypePath` directly). Lastly, I also added some missing `on_unimplemented` attributes for `MaybeTyped` and `RegisterForReflection` (which you can see in the image above). Here's how this looks in my IDE now:

Similar image as before showing the errors listed
by the IDE. This time the errors read as follows: \

## Testing You can test by adding the following code and verifying the compile errors are correct: ```rust #[derive(Reflect)] struct Foo(Bar); struct Bar; ``` --- .../compile_fail/tests/reflect_derive/generics_fail.rs | 4 ++-- .../compile_fail/tests/reflect_remote/nested_fail.rs | 2 +- crates/bevy_reflect/src/from_reflect.rs | 4 ++-- crates/bevy_reflect/src/lib.rs | 4 ++++ crates/bevy_reflect/src/path/mod.rs | 2 +- crates/bevy_reflect/src/type_info.rs | 6 +++++- crates/bevy_reflect/src/type_path.rs | 4 ++-- crates/bevy_reflect/src/type_registry.rs | 2 +- 8 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs index 92b16288c4..6f90097828 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_derive/generics_fail.rs @@ -12,8 +12,8 @@ struct NoReflect(f32); fn main() { let mut foo: Box = Box::new(Foo:: { a: NoReflect(42.0) }); - //~^ ERROR: `NoReflect` does not provide type registration information - //~| ERROR: `NoReflect` can not provide type information through reflection + //~^ ERROR: `NoReflect` does not implement `GetTypeRegistration` so cannot provide type registration information + //~| ERROR: `NoReflect` does not implement `Typed` so cannot provide static type information // foo doesn't implement Reflect because NoReflect doesn't implement Reflect foo.get_field::("a").unwrap(); diff --git a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs index c9f85c0001..0f8ade8e23 100644 --- a/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs +++ b/crates/bevy_reflect/compile_fail/tests/reflect_remote/nested_fail.rs @@ -25,7 +25,7 @@ mod incorrect_inner_type { //~^ ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected //~| ERROR: `TheirInner` does not implement `PartialReflect` so cannot be introspected - //~| ERROR: `TheirInner` can not be used as a dynamic type path + //~| ERROR: `TheirInner` does not implement `TypePath` so cannot provide dynamic type path information //~| ERROR: `?` operator has incompatible types struct MyOuter { // Reason: Should not use `MyInner` directly diff --git a/crates/bevy_reflect/src/from_reflect.rs b/crates/bevy_reflect/src/from_reflect.rs index b1ca70f528..7a8c3b289d 100644 --- a/crates/bevy_reflect/src/from_reflect.rs +++ b/crates/bevy_reflect/src/from_reflect.rs @@ -22,8 +22,8 @@ use crate::{FromType, PartialReflect, Reflect}; /// [`DynamicStruct`]: crate::DynamicStruct /// [crate-level documentation]: crate #[diagnostic::on_unimplemented( - message = "`{Self}` can not be created through reflection", - note = "consider annotating `{Self}` with `#[derive(FromReflect)]`" + message = "`{Self}` does not implement `FromReflect` so cannot be created through reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait FromReflect: Reflect + Sized { /// Constructs a concrete instance of `Self` from a reflected value. diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index 64216bfc09..b226af4432 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -620,6 +620,10 @@ pub mod __macro_exports { /// /// This trait has a blanket implementation for all types that implement `GetTypeRegistration` /// and manual implementations for all dynamic types (which simply do nothing). + #[diagnostic::on_unimplemented( + message = "`{Self}` does not implement `GetTypeRegistration` so cannot be registered for reflection", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" + )] pub trait RegisterForReflection { #[allow(unused_variables)] fn __register(registry: &mut TypeRegistry) {} diff --git a/crates/bevy_reflect/src/path/mod.rs b/crates/bevy_reflect/src/path/mod.rs index c3d8ccfa2d..23918bed15 100644 --- a/crates/bevy_reflect/src/path/mod.rs +++ b/crates/bevy_reflect/src/path/mod.rs @@ -237,7 +237,7 @@ impl<'a> ReflectPath<'a> for &'a str { /// [`Array`]: crate::Array /// [`Enum`]: crate::Enum #[diagnostic::on_unimplemented( - message = "`{Self}` does not provide a reflection path", + message = "`{Self}` does not implement `GetPath` so cannot be accessed by reflection path", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait GetPath: PartialReflect { diff --git a/crates/bevy_reflect/src/type_info.rs b/crates/bevy_reflect/src/type_info.rs index d3450239e6..ca9963f380 100644 --- a/crates/bevy_reflect/src/type_info.rs +++ b/crates/bevy_reflect/src/type_info.rs @@ -83,7 +83,7 @@ use thiserror::Error; /// /// [utility]: crate::utility #[diagnostic::on_unimplemented( - message = "`{Self}` can not provide type information through reflection", + message = "`{Self}` does not implement `Typed` so cannot provide static type information", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait Typed: Reflect + TypePath { @@ -103,6 +103,10 @@ pub trait Typed: Reflect + TypePath { /// This trait has a blanket implementation for all types that implement `Typed` /// and manual implementations for all dynamic types (which simply return `None`). #[doc(hidden)] +#[diagnostic::on_unimplemented( + message = "`{Self}` does not implement `Typed` so cannot provide static type information", + note = "consider annotating `{Self}` with `#[derive(Reflect)]`" +)] pub trait MaybeTyped: PartialReflect { /// Returns the compile-time [info] for the underlying type, if it exists. /// diff --git a/crates/bevy_reflect/src/type_path.rs b/crates/bevy_reflect/src/type_path.rs index dd2e18cc12..bc685eb9f9 100644 --- a/crates/bevy_reflect/src/type_path.rs +++ b/crates/bevy_reflect/src/type_path.rs @@ -80,7 +80,7 @@ use std::fmt; /// [`module_path`]: TypePath::module_path /// [`type_ident`]: TypePath::type_ident #[diagnostic::on_unimplemented( - message = "`{Self}` does not have a type path", + message = "`{Self}` does not implement `TypePath` so cannot provide static type path information", note = "consider annotating `{Self}` with `#[derive(Reflect)]` or `#[derive(TypePath)]`" )] pub trait TypePath: 'static { @@ -134,7 +134,7 @@ pub trait TypePath: 'static { /// /// [`Reflect`]: crate::Reflect #[diagnostic::on_unimplemented( - message = "`{Self}` can not be used as a dynamic type path", + message = "`{Self}` does not implement `TypePath` so cannot provide dynamic type path information", note = "consider annotating `{Self}` with `#[derive(Reflect)]` or `#[derive(TypePath)]`" )] pub trait DynamicTypePath { diff --git a/crates/bevy_reflect/src/type_registry.rs b/crates/bevy_reflect/src/type_registry.rs index 1263b0bbed..1fe7208956 100644 --- a/crates/bevy_reflect/src/type_registry.rs +++ b/crates/bevy_reflect/src/type_registry.rs @@ -57,7 +57,7 @@ impl Debug for TypeRegistryArc { /// /// [crate-level documentation]: crate #[diagnostic::on_unimplemented( - message = "`{Self}` does not provide type registration information", + message = "`{Self}` does not implement `GetTypeRegistration` so cannot provide type registration information", note = "consider annotating `{Self}` with `#[derive(Reflect)]`" )] pub trait GetTypeRegistration: 'static { From 79f6fcd1ebb46e7efd092e6905a14aa2a57a9759 Mon Sep 17 00:00:00 2001 From: Christian Hughes <9044780+ItsDoot@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:29:44 -0500 Subject: [PATCH 022/115] EntityRef/Mut get_components (immutable variants only) (#15089) # Objective Smaller scoped version of #13375 without the `_mut` variants which currently have unsoundness issues. ## Solution Same as #13375, but without the `_mut` variants. ## Testing - The same test from #13375 is reused. --- ## Migration Guide - Renamed `FilteredEntityRef::components` to `FilteredEntityRef::accessed_components` and `FilteredEntityMut::components` to `FilteredEntityMut::accessed_components`. --------- Co-authored-by: Carter Anderson Co-authored-by: Periwink --- crates/bevy_ecs/src/world/entity_ref.rs | 77 ++++++++++++++++++- .../bevy_ecs/src/world/unsafe_world_cell.rs | 50 ++++++++++++ examples/ecs/dynamic.rs | 2 +- 3 files changed, 125 insertions(+), 4 deletions(-) diff --git a/crates/bevy_ecs/src/world/entity_ref.rs b/crates/bevy_ecs/src/world/entity_ref.rs index 5adfb2d302..69a6aa3ccd 100644 --- a/crates/bevy_ecs/src/world/entity_ref.rs +++ b/crates/bevy_ecs/src/world/entity_ref.rs @@ -6,7 +6,7 @@ use crate::{ entity::{Entities, Entity, EntityLocation}, event::Event, observer::{Observer, Observers}, - query::Access, + query::{Access, ReadOnlyQueryData}, removal_detection::RemovedComponentEvents, storage::Storages, system::IntoObserverSystem, @@ -156,6 +156,22 @@ impl<'w> EntityRef<'w> { // SAFETY: We have read-only access to all components of this entity. unsafe { self.0.get_by_id(component_id) } } + + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + pub fn components(&self) -> Q::Item<'w> { + self.get_components::().expect(QUERY_MISMATCH_ERROR) + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + pub fn get_components(&self) -> Option> { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.0.get_components::() } + } } impl<'w> From> for EntityRef<'w> { @@ -351,6 +367,22 @@ impl<'w> EntityMut<'w> { self.as_readonly().get() } + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + pub fn components(&self) -> Q::Item<'_> { + self.get_components::().expect(QUERY_MISMATCH_ERROR) + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + pub fn get_components(&self) -> Option> { + // SAFETY: We have read-only access to all components of this entity. + unsafe { self.0.get_components::() } + } + /// Consumes `self` and gets access to the component of type `T` with the /// world `'w` lifetime for the current entity. /// @@ -648,6 +680,23 @@ impl<'w> EntityWorldMut<'w> { EntityRef::from(self).get() } + /// Returns read-only components for the current entity that match the query `Q`. + /// + /// # Panics + /// + /// If the entity does not have the components required by the query `Q`. + #[inline] + pub fn components(&self) -> Q::Item<'_> { + EntityRef::from(self).components::() + } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + #[inline] + pub fn get_components(&self) -> Option> { + EntityRef::from(self).get_components::() + } + /// Consumes `self` and gets access to the component of type `T` with /// the world `'w` lifetime for the current entity. /// Returns `None` if the entity does not have a component of type `T`. @@ -1491,6 +1540,8 @@ unsafe fn trigger_on_replace_and_on_remove_hooks_and_observers( } } +const QUERY_MISMATCH_ERROR: &str = "Query does not match the current entity"; + /// A view into a single entity and component in a world, which may either be vacant or occupied. /// /// This `enum` can only be constructed from the [`entry`] method on [`EntityWorldMut`]. @@ -1878,7 +1929,7 @@ impl<'w> FilteredEntityRef<'w> { /// Returns an iterator over the component ids that are accessed by self. #[inline] - pub fn components(&self) -> impl Iterator + '_ { + pub fn accessed_components(&self) -> impl Iterator + '_ { self.access.component_reads_and_writes() } @@ -2135,7 +2186,7 @@ impl<'w> FilteredEntityMut<'w> { /// Returns an iterator over the component ids that are accessed by self. #[inline] - pub fn components(&self) -> impl Iterator + '_ { + pub fn accessed_components(&self) -> impl Iterator + '_ { self.access.component_reads_and_writes() } @@ -3115,4 +3166,24 @@ mod tests { assert!(e.get_mut_by_id(a_id).is_none()); assert!(e.get_change_ticks_by_id(a_id).is_none()); } + + #[test] + fn get_components() { + #[derive(Component, PartialEq, Eq, Debug)] + struct X(usize); + + #[derive(Component, PartialEq, Eq, Debug)] + struct Y(usize); + let mut world = World::default(); + let e1 = world.spawn((X(7), Y(10))).id(); + let e2 = world.spawn(X(8)).id(); + let e3 = world.spawn_empty().id(); + + assert_eq!( + Some((&X(7), &Y(10))), + world.entity(e1).get_components::<(&X, &Y)>() + ); + assert_eq!(None, world.entity(e2).get_components::<(&X, &Y)>()); + assert_eq!(None, world.entity(e3).get_components::<(&X, &Y)>()); + } } diff --git a/crates/bevy_ecs/src/world/unsafe_world_cell.rs b/crates/bevy_ecs/src/world/unsafe_world_cell.rs index 00b5349d80..07e0591c29 100644 --- a/crates/bevy_ecs/src/world/unsafe_world_cell.rs +++ b/crates/bevy_ecs/src/world/unsafe_world_cell.rs @@ -11,6 +11,7 @@ use crate::{ entity::{Entities, Entity, EntityLocation}, observer::Observers, prelude::Component, + query::{DebugCheckedUnwrap, ReadOnlyQueryData}, removal_detection::RemovedComponentEvents, storage::{Column, ComponentSparseSet, Storages}, system::{Res, Resource}, @@ -882,6 +883,55 @@ impl<'w> UnsafeEntityCell<'w> { }) } } + + /// Returns read-only components for the current entity that match the query `Q`, + /// or `None` if the entity does not have the components required by the query `Q`. + /// + /// # Safety + /// It is the callers responsibility to ensure that + /// - the [`UnsafeEntityCell`] has permission to access the queried data immutably + /// - no mutable references to the queried data exist at the same time + pub(crate) unsafe fn get_components(&self) -> Option> { + // SAFETY: World is only used to access query data and initialize query state + let state = unsafe { + let world = self.world().world(); + Q::get_state(world.components())? + }; + let location = self.location(); + // SAFETY: Location is guaranteed to exist + let archetype = unsafe { + self.world + .archetypes() + .get(location.archetype_id) + .debug_checked_unwrap() + }; + if Q::matches_component_set(&state, &|id| archetype.contains(id)) { + // SAFETY: state was initialized above using the world passed into this function + let mut fetch = unsafe { + Q::init_fetch( + self.world, + &state, + self.world.last_change_tick(), + self.world.change_tick(), + ) + }; + // SAFETY: Table is guaranteed to exist + let table = unsafe { + self.world + .storages() + .tables + .get(location.table_id) + .debug_checked_unwrap() + }; + // SAFETY: Archetype and table are from the same world used to initialize state and fetch. + // Table corresponds to archetype. State is the same state used to init fetch above. + unsafe { Q::set_archetype(&mut fetch, &state, archetype, table) } + // SAFETY: Called after set_archetype above. Entity and location are guaranteed to exist. + unsafe { Some(Q::fetch(&mut fetch, self.id(), location.table_row)) } + } else { + None + } + } } impl<'w> UnsafeEntityCell<'w> { diff --git a/examples/ecs/dynamic.rs b/examples/ecs/dynamic.rs index 79ec202ae4..e7d20a8997 100644 --- a/examples/ecs/dynamic.rs +++ b/examples/ecs/dynamic.rs @@ -151,7 +151,7 @@ fn main() { query.iter_mut(&mut world).for_each(|filtered_entity| { let terms = filtered_entity - .components() + .accessed_components() .map(|id| { let ptr = filtered_entity.get_by_id(id).unwrap(); let info = component_info.get(&id).unwrap(); From 09d229201699730766ca452c462ff7ef2ba34ec1 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 9 Sep 2024 17:34:24 +0100 Subject: [PATCH 023/115] Add a border to the UI material example (#15120) # Objective There aren't any examples of how to draw a ui material with borders. ## Solution Add border rendering to the `ui_material` example's shader. ## Showcase bordermat --------- Co-authored-by: charlotte --- assets/shaders/custom_ui_material.wgsl | 11 +++++++++++ examples/ui/ui_material.rs | 9 ++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/assets/shaders/custom_ui_material.wgsl b/assets/shaders/custom_ui_material.wgsl index fa0344930d..2b7eb01e92 100644 --- a/assets/shaders/custom_ui_material.wgsl +++ b/assets/shaders/custom_ui_material.wgsl @@ -5,10 +5,21 @@ @group(1) @binding(1) var slider: f32; @group(1) @binding(2) var material_color_texture: texture_2d; @group(1) @binding(3) var material_color_sampler: sampler; +@group(1) @binding(4) var border_color: vec4; @fragment fn fragment(in: UiVertexOutput) -> @location(0) vec4 { + let r = in.uv - 0.5; + let b = vec2( + select(in.border_widths.x, in.border_widths.y, r.x < 0.), + select(in.border_widths.z, in.border_widths.w, r.y < 0.) + ); + + if any(0.5 - b < abs(r)) { + return border_color; + } + if in.uv.x < slider { let output_color = textureSample(material_color_texture, material_color_sampler, in.uv) * color; return output_color; diff --git a/examples/ui/ui_material.rs b/examples/ui/ui_material.rs index bfd06ad58f..6aacc9bb8d 100644 --- a/examples/ui/ui_material.rs +++ b/examples/ui/ui_material.rs @@ -42,12 +42,14 @@ fn setup( position_type: PositionType::Absolute, width: Val::Px(905.0 * banner_scale_factor), height: Val::Px(363.0 * banner_scale_factor), + border: UiRect::all(Val::Px(10.)), ..default() }, material: ui_materials.add(CustomUiMaterial { color: LinearRgba::WHITE.to_f32_array().into(), slider: 0.5, color_texture: asset_server.load("branding/banner.png"), + border_color: LinearRgba::WHITE.to_f32_array().into(), }), ..default() }); @@ -67,6 +69,9 @@ struct CustomUiMaterial { #[texture(2)] #[sampler(3)] color_texture: Handle, + /// Color of the image's border + #[uniform(4)] + border_color: Vec4, } impl UiMaterial for CustomUiMaterial { @@ -87,9 +92,11 @@ fn animate( if let Some(material) = materials.get_mut(handle) { // rainbow color effect let new_color = Color::hsl((time.elapsed_seconds() * 60.0) % 360.0, 1., 0.5); - material.color = LinearRgba::from(new_color).to_f32_array().into(); + let border_color = Color::hsl((time.elapsed_seconds() * 60.0) % 360.0, 0.75, 0.75); + material.color = new_color.to_linear().to_vec4(); material.slider = ((time.elapsed_seconds() % (duration * 2.0)) - duration).abs() / duration; + material.border_color = border_color.to_linear().to_vec4(); } } } From adc2cf7dfe796540cb5a1e9511f433ee5c04fa95 Mon Sep 17 00:00:00 2001 From: UkoeHB <37489173+UkoeHB@users.noreply.github.com> Date: Mon, 9 Sep 2024 11:37:27 -0500 Subject: [PATCH 024/115] Add state scoped events (#15085) # Objective - Improve robustness of state transitions. Currently events that should be scoped to a specific state can leak between state scopes since events live for two ticks. - See https://github.com/bevyengine/bevy/issues/15072 ## Solution - Allow registering state scoped events that will be automatically cleared when exiting a state. This is *most of the time* not obviously useful, but enables users to write correct code that will avoid/reduce edge conditions (such as systems that aren't state scoped polling for a state scoped event and having unintended side effects outside a specific state instance). ## Testing Did not test. --- ## Showcase Added state scoped events that will be automatically cleared when exiting a state. Useful when you want to guarantee clean state transitions. Normal way to add an event: ```rust fn setup(app: &mut App) { app.add_event::(); } ``` Add a state-scoped event (**NEW**): ```rust fn setup(app: &mut App) { app.add_state_scoped_event::(GameState::Play); } ``` --- crates/bevy_state/src/lib.rs | 7 ++ crates/bevy_state/src/state_scoped_events.rs | 109 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 crates/bevy_state/src/state_scoped_events.rs diff --git a/crates/bevy_state/src/lib.rs b/crates/bevy_state/src/lib.rs index 88598db0b9..c2aba9c24d 100644 --- a/crates/bevy_state/src/lib.rs +++ b/crates/bevy_state/src/lib.rs @@ -44,6 +44,10 @@ pub mod state; /// Provides [`StateScoped`](crate::state_scoped::StateScoped) and /// [`clear_state_scoped_entities`](crate::state_scoped::clear_state_scoped_entities) for managing lifetime of entities. pub mod state_scoped; +#[cfg(feature = "bevy_app")] +/// Provides [`App`](bevy_app::App) and [`SubApp`](bevy_app::SubApp) with methods for registering +/// state-scoped events. +pub mod state_scoped_events; #[cfg(feature = "bevy_reflect")] /// Provides definitions for the basic traits required by the state system @@ -71,4 +75,7 @@ pub mod prelude { }; #[doc(hidden)] pub use crate::state_scoped::StateScoped; + #[cfg(feature = "bevy_app")] + #[doc(hidden)] + pub use crate::state_scoped_events::StateScopedEventsAppExt; } diff --git a/crates/bevy_state/src/state_scoped_events.rs b/crates/bevy_state/src/state_scoped_events.rs new file mode 100644 index 0000000000..b3baeab3ca --- /dev/null +++ b/crates/bevy_state/src/state_scoped_events.rs @@ -0,0 +1,109 @@ +use std::marker::PhantomData; + +use bevy_app::{App, SubApp}; +use bevy_ecs::{ + event::{Event, EventReader, Events}, + system::{Commands, Resource}, + world::World, +}; +use bevy_utils::HashMap; + +use crate::state::{FreelyMutableState, OnExit, StateTransitionEvent}; + +fn clear_event_queue(w: &mut World) { + if let Some(mut queue) = w.get_resource_mut::>() { + queue.clear(); + } +} + +#[derive(Resource)] +struct StateScopedEvents { + cleanup_fns: HashMap>, +} + +impl StateScopedEvents { + fn add_event(&mut self, state: S) { + self.cleanup_fns + .entry(state) + .or_default() + .push(clear_event_queue::); + } + + fn cleanup(&self, w: &mut World, state: S) { + let Some(fns) = self.cleanup_fns.get(&state) else { + return; + }; + for callback in fns { + (*callback)(w); + } + } +} + +impl Default for StateScopedEvents { + fn default() -> Self { + Self { + cleanup_fns: HashMap::default(), + } + } +} + +fn cleanup_state_scoped_event( + mut c: Commands, + mut transitions: EventReader>, +) { + let Some(transition) = transitions.read().last() else { + return; + }; + if transition.entered == transition.exited { + return; + } + let Some(exited) = transition.exited.clone() else { + return; + }; + + c.add(move |w: &mut World| { + w.resource_scope::, ()>(|w, events| { + events.cleanup(w, exited); + }); + }); +} + +fn add_state_scoped_event_impl( + app: &mut SubApp, + _p: PhantomData, + state: S, +) { + if !app.world().contains_resource::>() { + app.init_resource::>(); + } + app.add_event::(); + app.world_mut() + .resource_mut::>() + .add_event::(state.clone()); + app.add_systems(OnExit(state), cleanup_state_scoped_event::); +} + +/// Extension trait for [`App`] adding methods for registering state scoped events. +pub trait StateScopedEventsAppExt { + /// Adds an [`Event`] that is automatically cleaned up when leaving the specified `state`. + /// + /// Note that event cleanup is ordered ambiguously relative to [`StateScoped`](crate::prelude::StateScoped) entity + /// cleanup and the [`OnExit`] schedule for the target state. All of these (state scoped + /// entities and events cleanup, and `OnExit`) occur within schedule [`StateTransition`](crate::prelude::StateTransition) + /// and system set `StateTransitionSteps::ExitSchedules`. + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self; +} + +impl StateScopedEventsAppExt for App { + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + add_state_scoped_event_impl(self.main_mut(), PhantomData::, state); + self + } +} + +impl StateScopedEventsAppExt for SubApp { + fn add_state_scoped_event(&mut self, state: impl FreelyMutableState) -> &mut Self { + add_state_scoped_event_impl(self, PhantomData::, state); + self + } +} From bafffe1c5f0a0430aa32679eef54cb4d1c4eceb2 Mon Sep 17 00:00:00 2001 From: akimakinai <105044389+akimakinai@users.noreply.github.com> Date: Tue, 10 Sep 2024 01:53:20 +0900 Subject: [PATCH 025/115] Fix screenshot example (#15094) # Objective I noticed some issues in `screenshot` example: 1. Cursor icon won't return from `SystemCursorIcon::Progress` to default icon, even though screen shot saving is done. 2. Panics when exiting window: ``called `Result::unwrap()` on an `Err` value: NoEntities("bevy_ecs::query::state::QueryState>")`` ## Solution 1. Caused by cursor updating system not responding to [`CursorIcon` component removal](https://github.com/bevyengine/bevy/blob/5cfcbf47ed386515adefe80f99f8e8f7e7f3ce5a/examples/window/screenshot.rs#L38). I believe it should, so change it to react to `RemovedComponents`. (a suggestion) 2. Use `get_single` for window. ## Testing - run screenshot example --------- Co-authored-by: Alice Cecile --- crates/bevy_render/src/view/window/cursor.rs | 18 +++++++++++++++--- examples/window/screenshot.rs | 4 +++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/crates/bevy_render/src/view/window/cursor.rs b/crates/bevy_render/src/view/window/cursor.rs index 3bed223631..dbc5ebda35 100644 --- a/crates/bevy_render/src/view/window/cursor.rs +++ b/crates/bevy_render/src/view/window/cursor.rs @@ -4,10 +4,11 @@ use bevy_ecs::{ change_detection::DetectChanges, component::Component, entity::Entity, + observer::Trigger, query::With, reflect::ReflectComponent, system::{Commands, Local, Query, Res}, - world::Ref, + world::{OnRemove, Ref}, }; use bevy_reflect::{std_traits::ReflectDefault, Reflect}; use bevy_utils::{tracing::warn, HashSet}; @@ -27,6 +28,8 @@ impl Plugin for CursorPlugin { app.register_type::() .init_resource::() .add_systems(Last, update_cursors); + + app.observe(on_remove_cursor_icon); } } @@ -84,12 +87,12 @@ pub enum CustomCursor { pub fn update_cursors( mut commands: Commands, - mut windows: Query<(Entity, Ref), With>, + windows: Query<(Entity, Ref), With>, cursor_cache: Res, images: Res>, mut queue: Local>, ) { - for (entity, cursor) in windows.iter_mut() { + for (entity, cursor) in windows.iter() { if !(queue.remove(&entity) || cursor.is_changed()) { continue; } @@ -161,6 +164,15 @@ pub fn update_cursors( } } +/// Resets the cursor to the default icon when `CursorIcon` is removed. +pub fn on_remove_cursor_icon(trigger: Trigger, mut commands: Commands) { + commands + .entity(trigger.entity()) + .insert(PendingCursor(Some(CursorSource::System( + convert_system_cursor_icon(SystemCursorIcon::Default), + )))); +} + /// Returns the image data as a `Vec`. /// Only supports rgba8 and rgba32float formats. fn image_to_rgba_pixels(image: &Image) -> Option> { diff --git a/examples/window/screenshot.rs b/examples/window/screenshot.rs index 1bf800d18c..806478bd08 100644 --- a/examples/window/screenshot.rs +++ b/examples/window/screenshot.rs @@ -32,7 +32,9 @@ fn screenshot_saving( screenshot_saving: Query>, windows: Query>, ) { - let window = windows.single(); + let Ok(window) = windows.get_single() else { + return; + }; match screenshot_saving.iter().count() { 0 => { commands.entity(window).remove::(); From ccb8854ec1da338a70eea1c4cd9552e2091b070c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:35:40 -0700 Subject: [PATCH 026/115] Bump peter-evans/create-pull-request from 6 to 7 (#15112) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 6 to 7.
Release notes

Sourced from peter-evans/create-pull-request's releases.

Create Pull Request v7.0.0

:sparkles: Now supports commit signing with bot-generated tokens! See "What's new" below. :writing_hand::robot:

Behaviour changes

  • Action input git-token has been renamed branch-token, to be more clear about its purpose. The branch-token is the token that the action will use to create and update the branch.
  • The action now handles requests that have been rate-limited by GitHub. Requests hitting a primary rate limit will retry twice, for a total of three attempts. Requests hitting a secondary rate limit will not be retried.
  • The pull-request-operation output now returns none when no operation was executed.
  • Removed deprecated output environment variable PULL_REQUEST_NUMBER. Please use the pull-request-number action output instead.

What's new

  • The action can now sign commits as github-actions[bot] when using GITHUB_TOKEN, or your own bot when using GitHub App tokens. See commit signing for details.
  • Action input draft now accepts a new value always-true. This will set the pull request to draft status when the pull request is updated, as well as on creation.
  • A new action input maintainer-can-modify indicates whether maintainers can modify the pull request. The default is true, which retains the existing behaviour of the action.
  • A new output pull-request-commits-verified returns true or false, indicating whether GitHub considers the signature of the branch's commits to be verified.

What's Changed

New Contributors

Full Changelog: https://github.com/peter-evans/create-pull-request/compare/v6.1.0...v7.0.0

Create Pull Request v6.1.0

✨ Adds pull-request-branch as an action output.

What's Changed

... (truncated)

Commits
  • 8867c4a fix: handle ambiguous argument failure on diff stat (#3312)
  • 6073f54 build(deps-dev): bump @​typescript-eslint/eslint-plugin (#3291)
  • 6d01b56 build(deps-dev): bump eslint-plugin-import from 2.29.1 to 2.30.0 (#3290)
  • 25cf845 build(deps-dev): bump @​typescript-eslint/parser from 7.17.0 to 7.18.0 (#3289)
  • d87b980 build(deps-dev): bump @​types/node from 18.19.46 to 18.19.48 (#3288)
  • 119d131 build(deps): bump peter-evans/create-pull-request from 6 to 7 (#3283)
  • 73e6230 docs: update readme
  • c0348e8 ci: add v7 to workflow
  • 4320041 feat: signed commits (v7) (#3057)
  • 0c2a66f build(deps-dev): bump ts-jest from 29.2.4 to 29.2.5 (#3256)
  • Additional commits viewable in compare view

[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=peter-evans/create-pull-request&package-manager=github_actions&previous-version=6&new-version=7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) ---
Dependabot commands and options
You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot merge` will merge this PR after your CI passes on it - `@dependabot squash and merge` will squash and merge this PR after your CI passes on it - `@dependabot cancel merge` will cancel a previously requested merge and block automerging - `@dependabot reopen` will reopen this PR if it is closed - `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually - `@dependabot show ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/post-release.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/post-release.yml b/.github/workflows/post-release.yml index 1f30fe2428..7902584a9f 100644 --- a/.github/workflows/post-release.yml +++ b/.github/workflows/post-release.yml @@ -49,7 +49,7 @@ jobs: --exclude build-wasm-example - name: Create PR - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: delete-branch: true base: "main" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4efe748bcf..32e481b230 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -46,7 +46,7 @@ jobs: --exclude build-wasm-example - name: Create PR - uses: peter-evans/create-pull-request@v6 + uses: peter-evans/create-pull-request@v7 with: delete-branch: true base: "main" From 4e6471ed2303cf09cf09786519a6fce7d48b1e9b Mon Sep 17 00:00:00 2001 From: robtfm <50659922+robtfm@users.noreply.github.com> Date: Mon, 9 Sep 2024 18:51:38 +0100 Subject: [PATCH 027/115] honour NoFrustumCulling for shadows (#15117) # Objective `NoFrustumCulling` prevents meshes from being considered out of view based on AABBs (sometimes useful for skinned meshes which don't recalculate AABBs currently). it currently only applies for primary view rendering, not for shadow rendering which can result in missing shadows. ## Solution Add checks for `NoFrustumCulling` to `check_dir_light_mesh_visibility` and `check_point_light_mesh_visibility` so that `NoFrustumCulling` entities are rendered to all shadow views as well as all primary views. --- crates/bevy_pbr/src/light/mod.rs | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/bevy_pbr/src/light/mod.rs b/crates/bevy_pbr/src/light/mod.rs index d0db103c93..f6124c71de 100644 --- a/crates/bevy_pbr/src/light/mod.rs +++ b/crates/bevy_pbr/src/light/mod.rs @@ -11,7 +11,8 @@ use bevy_render::{ mesh::Mesh, primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, Sphere}, view::{ - InheritedVisibility, RenderLayers, ViewVisibility, VisibilityRange, VisibleEntityRanges, + InheritedVisibility, NoFrustumCulling, RenderLayers, ViewVisibility, VisibilityRange, + VisibleEntityRanges, }, }; use bevy_transform::components::{GlobalTransform, Transform}; @@ -683,6 +684,7 @@ pub fn check_dir_light_mesh_visibility( Option<&Aabb>, Option<&GlobalTransform>, Has, + Has, ), ( Without, @@ -742,6 +744,7 @@ pub fn check_dir_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -768,7 +771,9 @@ pub fn check_dir_light_mesh_visibility( .zip(view_visible_entities_local_queue.iter_mut()) { // Disable near-plane culling, as a shadow caster could lie before the near plane. - if !frustum.intersects_obb(aabb, &transform.affine(), false, true) { + if !has_no_frustum_culling + && !frustum.intersects_obb(aabb, &transform.affine(), false, true) + { continue; } visible = true; @@ -848,6 +853,7 @@ pub fn check_point_light_mesh_visibility( Option<&Aabb>, Option<&GlobalTransform>, Has, + Has, ), ( Without, @@ -897,6 +903,7 @@ pub fn check_point_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -917,7 +924,9 @@ pub fn check_point_light_mesh_visibility( if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { let model_to_world = transform.affine(); // Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light - if !light_sphere.intersects_obb(aabb, &model_to_world) { + if !has_no_frustum_culling + && !light_sphere.intersects_obb(aabb, &model_to_world) + { return; } @@ -925,7 +934,9 @@ pub fn check_point_light_mesh_visibility( .iter() .zip(cubemap_visible_entities_local_queue.iter_mut()) { - if frustum.intersects_obb(aabb, &model_to_world, true, true) { + if has_no_frustum_culling + || frustum.intersects_obb(aabb, &model_to_world, true, true) + { view_visibility.set(); visible_entities.push(entity); } @@ -980,6 +991,7 @@ pub fn check_point_light_mesh_visibility( maybe_aabb, maybe_transform, has_visibility_range, + has_no_frustum_culling, )| { if !inherited_visibility.get() { return; @@ -1001,11 +1013,15 @@ pub fn check_point_light_mesh_visibility( if let (Some(aabb), Some(transform)) = (maybe_aabb, maybe_transform) { let model_to_world = transform.affine(); // Do a cheap sphere vs obb test to prune out most meshes outside the sphere of the light - if !light_sphere.intersects_obb(aabb, &model_to_world) { + if !has_no_frustum_culling + && !light_sphere.intersects_obb(aabb, &model_to_world) + { return; } - if frustum.intersects_obb(aabb, &model_to_world, true, true) { + if has_no_frustum_culling + || frustum.intersects_obb(aabb, &model_to_world, true, true) + { view_visibility.set(); spot_visible_entities_local_queue.push(entity); } From a5c4606a985471fbfc9e1128c4a2ddc6c0ef9a07 Mon Sep 17 00:00:00 2001 From: Lixou <82600264+DasLixou@users.noreply.github.com> Date: Mon, 9 Sep 2024 19:52:11 +0200 Subject: [PATCH 028/115] Fix Welcome Contributors CI (#15123) # Objective Fixes #15121 (hopefully, just read the docs, don't know how to test this tho) ## Solution Adds write permissions for pull-requests, see https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#create-an-issue-comment--fine-grained-access-tokens for more information. --- .github/workflows/welcome.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml index 23bcbcf8be..96a287981d 100644 --- a/.github/workflows/welcome.yml +++ b/.github/workflows/welcome.yml @@ -11,6 +11,8 @@ on: jobs: welcome: runs-on: ubuntu-latest + permissions: + pull-requests: write steps: - uses: actions/github-script@v7 with: From 90bb1adeb28b82be96546e0c2cde81530241f7af Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Mon, 9 Sep 2024 10:52:40 -0700 Subject: [PATCH 029/115] bevy_reflect: Contextual serialization error messages (#13888) # Objective Reflection serialization can be difficult to debug. A lot of times a type fails to be serialized and the user is left wondering where that type came from. This is most often encountered with Bevy's scenes. Attempting to serialize all resources in the world will fail because some resources can't be serialized. For example, users will often get complaints about `bevy_utils::Instant` not registering `ReflectSerialize`. Well, `Instant` can't be serialized, so the only other option is to exclude the resource that contains it. But what resource contains it? This is where reflection serialization can get a little tricky (it's `Time` btw). ## Solution Add the `debug_stack` feature to `bevy_reflect`. When enabled, the reflection serializers and deserializers will keep track of the current type stack. And this stack will be used in error messages to help with debugging. Now, if we unknowingly try to serialize `Time`, we'll get the following error: ``` type `bevy_utils::Instant` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_time::time::Time` -> `bevy_time::real::Real` -> `bevy_utils::Instant`) ``` ### Implementation This makes use of `thread_local!` to manage an internal `TypeInfoStack` which holds a stack of `&'static TypeInfo`. We push to the stack before a type is (de)serialized and pop from the stack afterwards. Using a thread-local should be fine since we know two (de)serializers can't be running at the same time (and if they're running on separate threads, then we're still good). The only potential issue would be if a user went through one of the sub-serializers, like `StructSerializer`. However, I don't think many users are going through these types (I don't even know if we necessarily want to keep those public either, but we'll save that for a different PR). Additionally, this is just a debug feature that only affects error messages, so it wouldn't have any drastically negative effect. It would just result in the stack not being cleared properly if there were any errors. Lastly, this is not the most performant implementation since we now fetch the `TypeInfo` an extra time. But I figured that for a debug tool, it wouldn't matter too much. ### Feature This also adds a `debug` feature, which enables the `debug_stack` feature. I added it because I think we may want to potentially add more debug tools in the future, and this gives us a good framework for adding those. Users who want all debug features, present and future, can just set `debug`. If they only want this feature, then they can just use `debug_stack`. I also made the `debug` feature default to help capture the widest audience (i.e. the users who want this feature but don't know they do). However, if we think it's better as a non-default feature, I can change it! And if there's any bikeshedding around the name `debug_stack`, let me know! ## Testing Run the following command: ``` cargo test --package bevy_reflect --features debug_stack ``` --- ## Changelog - Added the `debug` and `debug_stack` features to `bevy_reflect` - Updated the error messages returned by the reflection serializers and deserializers to include more contextual information when the `debug_stack` or `debug` feature is enabled --- crates/bevy_reflect/Cargo.toml | 6 +- crates/bevy_reflect/src/lib.rs | 16 +- crates/bevy_reflect/src/serde/de/arrays.rs | 7 +- .../bevy_reflect/src/serde/de/deserializer.rs | 196 ++++++++++-------- crates/bevy_reflect/src/serde/de/enums.rs | 12 +- .../bevy_reflect/src/serde/de/error_utils.rs | 26 +++ crates/bevy_reflect/src/serde/de/lists.rs | 7 +- crates/bevy_reflect/src/serde/de/maps.rs | 4 +- crates/bevy_reflect/src/serde/de/mod.rs | 51 ++++- crates/bevy_reflect/src/serde/de/options.rs | 5 +- .../src/serde/de/registration_utils.rs | 7 +- .../src/serde/de/registrations.rs | 3 +- crates/bevy_reflect/src/serde/de/sets.rs | 2 +- .../bevy_reflect/src/serde/de/struct_utils.rs | 26 ++- .../bevy_reflect/src/serde/de/tuple_utils.rs | 15 +- crates/bevy_reflect/src/serde/mod.rs | 2 +- crates/bevy_reflect/src/serde/ser/arrays.rs | 2 +- crates/bevy_reflect/src/serde/ser/enums.rs | 22 +- .../bevy_reflect/src/serde/ser/error_utils.rs | 26 +++ crates/bevy_reflect/src/serde/ser/lists.rs | 2 +- crates/bevy_reflect/src/serde/ser/maps.rs | 4 +- crates/bevy_reflect/src/serde/ser/mod.rs | 68 +++++- .../src/serde/ser/serializable.rs | 17 +- .../bevy_reflect/src/serde/ser/serializer.rs | 49 ++++- crates/bevy_reflect/src/serde/ser/sets.rs | 2 +- crates/bevy_reflect/src/serde/ser/structs.rs | 14 +- .../src/serde/ser/tuple_structs.rs | 11 +- crates/bevy_reflect/src/serde/ser/tuples.rs | 2 +- crates/bevy_reflect/src/type_info_stack.rs | 49 +++++ 29 files changed, 482 insertions(+), 171 deletions(-) create mode 100644 crates/bevy_reflect/src/serde/de/error_utils.rs create mode 100644 crates/bevy_reflect/src/serde/ser/error_utils.rs create mode 100644 crates/bevy_reflect/src/type_info_stack.rs diff --git a/crates/bevy_reflect/Cargo.toml b/crates/bevy_reflect/Cargo.toml index 65ded3f8de..c86a92e852 100644 --- a/crates/bevy_reflect/Cargo.toml +++ b/crates/bevy_reflect/Cargo.toml @@ -10,13 +10,17 @@ keywords = ["bevy"] rust-version = "1.76.0" [features] -default = ["smallvec"] +default = ["smallvec", "debug"] # When enabled, provides Bevy-related reflection implementations bevy = ["smallvec", "smol_str"] glam = ["dep:glam"] petgraph = ["dep:petgraph"] smallvec = ["dep:smallvec"] uuid = ["dep:uuid"] +# Enables features useful for debugging reflection +debug = ["debug_stack"] +# When enabled, keeps track of the current serialization/deserialization context for better error messages +debug_stack = [] # When enabled, allows documentation comments to be accessed via reflection documentation = ["bevy_reflect_derive/documentation"] # Enables function reflection diff --git a/crates/bevy_reflect/src/lib.rs b/crates/bevy_reflect/src/lib.rs index b226af4432..dc820caaaa 100644 --- a/crates/bevy_reflect/src/lib.rs +++ b/crates/bevy_reflect/src/lib.rs @@ -473,7 +473,7 @@ //! //! | Default | Dependencies | //! | :-----: | :---------------------------------------: | -//! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] | +//! | ❌ | [`bevy_math`], [`glam`], [`smallvec`] | //! //! This feature makes it so that the appropriate reflection traits are implemented on all the types //! necessary for the [Bevy] game engine. @@ -493,6 +493,18 @@ //! This can be useful for generating documentation for scripting language interop or //! for displaying tooltips in an editor. //! +//! ## `debug` +//! +//! | Default | Dependencies | +//! | :-----: | :-------------------------------------------: | +//! | ✅ | `debug_stack` | +//! +//! This feature enables useful debug features for reflection. +//! +//! This includes the `debug_stack` feature, +//! which enables capturing the type stack when serializing or deserializing a type +//! and displaying it in error messages. +//! //! [Reflection]: https://en.wikipedia.org/wiki/Reflective_programming //! [Bevy]: https://bevyengine.org/ //! [limitations]: #limitations @@ -562,6 +574,8 @@ pub mod attributes; mod enums; pub mod serde; pub mod std_traits; +#[cfg(feature = "debug_stack")] +mod type_info_stack; pub mod utility; /// The reflect prelude. diff --git a/crates/bevy_reflect/src/serde/de/arrays.rs b/crates/bevy_reflect/src/serde/de/arrays.rs index 6e2f331a65..2ffd2419a9 100644 --- a/crates/bevy_reflect/src/serde/de/arrays.rs +++ b/crates/bevy_reflect/src/serde/de/arrays.rs @@ -35,9 +35,10 @@ impl<'a, 'de> Visitor<'de> for ArrayVisitor<'a> { { let mut vec = Vec::with_capacity(seq.size_hint().unwrap_or_default()); let registration = try_get_registration(self.array_info.item_ty(), self.registry)?; - while let Some(value) = - seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? - { + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal( + registration, + self.registry, + ))? { vec.push(value); } diff --git a/crates/bevy_reflect/src/serde/de/deserializer.rs b/crates/bevy_reflect/src/serde/de/deserializer.rs index a4496dc72d..7af7aa0997 100644 --- a/crates/bevy_reflect/src/serde/de/deserializer.rs +++ b/crates/bevy_reflect/src/serde/de/deserializer.rs @@ -1,5 +1,8 @@ use crate::serde::de::arrays::ArrayVisitor; use crate::serde::de::enums::EnumVisitor; +use crate::serde::de::error_utils::make_custom_error; +#[cfg(feature = "debug_stack")] +use crate::serde::de::error_utils::TYPE_INFO_STACK; use crate::serde::de::lists::ListVisitor; use crate::serde::de::maps::MapVisitor; use crate::serde::de::options::OptionVisitor; @@ -231,6 +234,20 @@ pub struct TypedReflectDeserializer<'a> { impl<'a> TypedReflectDeserializer<'a> { pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self { + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new()); + + Self { + registration, + registry, + } + } + + /// An internal constructor for creating a deserializer without resetting the type info stack. + pub(super) fn new_internal( + registration: &'a TypeRegistration, + registry: &'a TypeRegistry, + ) -> Self { Self { registration, registry, @@ -245,89 +262,106 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> { where D: serde::Deserializer<'de>, { - let type_path = self.registration.type_info().type_path(); + let deserialize_internal = || -> Result { + let type_path = self.registration.type_info().type_path(); - // Handle both Value case and types that have a custom `ReflectDeserialize` - if let Some(deserialize_reflect) = self.registration.data::() { - let value = deserialize_reflect.deserialize(deserializer)?; - return Ok(value.into_partial_reflect()); - } + // Handle both Value case and types that have a custom `ReflectDeserialize` + if let Some(deserialize_reflect) = self.registration.data::() { + let value = deserialize_reflect.deserialize(deserializer)?; + return Ok(value.into_partial_reflect()); + } - match self.registration.type_info() { - TypeInfo::Struct(struct_info) => { - let mut dynamic_struct = deserializer.deserialize_struct( - struct_info.type_path_table().ident().unwrap(), - struct_info.field_names(), - StructVisitor::new(struct_info, self.registration, self.registry), - )?; - dynamic_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_struct)) + match self.registration.type_info() { + TypeInfo::Struct(struct_info) => { + let mut dynamic_struct = deserializer.deserialize_struct( + struct_info.type_path_table().ident().unwrap(), + struct_info.field_names(), + StructVisitor::new(struct_info, self.registration, self.registry), + )?; + dynamic_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_struct)) + } + TypeInfo::TupleStruct(tuple_struct_info) => { + let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( + tuple_struct_info.type_path_table().ident().unwrap(), + tuple_struct_info.field_len(), + TupleStructVisitor::new( + tuple_struct_info, + self.registration, + self.registry, + ), + )?; + dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple_struct)) + } + TypeInfo::List(list_info) => { + let mut dynamic_list = + deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?; + dynamic_list.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_list)) + } + TypeInfo::Array(array_info) => { + let mut dynamic_array = deserializer.deserialize_tuple( + array_info.capacity(), + ArrayVisitor::new(array_info, self.registry), + )?; + dynamic_array.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_array)) + } + TypeInfo::Map(map_info) => { + let mut dynamic_map = + deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?; + dynamic_map.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_map)) + } + TypeInfo::Set(set_info) => { + let mut dynamic_set = + deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?; + dynamic_set.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_set)) + } + TypeInfo::Tuple(tuple_info) => { + let mut dynamic_tuple = deserializer.deserialize_tuple( + tuple_info.field_len(), + TupleVisitor::new(tuple_info, self.registration, self.registry), + )?; + dynamic_tuple.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_tuple)) + } + TypeInfo::Enum(enum_info) => { + let mut dynamic_enum = if enum_info.type_path_table().module_path() + == Some("core::option") + && enum_info.type_path_table().ident() == Some("Option") + { + deserializer + .deserialize_option(OptionVisitor::new(enum_info, self.registry))? + } else { + deserializer.deserialize_enum( + enum_info.type_path_table().ident().unwrap(), + enum_info.variant_names(), + EnumVisitor::new(enum_info, self.registration, self.registry), + )? + }; + dynamic_enum.set_represented_type(Some(self.registration.type_info())); + Ok(Box::new(dynamic_enum)) + } + TypeInfo::Value(_) => { + // This case should already be handled + Err(make_custom_error(format_args!( + "type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + ))) + } } - TypeInfo::TupleStruct(tuple_struct_info) => { - let mut dynamic_tuple_struct = deserializer.deserialize_tuple_struct( - tuple_struct_info.type_path_table().ident().unwrap(), - tuple_struct_info.field_len(), - TupleStructVisitor::new(tuple_struct_info, self.registration, self.registry), - )?; - dynamic_tuple_struct.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple_struct)) - } - TypeInfo::List(list_info) => { - let mut dynamic_list = - deserializer.deserialize_seq(ListVisitor::new(list_info, self.registry))?; - dynamic_list.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_list)) - } - TypeInfo::Array(array_info) => { - let mut dynamic_array = deserializer.deserialize_tuple( - array_info.capacity(), - ArrayVisitor::new(array_info, self.registry), - )?; - dynamic_array.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_array)) - } - TypeInfo::Map(map_info) => { - let mut dynamic_map = - deserializer.deserialize_map(MapVisitor::new(map_info, self.registry))?; - dynamic_map.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_map)) - } - TypeInfo::Set(set_info) => { - let mut dynamic_set = - deserializer.deserialize_seq(SetVisitor::new(set_info, self.registry))?; - dynamic_set.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_set)) - } - TypeInfo::Tuple(tuple_info) => { - let mut dynamic_tuple = deserializer.deserialize_tuple( - tuple_info.field_len(), - TupleVisitor::new(tuple_info, self.registration, self.registry), - )?; - dynamic_tuple.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_tuple)) - } - TypeInfo::Enum(enum_info) => { - let mut dynamic_enum = if enum_info.type_path_table().module_path() - == Some("core::option") - && enum_info.type_path_table().ident() == Some("Option") - { - deserializer.deserialize_option(OptionVisitor::new(enum_info, self.registry))? - } else { - deserializer.deserialize_enum( - enum_info.type_path_table().ident().unwrap(), - enum_info.variant_names(), - EnumVisitor::new(enum_info, self.registration, self.registry), - )? - }; - dynamic_enum.set_represented_type(Some(self.registration.type_info())); - Ok(Box::new(dynamic_enum)) - } - TypeInfo::Value(_) => { - // This case should already be handled - Err(Error::custom(format_args!( - "Type `{type_path}` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`", - ))) - } - } + }; + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(self.registration.type_info())); + + let output = deserialize_internal(); + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + + output } } diff --git a/crates/bevy_reflect/src/serde/de/enums.rs b/crates/bevy_reflect/src/serde/de/enums.rs index 9350ebce6f..8f4a094e83 100644 --- a/crates/bevy_reflect/src/serde/de/enums.rs +++ b/crates/bevy_reflect/src/serde/de/enums.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::serde::de::helpers::ExpectedValues; use crate::serde::de::registration_utils::try_get_registration; use crate::serde::de::struct_utils::{visit_struct, visit_struct_seq}; @@ -67,10 +68,9 @@ impl<'a, 'de> Visitor<'de> for EnumVisitor<'a> { *TupleLikeInfo::field_at(tuple_info, 0)?.ty(), self.registry, )?; - let value = variant.newtype_variant_seed(TypedReflectDeserializer::new( - registration, - self.registry, - ))?; + let value = variant.newtype_variant_seed( + TypedReflectDeserializer::new_internal(registration, self.registry), + )?; let mut dynamic_tuple = DynamicTuple::default(); dynamic_tuple.insert_boxed(value); dynamic_tuple.into() @@ -121,7 +121,7 @@ impl<'de> DeserializeSeed<'de> for VariantDeserializer { E: Error, { self.0.variant_at(variant_index as usize).ok_or_else(|| { - Error::custom(format_args!( + make_custom_error(format_args!( "no variant found at index `{}` on enum `{}`", variant_index, self.0.type_path() @@ -135,7 +135,7 @@ impl<'de> DeserializeSeed<'de> for VariantDeserializer { { self.0.variant(variant_name).ok_or_else(|| { let names = self.0.iter().map(VariantInfo::name); - Error::custom(format_args!( + make_custom_error(format_args!( "unknown variant `{}`, expected one of {:?}", variant_name, ExpectedValues::from_iter(names) diff --git a/crates/bevy_reflect/src/serde/de/error_utils.rs b/crates/bevy_reflect/src/serde/de/error_utils.rs new file mode 100644 index 0000000000..6a97c2518c --- /dev/null +++ b/crates/bevy_reflect/src/serde/de/error_utils.rs @@ -0,0 +1,26 @@ +use core::fmt::Display; +use serde::de::Error; + +#[cfg(feature = "debug_stack")] +thread_local! { + /// The thread-local [`TypeInfoStack`] used for debugging. + /// + /// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack + pub(super) static TYPE_INFO_STACK: std::cell::RefCell = const { std::cell::RefCell::new( + crate::type_info_stack::TypeInfoStack::new() + ) }; +} + +/// A helper function for generating a custom deserialization error message. +/// +/// This function should be preferred over [`Error::custom`] as it will include +/// other useful information, such as the [type info stack]. +/// +/// [type info stack]: crate::type_info_stack::TypeInfoStack +pub(super) fn make_custom_error(msg: impl Display) -> E { + #[cfg(feature = "debug_stack")] + return TYPE_INFO_STACK + .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + #[cfg(not(feature = "debug_stack"))] + return E::custom(msg); +} diff --git a/crates/bevy_reflect/src/serde/de/lists.rs b/crates/bevy_reflect/src/serde/de/lists.rs index 30e7d9282d..d0387f8a0a 100644 --- a/crates/bevy_reflect/src/serde/de/lists.rs +++ b/crates/bevy_reflect/src/serde/de/lists.rs @@ -35,9 +35,10 @@ impl<'a, 'de> Visitor<'de> for ListVisitor<'a> { { let mut list = DynamicList::default(); let registration = try_get_registration(self.list_info.item_ty(), self.registry)?; - while let Some(value) = - seq.next_element_seed(TypedReflectDeserializer::new(registration, self.registry))? - { + while let Some(value) = seq.next_element_seed(TypedReflectDeserializer::new_internal( + registration, + self.registry, + ))? { list.push_box(value); } Ok(list) diff --git a/crates/bevy_reflect/src/serde/de/maps.rs b/crates/bevy_reflect/src/serde/de/maps.rs index 0b185110d6..1bda2e6837 100644 --- a/crates/bevy_reflect/src/serde/de/maps.rs +++ b/crates/bevy_reflect/src/serde/de/maps.rs @@ -33,11 +33,11 @@ impl<'a, 'de> Visitor<'de> for MapVisitor<'a> { let mut dynamic_map = DynamicMap::default(); let key_registration = try_get_registration(self.map_info.key_ty(), self.registry)?; let value_registration = try_get_registration(self.map_info.value_ty(), self.registry)?; - while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new( + while let Some(key) = map.next_key_seed(TypedReflectDeserializer::new_internal( key_registration, self.registry, ))? { - let value = map.next_value_seed(TypedReflectDeserializer::new( + let value = map.next_value_seed(TypedReflectDeserializer::new_internal( value_registration, self.registry, ))?; diff --git a/crates/bevy_reflect/src/serde/de/mod.rs b/crates/bevy_reflect/src/serde/de/mod.rs index 40bf927774..bc92d3ccf8 100644 --- a/crates/bevy_reflect/src/serde/de/mod.rs +++ b/crates/bevy_reflect/src/serde/de/mod.rs @@ -4,6 +4,7 @@ pub use registrations::*; mod arrays; mod deserializer; mod enums; +mod error_utils; mod helpers; mod lists; mod maps; @@ -275,7 +276,7 @@ mod tests { let mut registry = get_registry(); registry.register::(); let registration = registry.get(TypeId::of::()).unwrap(); - let reflect_deserializer = TypedReflectDeserializer::new(registration, ®istry); + let reflect_deserializer = TypedReflectDeserializer::new_internal(registration, ®istry); let mut ron_deserializer = ron::de::Deserializer::from_str(input).unwrap(); let dynamic_output = reflect_deserializer .deserialize(&mut ron_deserializer) @@ -510,6 +511,52 @@ mod tests { let error = reflect_deserializer .deserialize(&mut deserializer) .unwrap_err(); - assert_eq!(error, ron::Error::Message("Type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); + #[cfg(feature = "debug_stack")] + assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive`)".to_string())); + #[cfg(not(feature = "debug_stack"))] + assert_eq!(error, ron::Error::Message("type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string())); + } + + #[cfg(feature = "debug_stack")] + mod debug_stack { + use super::*; + + #[test] + fn should_report_context_in_errors() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect)] + struct Bar { + some_other_field: Option, + baz: Baz, + } + + #[derive(Reflect)] + struct Baz { + value: Vec>, + } + + let mut registry = TypeRegistry::new(); + registry.register::(); + registry.register::(); + registry.register::(); + registry.register::>(); + + let input = r#"{"bevy_reflect::serde::de::tests::debug_stack::Foo":(bar:(some_other_field:Some(123),baz:(value:[(start:0.0,end:1.0)])))}"#; + let mut deserializer = ron::de::Deserializer::from_str(input).unwrap(); + let reflect_deserializer = ReflectDeserializer::new(®istry); + let error = reflect_deserializer + .deserialize(&mut deserializer) + .unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` did not register the `ReflectDeserialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `bevy_reflect::serde::de::tests::debug_stack::Foo` -> `bevy_reflect::serde::de::tests::debug_stack::Bar` -> `bevy_reflect::serde::de::tests::debug_stack::Baz` -> `alloc::vec::Vec>` -> `core::ops::RangeInclusive`)".to_string() + ) + ); + } } } diff --git a/crates/bevy_reflect/src/serde/de/options.rs b/crates/bevy_reflect/src/serde/de/options.rs index 103121a4b1..f4a5467c63 100644 --- a/crates/bevy_reflect/src/serde/de/options.rs +++ b/crates/bevy_reflect/src/serde/de/options.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::serde::de::registration_utils::try_get_registration; use crate::serde::TypedReflectDeserializer; use crate::{DynamicEnum, DynamicTuple, EnumInfo, TypeRegistry, VariantInfo}; @@ -46,14 +47,14 @@ impl<'a, 'de> Visitor<'de> for OptionVisitor<'a> { VariantInfo::Tuple(tuple_info) if tuple_info.field_len() == 1 => { let field = tuple_info.field_at(0).unwrap(); let registration = try_get_registration(*field.ty(), self.registry)?; - let de = TypedReflectDeserializer::new(registration, self.registry); + let de = TypedReflectDeserializer::new_internal(registration, self.registry); let mut value = DynamicTuple::default(); value.insert_boxed(de.deserialize(deserializer)?); let mut option = DynamicEnum::default(); option.set_variant("Some", value); Ok(option) } - info => Err(Error::custom(format_args!( + info => Err(make_custom_error(format_args!( "invalid variant, expected `Some` but got `{}`", info.name() ))), diff --git a/crates/bevy_reflect/src/serde/de/registration_utils.rs b/crates/bevy_reflect/src/serde/de/registration_utils.rs index 8a170d1732..2c88a73939 100644 --- a/crates/bevy_reflect/src/serde/de/registration_utils.rs +++ b/crates/bevy_reflect/src/serde/de/registration_utils.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::{Type, TypeRegistration, TypeRegistry}; use serde::de::Error; @@ -8,8 +9,8 @@ pub(super) fn try_get_registration( ty: Type, registry: &TypeRegistry, ) -> Result<&TypeRegistration, E> { - let registration = registry - .get(ty.id()) - .ok_or_else(|| Error::custom(format_args!("no registration found for type `{ty:?}`")))?; + let registration = registry.get(ty.id()).ok_or_else(|| { + make_custom_error(format_args!("no registration found for type `{ty:?}`")) + })?; Ok(registration) } diff --git a/crates/bevy_reflect/src/serde/de/registrations.rs b/crates/bevy_reflect/src/serde/de/registrations.rs index 9ed10628f7..8b34c348c0 100644 --- a/crates/bevy_reflect/src/serde/de/registrations.rs +++ b/crates/bevy_reflect/src/serde/de/registrations.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::{TypeRegistration, TypeRegistry}; use core::fmt::Formatter; use serde::de::{DeserializeSeed, Error, Visitor}; @@ -42,7 +43,7 @@ impl<'a, 'de> DeserializeSeed<'de> for TypeRegistrationDeserializer<'a> { E: Error, { self.0.get_with_type_path(type_path).ok_or_else(|| { - Error::custom(format_args!("No registration found for `{type_path}`")) + make_custom_error(format_args!("no registration found for `{type_path}`")) }) } } diff --git a/crates/bevy_reflect/src/serde/de/sets.rs b/crates/bevy_reflect/src/serde/de/sets.rs index fcb72a8f77..2127f84b48 100644 --- a/crates/bevy_reflect/src/serde/de/sets.rs +++ b/crates/bevy_reflect/src/serde/de/sets.rs @@ -32,7 +32,7 @@ impl<'a, 'de> Visitor<'de> for SetVisitor<'a> { { let mut dynamic_set = DynamicSet::default(); let value_registration = try_get_registration(self.set_info.value_ty(), self.registry)?; - while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new( + while let Some(value) = set.next_element_seed(TypedReflectDeserializer::new_internal( value_registration, self.registry, ))? { diff --git a/crates/bevy_reflect/src/serde/de/struct_utils.rs b/crates/bevy_reflect/src/serde/de/struct_utils.rs index 5035746a60..836d36c0bd 100644 --- a/crates/bevy_reflect/src/serde/de/struct_utils.rs +++ b/crates/bevy_reflect/src/serde/de/struct_utils.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::serde::de::helpers::{ExpectedValues, Ident}; use crate::serde::de::registration_utils::try_get_registration; use crate::serde::{SerializationData, TypedReflectDeserializer}; @@ -18,8 +19,8 @@ pub(super) trait StructLikeInfo { impl StructLikeInfo for StructInfo { fn field(&self, name: &str) -> Result<&NamedField, E> { Self::field(self, name).ok_or_else(|| { - Error::custom(format_args!( - "no field named {} on struct {}", + make_custom_error(format_args!( + "no field named `{}` on struct `{}`", name, self.type_path(), )) @@ -28,8 +29,8 @@ impl StructLikeInfo for StructInfo { fn field_at(&self, index: usize) -> Result<&NamedField, E> { Self::field_at(self, index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on struct {}", + make_custom_error(format_args!( + "no field at index `{}` on struct `{}`", index, self.type_path(), )) @@ -48,8 +49,8 @@ impl StructLikeInfo for StructInfo { impl StructLikeInfo for StructVariantInfo { fn field(&self, name: &str) -> Result<&NamedField, E> { Self::field(self, name).ok_or_else(|| { - Error::custom(format_args!( - "no field named {} on variant {}", + make_custom_error(format_args!( + "no field named `{}` on variant `{}`", name, self.name(), )) @@ -58,8 +59,8 @@ impl StructLikeInfo for StructVariantInfo { fn field_at(&self, index: usize) -> Result<&NamedField, E> { Self::field_at(self, index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on variant {}", + make_custom_error(format_args!( + "no field at index `{}` on variant `{}`", index, self.name(), )) @@ -92,14 +93,17 @@ where while let Some(Ident(key)) = map.next_key::()? { let field = info.field::(&key).map_err(|_| { let fields = info.iter_fields().map(NamedField::name); - Error::custom(format_args!( + make_custom_error(format_args!( "unknown field `{}`, expected one of {:?}", key, ExpectedValues::from_iter(fields) )) })?; let registration = try_get_registration(*field.ty(), registry)?; - let value = map.next_value_seed(TypedReflectDeserializer::new(registration, registry))?; + let value = map.next_value_seed(TypedReflectDeserializer::new_internal( + registration, + registry, + ))?; dynamic_struct.insert_boxed(&key, value); } @@ -156,7 +160,7 @@ where } let value = seq - .next_element_seed(TypedReflectDeserializer::new( + .next_element_seed(TypedReflectDeserializer::new_internal( try_get_registration(*info.field_at(index)?.ty(), registry)?, registry, ))? diff --git a/crates/bevy_reflect/src/serde/de/tuple_utils.rs b/crates/bevy_reflect/src/serde/de/tuple_utils.rs index 542b096bf5..7dd8ad4481 100644 --- a/crates/bevy_reflect/src/serde/de/tuple_utils.rs +++ b/crates/bevy_reflect/src/serde/de/tuple_utils.rs @@ -1,3 +1,4 @@ +use crate::serde::de::error_utils::make_custom_error; use crate::serde::de::registration_utils::try_get_registration; use crate::serde::{SerializationData, TypedReflectDeserializer}; use crate::{ @@ -18,8 +19,8 @@ impl TupleLikeInfo for TupleInfo { fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { Self::field_at(self, index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple {}", + make_custom_error(format_args!( + "no field at index `{}` on tuple `{}`", index, self.type_path(), )) @@ -34,8 +35,8 @@ impl TupleLikeInfo for TupleStructInfo { fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { Self::field_at(self, index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple struct {}", + make_custom_error(format_args!( + "no field at index `{}` on tuple struct `{}`", index, self.type_path(), )) @@ -50,8 +51,8 @@ impl TupleLikeInfo for TupleVariantInfo { fn field_at(&self, index: usize) -> Result<&UnnamedField, E> { Self::field_at(self, index).ok_or_else(|| { - Error::custom(format_args!( - "no field at index {} on tuple variant {}", + make_custom_error(format_args!( + "no field at index `{}` on tuple variant `{}`", index, self.name(), )) @@ -90,7 +91,7 @@ where } let value = seq - .next_element_seed(TypedReflectDeserializer::new( + .next_element_seed(TypedReflectDeserializer::new_internal( try_get_registration(*info.field_at(index)?.ty(), registry)?, registry, ))? diff --git a/crates/bevy_reflect/src/serde/mod.rs b/crates/bevy_reflect/src/serde/mod.rs index a962c36bd0..bc3137d0b2 100644 --- a/crates/bevy_reflect/src/serde/mod.rs +++ b/crates/bevy_reflect/src/serde/mod.rs @@ -142,7 +142,7 @@ mod tests { #[test] #[should_panic( - expected = "cannot serialize dynamic value without represented type: bevy_reflect::DynamicStruct" + expected = "cannot serialize dynamic value without represented type: `bevy_reflect::DynamicStruct`" )] fn should_not_serialize_unproxied_dynamic() { let registry = TypeRegistry::default(); diff --git a/crates/bevy_reflect/src/serde/ser/arrays.rs b/crates/bevy_reflect/src/serde/ser/arrays.rs index eb84919dfd..b318047ee2 100644 --- a/crates/bevy_reflect/src/serde/ser/arrays.rs +++ b/crates/bevy_reflect/src/serde/ser/arrays.rs @@ -22,7 +22,7 @@ impl<'a> Serialize for ArraySerializer<'a> { { let mut state = serializer.serialize_tuple(self.array.len())?; for value in self.array.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; } state.end() } diff --git a/crates/bevy_reflect/src/serde/ser/enums.rs b/crates/bevy_reflect/src/serde/ser/enums.rs index da151cc894..6951617cb6 100644 --- a/crates/bevy_reflect/src/serde/ser/enums.rs +++ b/crates/bevy_reflect/src/serde/ser/enums.rs @@ -1,6 +1,7 @@ +use crate::serde::ser::error_utils::make_custom_error; use crate::serde::TypedReflectSerializer; use crate::{Enum, TypeInfo, TypeRegistry, VariantInfo, VariantType}; -use serde::ser::{Error, SerializeStructVariant, SerializeTupleVariant}; +use serde::ser::{SerializeStructVariant, SerializeTupleVariant}; use serde::Serialize; /// A serializer for [`Enum`] values. @@ -24,8 +25,8 @@ impl<'a> Serialize for EnumSerializer<'a> { S: serde::Serializer, { let type_info = self.enum_value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", + make_custom_error(format_args!( + "cannot get type info for `{}`", self.enum_value.reflect_type_path() )) })?; @@ -33,7 +34,7 @@ impl<'a> Serialize for EnumSerializer<'a> { let enum_info = match type_info { TypeInfo::Enum(enum_info) => enum_info, info => { - return Err(Error::custom(format_args!( + return Err(make_custom_error(format_args!( "expected enum type but received {info:?}" ))); } @@ -44,7 +45,7 @@ impl<'a> Serialize for EnumSerializer<'a> { let variant_info = enum_info .variant_at(variant_index as usize) .ok_or_else(|| { - Error::custom(format_args!( + make_custom_error(format_args!( "variant at index `{variant_index}` does not exist", )) })?; @@ -66,7 +67,7 @@ impl<'a> Serialize for EnumSerializer<'a> { let struct_info = match variant_info { VariantInfo::Struct(struct_info) => struct_info, info => { - return Err(Error::custom(format_args!( + return Err(make_custom_error(format_args!( "expected struct variant type but received {info:?}", ))); } @@ -82,7 +83,7 @@ impl<'a> Serialize for EnumSerializer<'a> { let field_info = struct_info.field_at(index).unwrap(); state.serialize_field( field_info.name(), - &TypedReflectSerializer::new(field.value(), self.registry), + &TypedReflectSerializer::new_internal(field.value(), self.registry), )?; } state.end() @@ -93,13 +94,14 @@ impl<'a> Serialize for EnumSerializer<'a> { if type_info.type_path_table().module_path() == Some("core::option") && type_info.type_path_table().ident() == Some("Option") { - serializer.serialize_some(&TypedReflectSerializer::new(field, self.registry)) + serializer + .serialize_some(&TypedReflectSerializer::new_internal(field, self.registry)) } else { serializer.serialize_newtype_variant( enum_name, variant_index, variant_name, - &TypedReflectSerializer::new(field, self.registry), + &TypedReflectSerializer::new_internal(field, self.registry), ) } } @@ -111,7 +113,7 @@ impl<'a> Serialize for EnumSerializer<'a> { field_len, )?; for field in self.enum_value.iter_fields() { - state.serialize_field(&TypedReflectSerializer::new( + state.serialize_field(&TypedReflectSerializer::new_internal( field.value(), self.registry, ))?; diff --git a/crates/bevy_reflect/src/serde/ser/error_utils.rs b/crates/bevy_reflect/src/serde/ser/error_utils.rs new file mode 100644 index 0000000000..58a029f986 --- /dev/null +++ b/crates/bevy_reflect/src/serde/ser/error_utils.rs @@ -0,0 +1,26 @@ +use core::fmt::Display; +use serde::ser::Error; + +#[cfg(feature = "debug_stack")] +thread_local! { + /// The thread-local [`TypeInfoStack`] used for debugging. + /// + /// [`TypeInfoStack`]: crate::type_info_stack::TypeInfoStack + pub(super) static TYPE_INFO_STACK: std::cell::RefCell = const { std::cell::RefCell::new( + crate::type_info_stack::TypeInfoStack::new() + ) }; +} + +/// A helper function for generating a custom serialization error message. +/// +/// This function should be preferred over [`Error::custom`] as it will include +/// other useful information, such as the [type info stack]. +/// +/// [type info stack]: crate::type_info_stack::TypeInfoStack +pub(super) fn make_custom_error(msg: impl Display) -> E { + #[cfg(feature = "debug_stack")] + return TYPE_INFO_STACK + .with_borrow(|stack| E::custom(format_args!("{} (stack: {:?})", msg, stack))); + #[cfg(not(feature = "debug_stack"))] + return E::custom(msg); +} diff --git a/crates/bevy_reflect/src/serde/ser/lists.rs b/crates/bevy_reflect/src/serde/ser/lists.rs index 6e6b98958b..ad3bb1a040 100644 --- a/crates/bevy_reflect/src/serde/ser/lists.rs +++ b/crates/bevy_reflect/src/serde/ser/lists.rs @@ -22,7 +22,7 @@ impl<'a> Serialize for ListSerializer<'a> { { let mut state = serializer.serialize_seq(Some(self.list.len()))?; for value in self.list.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; } state.end() } diff --git a/crates/bevy_reflect/src/serde/ser/maps.rs b/crates/bevy_reflect/src/serde/ser/maps.rs index 589cfae3d1..d5493cd35e 100644 --- a/crates/bevy_reflect/src/serde/ser/maps.rs +++ b/crates/bevy_reflect/src/serde/ser/maps.rs @@ -23,8 +23,8 @@ impl<'a> Serialize for MapSerializer<'a> { let mut state = serializer.serialize_map(Some(self.map.len()))?; for (key, value) in self.map.iter() { state.serialize_entry( - &TypedReflectSerializer::new(key, self.registry), - &TypedReflectSerializer::new(value, self.registry), + &TypedReflectSerializer::new_internal(key, self.registry), + &TypedReflectSerializer::new_internal(value, self.registry), )?; } state.end() diff --git a/crates/bevy_reflect/src/serde/ser/mod.rs b/crates/bevy_reflect/src/serde/ser/mod.rs index 85876fabf2..ee2a283e82 100644 --- a/crates/bevy_reflect/src/serde/ser/mod.rs +++ b/crates/bevy_reflect/src/serde/ser/mod.rs @@ -3,6 +3,7 @@ pub use serializer::*; mod arrays; mod enums; +mod error_utils; mod lists; mod maps; mod serializable; @@ -429,11 +430,20 @@ mod tests { let serializer = ReflectSerializer::new(&value, ®istry); let error = ron::ser::to_string(&serializer).unwrap_err(); + #[cfg(feature = "debug_stack")] assert_eq!( error, ron::Error::Message( - "Type `core::ops::RangeInclusive` is not registered in the type registry" - .to_string() + "type `core::ops::RangeInclusive` is not registered in the type registry (stack: `core::ops::RangeInclusive`)" + .to_string(), + ) + ); + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` is not registered in the type registry" + .to_string(), ) ); } @@ -446,11 +456,63 @@ mod tests { let serializer = ReflectSerializer::new(&value, ®istry); let error = ron::ser::to_string(&serializer).unwrap_err(); + #[cfg(feature = "debug_stack")] assert_eq!( error, ron::Error::Message( - "Type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() + "type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data` (stack: `core::ops::RangeInclusive`)".to_string() + ) + ); + #[cfg(not(feature = "debug_stack"))] + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`".to_string() ) ); } + + #[cfg(feature = "debug_stack")] + mod debug_stack { + use super::*; + + #[test] + fn should_report_context_in_errors() { + #[derive(Reflect)] + struct Foo { + bar: Bar, + } + + #[derive(Reflect)] + struct Bar { + some_other_field: Option, + baz: Baz, + } + + #[derive(Reflect)] + struct Baz { + value: Vec>, + } + + let value = Foo { + bar: Bar { + some_other_field: Some(123), + baz: Baz { + value: vec![0.0..=1.0], + }, + }, + }; + + let registry = TypeRegistry::new(); + let serializer = ReflectSerializer::new(&value, ®istry); + + let error = ron::ser::to_string(&serializer).unwrap_err(); + assert_eq!( + error, + ron::Error::Message( + "type `core::ops::RangeInclusive` is not registered in the type registry (stack: `bevy_reflect::serde::ser::tests::debug_stack::Foo` -> `bevy_reflect::serde::ser::tests::debug_stack::Bar` -> `bevy_reflect::serde::ser::tests::debug_stack::Baz` -> `alloc::vec::Vec>` -> `core::ops::RangeInclusive`)".to_string() + ) + ); + } + } } diff --git a/crates/bevy_reflect/src/serde/ser/serializable.rs b/crates/bevy_reflect/src/serde/ser/serializable.rs index 9c285816f2..48d3b29683 100644 --- a/crates/bevy_reflect/src/serde/ser/serializable.rs +++ b/crates/bevy_reflect/src/serde/ser/serializable.rs @@ -1,3 +1,4 @@ +use crate::serde::ser::error_utils::make_custom_error; use crate::{PartialReflect, ReflectSerialize, TypeRegistry}; use serde::ser::Error; use std::ops::Deref; @@ -23,29 +24,29 @@ impl<'a> Serializable<'a> { type_registry: &TypeRegistry, ) -> Result, E> { let value = value.try_as_reflect().ok_or_else(|| { - Error::custom(format_args!( - "Type '{}' does not implement `Reflect`", + make_custom_error(format_args!( + "type `{}` does not implement `Reflect`", value.reflect_type_path() )) })?; let info = value.get_represented_type_info().ok_or_else(|| { - Error::custom(format_args!( - "Type '{}' does not represent any type", + make_custom_error(format_args!( + "type `{}` does not represent any type", value.reflect_type_path(), )) })?; let registration = type_registry.get(info.type_id()).ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` is not registered in the type registry", + make_custom_error(format_args!( + "type `{}` is not registered in the type registry", info.type_path(), )) })?; let reflect_serialize = registration.data::().ok_or_else(|| { - Error::custom(format_args!( - "Type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", + make_custom_error(format_args!( + "type `{}` did not register the `ReflectSerialize` type data. For certain types, this may need to be registered manually using `register_type_data`", info.type_path(), )) })?; diff --git a/crates/bevy_reflect/src/serde/ser/serializer.rs b/crates/bevy_reflect/src/serde/ser/serializer.rs index eb3afe96f8..cdc00452aa 100644 --- a/crates/bevy_reflect/src/serde/ser/serializer.rs +++ b/crates/bevy_reflect/src/serde/ser/serializer.rs @@ -1,5 +1,8 @@ use crate::serde::ser::arrays::ArraySerializer; use crate::serde::ser::enums::EnumSerializer; +use crate::serde::ser::error_utils::make_custom_error; +#[cfg(feature = "debug_stack")] +use crate::serde::ser::error_utils::TYPE_INFO_STACK; use crate::serde::ser::lists::ListSerializer; use crate::serde::ser::maps::MapSerializer; use crate::serde::ser::sets::SetSerializer; @@ -8,7 +11,7 @@ use crate::serde::ser::tuple_structs::TupleStructSerializer; use crate::serde::ser::tuples::TupleSerializer; use crate::serde::Serializable; use crate::{PartialReflect, ReflectRef, TypeRegistry}; -use serde::ser::{Error, SerializeMap}; +use serde::ser::SerializeMap; use serde::Serialize; /// A general purpose serializer for reflected types. @@ -54,7 +57,7 @@ pub struct ReflectSerializer<'a> { impl<'a> ReflectSerializer<'a> { pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - ReflectSerializer { value, registry } + Self { value, registry } } } @@ -69,13 +72,13 @@ impl<'a> Serialize for ReflectSerializer<'a> { .get_represented_type_info() .ok_or_else(|| { if self.value.is_dynamic() { - Error::custom(format_args!( - "cannot serialize dynamic value without represented type: {}", + make_custom_error(format_args!( + "cannot serialize dynamic value without represented type: `{}`", self.value.reflect_type_path() )) } else { - Error::custom(format_args!( - "cannot get type info for {}", + make_custom_error(format_args!( + "cannot get type info for `{}`", self.value.reflect_type_path() )) } @@ -132,7 +135,15 @@ pub struct TypedReflectSerializer<'a> { impl<'a> TypedReflectSerializer<'a> { pub fn new(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { - TypedReflectSerializer { value, registry } + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new()); + + Self { value, registry } + } + + /// An internal constructor for creating a serializer without resetting the type info stack. + pub(super) fn new_internal(value: &'a dyn PartialReflect, registry: &'a TypeRegistry) -> Self { + Self { value, registry } } } @@ -141,14 +152,29 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { where S: serde::Serializer, { + #[cfg(feature = "debug_stack")] + { + let info = self.value.get_represented_type_info().ok_or_else(|| { + make_custom_error(format_args!( + "type `{}` does not represent any type", + self.value.reflect_type_path(), + )) + })?; + + TYPE_INFO_STACK.with_borrow_mut(|stack| stack.push(info)); + } + // Handle both Value case and types that have a custom `Serialize` let serializable = Serializable::try_from_reflect_value::(self.value, self.registry); if let Ok(serializable) = serializable { + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + return serializable.serialize(serializer); } - match self.value.reflect_ref() { + let output = match self.value.reflect_ref() { ReflectRef::Struct(value) => { StructSerializer::new(value, self.registry).serialize(serializer) } @@ -174,6 +200,11 @@ impl<'a> Serialize for TypedReflectSerializer<'a> { EnumSerializer::new(value, self.registry).serialize(serializer) } ReflectRef::Value(_) => Err(serializable.err().unwrap()), - } + }; + + #[cfg(feature = "debug_stack")] + TYPE_INFO_STACK.with_borrow_mut(crate::type_info_stack::TypeInfoStack::pop); + + output } } diff --git a/crates/bevy_reflect/src/serde/ser/sets.rs b/crates/bevy_reflect/src/serde/ser/sets.rs index 9a5abab572..846f9e4f84 100644 --- a/crates/bevy_reflect/src/serde/ser/sets.rs +++ b/crates/bevy_reflect/src/serde/ser/sets.rs @@ -22,7 +22,7 @@ impl<'a> Serialize for SetSerializer<'a> { { let mut state = serializer.serialize_seq(Some(self.set.len()))?; for value in self.set.iter() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; } state.end() } diff --git a/crates/bevy_reflect/src/serde/ser/structs.rs b/crates/bevy_reflect/src/serde/ser/structs.rs index 2c31280f05..7763e297fe 100644 --- a/crates/bevy_reflect/src/serde/ser/structs.rs +++ b/crates/bevy_reflect/src/serde/ser/structs.rs @@ -1,6 +1,7 @@ +use crate::serde::ser::error_utils::make_custom_error; use crate::serde::{SerializationData, TypedReflectSerializer}; use crate::{Struct, TypeInfo, TypeRegistry}; -use serde::ser::{Error, SerializeStruct}; +use serde::ser::SerializeStruct; use serde::Serialize; /// A serializer for [`Struct`] values. @@ -27,8 +28,8 @@ impl<'a> Serialize for StructSerializer<'a> { .struct_value .get_represented_type_info() .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", + make_custom_error(format_args!( + "cannot get type info for `{}`", self.struct_value.reflect_type_path() )) })?; @@ -36,7 +37,7 @@ impl<'a> Serialize for StructSerializer<'a> { let struct_info = match type_info { TypeInfo::Struct(struct_info) => struct_info, info => { - return Err(Error::custom(format_args!( + return Err(make_custom_error(format_args!( "expected struct type but received {info:?}" ))); } @@ -60,7 +61,10 @@ impl<'a> Serialize for StructSerializer<'a> { continue; } let key = struct_info.field_at(index).unwrap().name(); - state.serialize_field(key, &TypedReflectSerializer::new(value, self.registry))?; + state.serialize_field( + key, + &TypedReflectSerializer::new_internal(value, self.registry), + )?; } state.end() } diff --git a/crates/bevy_reflect/src/serde/ser/tuple_structs.rs b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs index e60c9ad66c..625e41e116 100644 --- a/crates/bevy_reflect/src/serde/ser/tuple_structs.rs +++ b/crates/bevy_reflect/src/serde/ser/tuple_structs.rs @@ -1,6 +1,7 @@ +use crate::serde::ser::error_utils::make_custom_error; use crate::serde::{SerializationData, TypedReflectSerializer}; use crate::{TupleStruct, TypeInfo, TypeRegistry}; -use serde::ser::{Error, SerializeTupleStruct}; +use serde::ser::SerializeTupleStruct; use serde::Serialize; /// A serializer for [`TupleStruct`] values. @@ -27,8 +28,8 @@ impl<'a> Serialize for TupleStructSerializer<'a> { .tuple_struct .get_represented_type_info() .ok_or_else(|| { - Error::custom(format_args!( - "cannot get type info for {}", + make_custom_error(format_args!( + "cannot get type info for `{}`", self.tuple_struct.reflect_type_path() )) })?; @@ -36,7 +37,7 @@ impl<'a> Serialize for TupleStructSerializer<'a> { let tuple_struct_info = match type_info { TypeInfo::TupleStruct(tuple_struct_info) => tuple_struct_info, info => { - return Err(Error::custom(format_args!( + return Err(make_custom_error(format_args!( "expected tuple struct type but received {info:?}" ))); } @@ -59,7 +60,7 @@ impl<'a> Serialize for TupleStructSerializer<'a> { { continue; } - state.serialize_field(&TypedReflectSerializer::new(value, self.registry))?; + state.serialize_field(&TypedReflectSerializer::new_internal(value, self.registry))?; } state.end() } diff --git a/crates/bevy_reflect/src/serde/ser/tuples.rs b/crates/bevy_reflect/src/serde/ser/tuples.rs index c3b90d5f56..e106149613 100644 --- a/crates/bevy_reflect/src/serde/ser/tuples.rs +++ b/crates/bevy_reflect/src/serde/ser/tuples.rs @@ -23,7 +23,7 @@ impl<'a> Serialize for TupleSerializer<'a> { let mut state = serializer.serialize_tuple(self.tuple.field_len())?; for value in self.tuple.iter_fields() { - state.serialize_element(&TypedReflectSerializer::new(value, self.registry))?; + state.serialize_element(&TypedReflectSerializer::new_internal(value, self.registry))?; } state.end() } diff --git a/crates/bevy_reflect/src/type_info_stack.rs b/crates/bevy_reflect/src/type_info_stack.rs new file mode 100644 index 0000000000..f7f22a54ac --- /dev/null +++ b/crates/bevy_reflect/src/type_info_stack.rs @@ -0,0 +1,49 @@ +use crate::TypeInfo; +use core::fmt::{Debug, Formatter}; +use core::slice::Iter; + +/// Helper struct for managing a stack of [`TypeInfo`] instances. +/// +/// This is useful for tracking the type hierarchy when serializing and deserializing types. +#[derive(Default, Clone)] +pub(crate) struct TypeInfoStack { + stack: Vec<&'static TypeInfo>, +} + +impl TypeInfoStack { + /// Create a new empty [`TypeInfoStack`]. + pub const fn new() -> Self { + Self { stack: Vec::new() } + } + + /// Push a new [`TypeInfo`] onto the stack. + pub fn push(&mut self, type_info: &'static TypeInfo) { + self.stack.push(type_info); + } + + /// Pop the last [`TypeInfo`] off the stack. + pub fn pop(&mut self) { + self.stack.pop(); + } + + /// Get an iterator over the stack in the order they were pushed. + pub fn iter(&self) -> Iter<&'static TypeInfo> { + self.stack.iter() + } +} + +impl Debug for TypeInfoStack { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + let mut iter = self.iter(); + + if let Some(first) = iter.next() { + write!(f, "`{}`", first.type_path())?; + } + + for info in iter { + write!(f, " -> `{}`", info.type_path())?; + } + + Ok(()) + } +} From 0cf276f239df231602240fef61cde257e0fb5374 Mon Sep 17 00:00:00 2001 From: Han Damin Date: Tue, 10 Sep 2024 07:34:44 +0900 Subject: [PATCH 030/115] Enhance ReflectCommandExt (#15128) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Objective - Enhance #15125 ## Solution - Modified `ReflectCommandExt::insert_reflect` to accept and handle both components and bundles. --------- Co-authored-by: Gonçalo Rica Pais da Silva Co-authored-by: Lixou <82600264+DasLixou@users.noreply.github.com> Co-authored-by: Hennadii Chernyshchyk --- .../bevy_ecs/src/reflect/entity_commands.rs | 63 ++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/bevy_ecs/src/reflect/entity_commands.rs b/crates/bevy_ecs/src/reflect/entity_commands.rs index a48d1299bd..f49d5870b6 100644 --- a/crates/bevy_ecs/src/reflect/entity_commands.rs +++ b/crates/bevy_ecs/src/reflect/entity_commands.rs @@ -2,7 +2,11 @@ use crate::prelude::Mut; use crate::reflect::AppTypeRegistry; use crate::system::{EntityCommands, Resource}; use crate::world::Command; -use crate::{entity::Entity, reflect::ReflectComponent, world::World}; +use crate::{ + entity::Entity, + reflect::{ReflectBundle, ReflectComponent}, + world::World, +}; use bevy_reflect::{PartialReflect, TypeRegistry}; use std::borrow::Cow; use std::marker::PhantomData; @@ -198,12 +202,16 @@ fn insert_reflect( panic!("error[B0003]: Could not insert a reflected component (of type {type_path}) for entity {entity:?} because it doesn't exist in this World. See: https://bevyengine.org/learn/errors/b0003"); }; let Some(type_registration) = type_registry.get(type_info.type_id()) else { - panic!("Could not get type registration (for component type {type_path}) because it doesn't exist in the TypeRegistry."); + panic!("`{type_path}` should be registered in type registry via `App::register_type<{type_path}>`"); }; - let Some(reflect_component) = type_registration.data::() else { - panic!("Could not get ReflectComponent data (for component type {type_path}) because it doesn't exist in this TypeRegistration."); - }; - reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry); + + if let Some(reflect_component) = type_registration.data::() { + reflect_component.insert(&mut entity, component.as_partial_reflect(), type_registry); + } else if let Some(reflect_bundle) = type_registration.data::() { + reflect_bundle.insert(&mut entity, component.as_partial_reflect(), type_registry); + } else { + panic!("`{type_path}` should have #[reflect(Component)] or #[reflect(Bundle)]"); + } } /// A [`Command`] that adds the boxed reflect component to an entity using the data in @@ -313,9 +321,9 @@ impl> Command for RemoveReflectWithRegistry #[cfg(test)] mod tests { use crate::prelude::{AppTypeRegistry, ReflectComponent}; - use crate::reflect::ReflectCommandExt; + use crate::reflect::{ReflectBundle, ReflectCommandExt}; use crate::system::{Commands, SystemState}; - use crate::{self as bevy_ecs, component::Component, world::World}; + use crate::{self as bevy_ecs, bundle::Bundle, component::Component, world::World}; use bevy_ecs_macros::Resource; use bevy_reflect::{PartialReflect, Reflect, TypeRegistry}; @@ -334,6 +342,17 @@ mod tests { #[reflect(Component)] struct ComponentA(u32); + #[derive(Component, Reflect, Default, PartialEq, Eq, Debug)] + #[reflect(Component)] + struct ComponentB(u32); + + #[derive(Bundle, Reflect, Default, Debug, PartialEq)] + #[reflect(Bundle)] + struct BundleA { + a: ComponentA, + b: ComponentB, + } + #[test] fn insert_reflected() { let mut world = World::new(); @@ -458,4 +477,32 @@ mod tests { assert_eq!(world.entity(entity).get::(), None); } + + #[test] + fn insert_reflect_bundle() { + let mut world = World::new(); + + let type_registry = AppTypeRegistry::default(); + { + let mut registry = type_registry.write(); + registry.register::(); + registry.register_type_data::(); + } + world.insert_resource(type_registry); + + let mut system_state: SystemState = SystemState::new(&mut world); + let mut commands = system_state.get_mut(&mut world); + + let entity = commands.spawn_empty().id(); + let bundle = Box::new(BundleA { + a: ComponentA(31), + b: ComponentB(20), + }) as Box; + commands.entity(entity).insert_reflect(bundle); + + system_state.apply(&mut world); + + assert_eq!(world.get::(entity), Some(&ComponentA(31))); + assert_eq!(world.get::(entity), Some(&ComponentB(20))); + } } From f326705cabadfa79e173be7bcb413c1edcdda134 Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Tue, 10 Sep 2024 10:34:58 +1200 Subject: [PATCH 031/115] Remove OrthographicProjection.scale (adopted) (#15075) # Objective Hello! I am adopting #11022 to resolve conflicts with `main`. tldr: this removes `scale` in favour of `scaling_mode`. Please see the original PR for explanation/discussion. Also relates to #2580. ## Migration Guide Replace all uses of `scale` with `scaling_mode`, keeping in mind that `scale` is (was) a multiplier. For example, replace ```rust scale: 2.0, scaling_mode: ScalingMode::FixedHorizontal(4.0), ``` with ```rust scaling_mode: ScalingMode::FixedHorizontal(8.0), ``` --------- Co-authored-by: Stepan Koltsov --- crates/bevy_gltf/src/loader.rs | 3 +-- crates/bevy_render/src/camera/projection.rs | 20 ++++---------------- examples/2d/pixel_grid_snap.rs | 3 ++- examples/3d/pbr.rs | 3 ++- examples/math/custom_primitives.rs | 1 - examples/stress_tests/many_lights.rs | 3 +-- 6 files changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/bevy_gltf/src/loader.rs b/crates/bevy_gltf/src/loader.rs index e2c193e110..88a1964370 100644 --- a/crates/bevy_gltf/src/loader.rs +++ b/crates/bevy_gltf/src/loader.rs @@ -1254,8 +1254,7 @@ fn load_node( let orthographic_projection = OrthographicProjection { near: orthographic.znear(), far: orthographic.zfar(), - scaling_mode: ScalingMode::FixedHorizontal(1.0), - scale: xmag, + scaling_mode: ScalingMode::FixedHorizontal(xmag), ..OrthographicProjection::default_3d() }; diff --git a/crates/bevy_render/src/camera/projection.rs b/crates/bevy_render/src/camera/projection.rs index bb6b047690..f719edda23 100644 --- a/crates/bevy_render/src/camera/projection.rs +++ b/crates/bevy_render/src/camera/projection.rs @@ -371,17 +371,6 @@ pub struct OrthographicProjection { /// /// Defaults to `ScalingMode::WindowSize(1.0)` pub scaling_mode: ScalingMode, - /// Scales the projection. - /// - /// As scale increases, the apparent size of objects decreases, and vice versa. - /// - /// Note: scaling can be set by [`scaling_mode`](Self::scaling_mode) as well. - /// This parameter scales on top of that. - /// - /// This property is particularly useful in implementing zoom functionality. - /// - /// Defaults to `1.0`. - pub scale: f32, /// The area that the projection covers relative to `viewport_origin`. /// /// Bevy's [`camera_system`](crate::camera::camera_system) automatically @@ -454,10 +443,10 @@ impl CameraProjection for OrthographicProjection { } self.area = Rect::new( - self.scale * -origin_x, - self.scale * -origin_y, - self.scale * (projection_width - origin_x), - self.scale * (projection_height - origin_y), + -origin_x, + -origin_y, + projection_width - origin_x, + projection_height - origin_y, ); } @@ -505,7 +494,6 @@ impl OrthographicProjection { /// objects that are behind it. pub fn default_3d() -> Self { OrthographicProjection { - scale: 1.0, near: 0.0, far: 1000.0, viewport_origin: Vec2::new(0.5, 0.5), diff --git a/examples/2d/pixel_grid_snap.rs b/examples/2d/pixel_grid_snap.rs index c191ad8c34..60b3c38506 100644 --- a/examples/2d/pixel_grid_snap.rs +++ b/examples/2d/pixel_grid_snap.rs @@ -12,6 +12,7 @@ use bevy::{ sprite::MaterialMesh2dBundle, window::WindowResized, }; +use bevy_render::camera::ScalingMode; /// In-game resolution width. const RES_WIDTH: u32 = 160; @@ -176,6 +177,6 @@ fn fit_canvas( let h_scale = event.width / RES_WIDTH as f32; let v_scale = event.height / RES_HEIGHT as f32; let mut projection = projections.single_mut(); - projection.scale = 1. / h_scale.min(v_scale).round(); + projection.scaling_mode = ScalingMode::WindowSize(h_scale.min(v_scale).round()); } } diff --git a/examples/3d/pbr.rs b/examples/3d/pbr.rs index a20adb8edf..021dd06ac8 100644 --- a/examples/3d/pbr.rs +++ b/examples/3d/pbr.rs @@ -1,5 +1,6 @@ //! This example shows how to configure Physically Based Rendering (PBR) parameters. +use bevy::render::camera::ScalingMode; use bevy::{asset::LoadState, prelude::*}; fn main() { @@ -120,7 +121,7 @@ fn setup( Camera3dBundle { transform: Transform::from_xyz(0.0, 0.0, 8.0).looking_at(Vec3::default(), Vec3::Y), projection: OrthographicProjection { - scale: 0.01, + scaling_mode: ScalingMode::WindowSize(100.0), ..OrthographicProjection::default_3d() } .into(), diff --git a/examples/math/custom_primitives.rs b/examples/math/custom_primitives.rs index 15432898e7..b06ec808fe 100644 --- a/examples/math/custom_primitives.rs +++ b/examples/math/custom_primitives.rs @@ -36,7 +36,6 @@ const TRANSFORM_2D: Transform = Transform { const PROJECTION_2D: Projection = Projection::Orthographic(OrthographicProjection { near: -1.0, far: 10.0, - scale: 1.0, viewport_origin: Vec2::new(0.5, 0.5), scaling_mode: ScalingMode::AutoMax { max_width: 8.0, diff --git a/examples/stress_tests/many_lights.rs b/examples/stress_tests/many_lights.rs index 33244a99b9..0fe8ff3593 100644 --- a/examples/stress_tests/many_lights.rs +++ b/examples/stress_tests/many_lights.rs @@ -94,8 +94,7 @@ fn setup( match std::env::args().nth(1).as_deref() { Some("orthographic") => commands.spawn(Camera3dBundle { projection: OrthographicProjection { - scale: 20.0, - scaling_mode: ScalingMode::FixedHorizontal(1.0), + scaling_mode: ScalingMode::FixedHorizontal(20.0), ..OrthographicProjection::default_3d() } .into(), From 8d143e3ed8ccba37d684f47aaf25617efc0c2217 Mon Sep 17 00:00:00 2001 From: ickshonpe Date: Mon, 9 Sep 2024 23:35:29 +0100 Subject: [PATCH 032/115] ui material node border calculations fix (#15119) # Objective Fixes #15115 ## Solution Retrieve the size of the node's parent in a separate query and base percentage border values on the parent node's width (or the width of the viewport in the case of root nodes). --- crates/bevy_ui/src/render/ui_material_pipeline.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/crates/bevy_ui/src/render/ui_material_pipeline.rs b/crates/bevy_ui/src/render/ui_material_pipeline.rs index ef81d3c33f..1a3cf489ac 100644 --- a/crates/bevy_ui/src/render/ui_material_pipeline.rs +++ b/crates/bevy_ui/src/render/ui_material_pipeline.rs @@ -8,6 +8,7 @@ use bevy_ecs::{ system::lifetimeless::{Read, SRes}, system::*, }; +use bevy_hierarchy::Parent; use bevy_math::{FloatOrd, Mat4, Rect, Vec2, Vec4Swizzles}; use bevy_render::{ extract_component::ExtractComponentPlugin, @@ -352,6 +353,7 @@ impl Default for ExtractedUiMaterialNodes { } } +#[allow(clippy::too_many_arguments)] pub fn extract_ui_material_nodes( mut extracted_uinodes: ResMut>, materials: Extract>>, @@ -367,12 +369,14 @@ pub fn extract_ui_material_nodes( &ViewVisibility, Option<&CalculatedClip>, Option<&TargetCamera>, + Option<&Parent>, ), Without, >, >, windows: Extract>>, ui_scale: Extract>, + node_query: Extract>, ) { let ui_logical_viewport_size = windows .get_single() @@ -385,7 +389,7 @@ pub fn extract_ui_material_nodes( // If there is only one camera, we use it as default let default_single_camera = default_ui_camera.get(); - for (entity, uinode, style, transform, handle, view_visibility, clip, camera) in + for (entity, uinode, style, transform, handle, view_visibility, clip, camera, maybe_parent) in uinode_query.iter() { let Some(camera_entity) = camera.map(TargetCamera::entity).or(default_single_camera) else { @@ -404,7 +408,11 @@ pub fn extract_ui_material_nodes( // Both vertical and horizontal percentage border values are calculated based on the width of the parent node // - let parent_width = uinode.size().x; + let parent_width = maybe_parent + .and_then(|parent| node_query.get(parent.get()).ok()) + .map(|parent_node| parent_node.size().x) + .unwrap_or(ui_logical_viewport_size.x); + let left = resolve_border_thickness(style.border.left, parent_width, ui_logical_viewport_size) / uinode.size().x; From b9b43ad89c5eddd83b702084ed8308cf420bf96c Mon Sep 17 00:00:00 2001 From: Rich Churcher Date: Tue, 10 Sep 2024 11:30:52 +1200 Subject: [PATCH 033/115] Add examples for orthographic and perspective zoom (#15092) # Objective Add examples for zooming (and orbiting) orthographic and perspective cameras. I'm pretty green with 3D, so please treat with suspicion! I note that if/when #15075 is merged, `.scale` will go away so this example uses `.scaling_mode`. Closes #2580 --- Cargo.toml | 12 ++ examples/README.md | 1 + examples/camera/projection_zoom.rs | 245 +++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 examples/camera/projection_zoom.rs diff --git a/Cargo.toml b/Cargo.toml index 0a0ea25aab..634ef2391d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3244,6 +3244,18 @@ description = "A first-person camera that uses a world model and a view model wi category = "Camera" wasm = true +[[example]] +name = "projection_zoom" +path = "examples/camera/projection_zoom.rs" +doc-scrape-examples = true + +[package.metadata.example.projection_zoom] +name = "Projection Zoom" +description = "Shows how to zoom and orbit orthographic and perspective projection cameras." +category = "Camera" +wasm = true + + [package.metadata.example.fps_overlay] name = "FPS overlay" description = "Demonstrates FPS overlay" diff --git a/examples/README.md b/examples/README.md index e2e3bbf426..542a827640 100644 --- a/examples/README.md +++ b/examples/README.md @@ -255,6 +255,7 @@ Example | Description --- | --- [2D top-down camera](../examples/camera/2d_top_down_camera.rs) | A 2D top-down camera smoothly following player movements [First person view model](../examples/camera/first_person_view_model.rs) | A first-person camera that uses a world model and a view model with different field of views (FOV) +[Projection Zoom](../examples/camera/projection_zoom.rs) | Shows how to zoom and orbit orthographic and perspective projection cameras. ## Dev tools diff --git a/examples/camera/projection_zoom.rs b/examples/camera/projection_zoom.rs new file mode 100644 index 0000000000..d490d971b8 --- /dev/null +++ b/examples/camera/projection_zoom.rs @@ -0,0 +1,245 @@ +//! Shows how to zoom and orbit orthographic and perspective projection cameras. + +use std::{ + f32::consts::{FRAC_PI_2, PI}, + ops::Range, +}; + +use bevy::{input::mouse::AccumulatedMouseScroll, prelude::*, render::camera::ScalingMode}; + +#[derive(Debug, Default, Resource)] +struct CameraSettings { + pub orbit_distance: f32, + // Multiply keyboard inputs by this factor + pub orbit_speed: f32, + // Clamp fixed vertical scale to this range + pub orthographic_zoom_range: Range, + // Multiply mouse wheel inputs by this factor + pub orthographic_zoom_speed: f32, + // Clamp field of view to this range + pub perspective_zoom_range: Range, + // Multiply mouse wheel inputs by this factor + pub perspective_zoom_speed: f32, + // Clamp pitch to this range + pub pitch_range: Range, +} + +fn main() { + App::new() + .add_plugins(DefaultPlugins) + .init_resource::() + .add_systems(Startup, (setup, instructions)) + .add_systems(Update, (orbit, switch_projection, zoom)) + .run(); +} + +/// Set up a simple 3D scene +fn setup( + mut camera_settings: ResMut, + mut commands: Commands, + mut meshes: ResMut>, + mut materials: ResMut>, +) { + // Perspective projections use field of view, expressed in radians. We would + // normally not set it to more than π, which represents a 180° FOV. + let min_fov = PI / 5.; + let max_fov = PI - 0.2; + + // In orthographic projections, we specify sizes in world units. The below values + // are very roughly similar to the above FOV settings, in terms of how "far away" + // the subject will appear when used with FixedVertical scaling mode. + let min_zoom = 5.0; + let max_zoom = 150.0; + + // Limiting pitch stops some unexpected rotation past 90° up or down. + let pitch_limit = FRAC_PI_2 - 0.01; + + camera_settings.orbit_distance = 10.0; + camera_settings.orbit_speed = 1.0; + camera_settings.orthographic_zoom_range = min_zoom..max_zoom; + camera_settings.orthographic_zoom_speed = 1.0; + camera_settings.perspective_zoom_range = min_fov..max_fov; + // Changes in FOV are much more noticeable due to its limited range in radians + camera_settings.perspective_zoom_speed = 0.05; + camera_settings.pitch_range = -pitch_limit..pitch_limit; + + commands.spawn(( + Name::new("Camera"), + Camera3dBundle { + projection: OrthographicProjection { + scaling_mode: ScalingMode::FixedVertical( + camera_settings.orthographic_zoom_range.start, + ), + ..OrthographicProjection::default_3d() + } + .into(), + transform: Transform::from_xyz(5.0, 5.0, 5.0).looking_at(Vec3::ZERO, Vec3::Y), + ..default() + }, + )); + + commands.spawn(( + Name::new("Plane"), + PbrBundle { + mesh: meshes.add(Plane3d::default().mesh().size(5.0, 5.0)), + material: materials.add(StandardMaterial { + base_color: Color::srgb(0.3, 0.5, 0.3), + // Turning off culling keeps the plane visible when viewed from beneath. + cull_mode: None, + ..default() + }), + ..default() + }, + )); + + commands.spawn(( + Name::new("Cube"), + PbrBundle { + mesh: meshes.add(Cuboid::default()), + material: materials.add(Color::srgb(0.8, 0.7, 0.6)), + transform: Transform::from_xyz(1.5, 0.51, 1.5), + ..default() + }, + )); + + commands.spawn(( + Name::new("Light"), + PointLightBundle { + transform: Transform::from_xyz(3.0, 8.0, 5.0), + ..default() + }, + )); +} + +fn instructions(mut commands: Commands) { + commands + .spawn(( + Name::new("Instructions"), + NodeBundle { + style: Style { + align_items: AlignItems::Start, + flex_direction: FlexDirection::Column, + justify_content: JustifyContent::Start, + width: Val::Percent(100.), + ..default() + }, + ..default() + }, + )) + .with_children(|parent| { + parent.spawn(TextBundle::from_section( + "Scroll mouse wheel to zoom in/out", + TextStyle::default(), + )); + parent.spawn(TextBundle::from_section( + "W or S: pitch", + TextStyle::default(), + )); + parent.spawn(TextBundle::from_section( + "A or D: yaw", + TextStyle::default(), + )); + }); +} + +fn orbit( + mut camera: Query<&mut Transform, With>, + camera_settings: Res, + keyboard_input: Res>, + time: Res