Finish the work on try_apply (#12646)

# Objective

Finish the `try_apply` implementation started in #6770 by @feyokorenhof.
Supersedes and closes #6770. Closes #6182

## Solution

Add `try_apply` to `Reflect` and implement it in all the places that
implement `Reflect`.

---

## Changelog

Added `try_apply` to `Reflect`.

---------

Co-authored-by: Feyo Korenhof <feyokorenhof@gmail.com>
Co-authored-by: Gino Valente <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
Brezak 2024-05-08 16:26:01 +02:00 committed by GitHub
parent 2f87bb8c1f
commit 9c4ac7c297
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 474 additions and 115 deletions

View file

@ -21,7 +21,7 @@ pub(crate) struct EnumVariantConstructors {
pub(crate) fn get_variant_constructors(
reflect_enum: &ReflectEnum,
ref_value: &Ident,
can_panic: bool,
return_apply_error: bool,
) -> EnumVariantConstructors {
let bevy_reflect_path = reflect_enum.meta().bevy_reflect_path();
let variant_count = reflect_enum.variants().len();
@ -50,21 +50,6 @@ pub(crate) fn get_variant_constructors(
_ => quote! { #FQDefault::default() }
}
} else {
let (resolve_error, resolve_missing) = if can_panic {
let field_ref_str = match &field_ident {
Member::Named(ident) => format!("the field `{ident}`"),
Member::Unnamed(index) => format!("the field at index {}", index.index)
};
let ty = field.data.ty.to_token_stream();
let on_error = format!("{field_ref_str} should be of type `{ty}`");
let on_missing = format!("{field_ref_str} is required but could not be found");
(quote!(.expect(#on_error)), quote!(.expect(#on_missing)))
} else {
(quote!(?), quote!(?))
};
let field_accessor = match &field.data.ident {
Some(ident) => {
let name = ident.to_string();
@ -74,6 +59,31 @@ pub(crate) fn get_variant_constructors(
};
reflect_index += 1;
let (resolve_error, resolve_missing) = if return_apply_error {
let field_ref_str = match &field_ident {
Member::Named(ident) => format!("{ident}"),
Member::Unnamed(index) => format!(".{}", index.index)
};
let ty = field.data.ty.to_token_stream();
(
quote!(.ok_or(#bevy_reflect_path::ApplyError::MismatchedTypes {
// The unwrap won't panic. By this point the #field_accessor would have been invoked once and any failure to
// access the given field handled by the `resolve_missing` code bellow.
from_type: ::core::convert::Into::into(
#bevy_reflect_path::DynamicTypePath::reflect_type_path(#FQOption::unwrap(#field_accessor))
),
to_type: ::core::convert::Into::into(<#ty as #bevy_reflect_path::TypePath>::type_path())
})?),
quote!(.ok_or(#bevy_reflect_path::ApplyError::MissingEnumField {
variant_name: ::core::convert::Into::into(#name),
field_name: ::core::convert::Into::into(#field_ref_str)
})?)
)
} else {
(quote!(?), quote!(?))
};
match &field.attrs.default {
DefaultBehavior::Func(path) => quote! {
if let #FQOption::Some(field) = #field_accessor {

View file

@ -230,7 +230,7 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
}
#[inline]
fn apply(&mut self, #ref_value: &dyn #bevy_reflect_path::Reflect) {
fn try_apply(&mut self, #ref_value: &dyn #bevy_reflect_path::Reflect) -> #FQResult<(), #bevy_reflect_path::ApplyError> {
if let #bevy_reflect_path::ReflectRef::Enum(#ref_value) = #bevy_reflect_path::Reflect::reflect_ref(#ref_value) {
if #bevy_reflect_path::Enum::variant_name(self) == #bevy_reflect_path::Enum::variant_name(#ref_value) {
// Same variant -> just update fields
@ -238,12 +238,16 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
#bevy_reflect_path::VariantType::Struct => {
for field in #bevy_reflect_path::Enum::iter_fields(#ref_value) {
let name = field.name().unwrap();
#bevy_reflect_path::Enum::field_mut(self, name).map(|v| v.apply(field.value()));
if let #FQOption::Some(v) = #bevy_reflect_path::Enum::field_mut(self, name) {
#bevy_reflect_path::Reflect::try_apply(v, field.value())?;
}
}
}
#bevy_reflect_path::VariantType::Tuple => {
for (index, field) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::Enum::iter_fields(#ref_value)) {
#bevy_reflect_path::Enum::field_at_mut(self, index).map(|v| v.apply(field.value()));
if let #FQOption::Some(v) = #bevy_reflect_path::Enum::field_at_mut(self, index) {
#bevy_reflect_path::Reflect::try_apply(v, field.value())?;
}
}
}
_ => {}
@ -254,12 +258,25 @@ pub(crate) fn impl_enum(reflect_enum: &ReflectEnum) -> proc_macro2::TokenStream
#(#variant_names => {
*self = #variant_constructors
})*
name => panic!("variant with name `{}` does not exist on enum `{}`", name, <Self as #bevy_reflect_path::TypePath>::type_path()),
name => {
return #FQResult::Err(
#bevy_reflect_path::ApplyError::UnknownVariant {
enum_name: ::core::convert::Into::into(#bevy_reflect_path::DynamicTypePath::reflect_type_path(self)),
variant_name: ::core::convert::Into::into(name),
}
);
}
}
}
} else {
panic!("`{}` is not an enum", #bevy_reflect_path::DynamicTypePath::reflect_type_path(#ref_value));
return #FQResult::Err(
#bevy_reflect_path::ApplyError::MismatchedKinds {
from_kind: #bevy_reflect_path::Reflect::reflect_kind(#ref_value),
to_kind: #bevy_reflect_path::ReflectKind::Enum,
}
);
}
#FQResult::Ok(())
}
fn reflect_kind(&self) -> #bevy_reflect_path::ReflectKind {

View file

@ -208,29 +208,37 @@ pub(crate) fn impl_struct(reflect_struct: &ReflectStruct) -> proc_macro2::TokenS
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
fn try_apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) -> #FQResult<(), #bevy_reflect_path::ApplyError> {
if let #bevy_reflect_path::ReflectRef::Struct(struct_value) = #bevy_reflect_path::Reflect::reflect_ref(value) {
for (i, value) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::Struct::iter_fields(struct_value)) {
let name = #bevy_reflect_path::Struct::name_at(struct_value, i).unwrap();
#bevy_reflect_path::Struct::field_mut(self, name).map(|v| v.apply(value));
if let #FQOption::Some(v) = #bevy_reflect_path::Struct::field_mut(self, name) {
#bevy_reflect_path::Reflect::try_apply(v, value)?;
}
}
} else {
panic!("Attempted to apply non-struct type to struct type.");
return #FQResult::Err(
#bevy_reflect_path::ApplyError::MismatchedKinds {
from_kind: #bevy_reflect_path::Reflect::reflect_kind(value),
to_kind: #bevy_reflect_path::ReflectKind::Struct
}
);
}
#FQResult::Ok(())
}
#[inline]
fn reflect_kind(&self) -> #bevy_reflect_path::ReflectKind {
#bevy_reflect_path::ReflectKind::Struct
}
#[inline]
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::Struct(self)
}
#[inline]
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::Struct(self)
}
#[inline]
fn reflect_owned(self: #FQBox<Self>) -> #bevy_reflect_path::ReflectOwned {
#bevy_reflect_path::ReflectOwned::Struct(self)
}

View file

@ -112,11 +112,11 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
_ => #FQOption::None,
}
}
#[inline]
fn field_len(&self) -> usize {
#field_count
}
#[inline]
fn iter_fields(&self) -> #bevy_reflect_path::TupleStructFieldIter {
#bevy_reflect_path::TupleStructFieldIter::new(self)
}
@ -177,28 +177,36 @@ pub(crate) fn impl_tuple_struct(reflect_struct: &ReflectStruct) -> proc_macro2::
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
fn try_apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) -> #FQResult<(), #bevy_reflect_path::ApplyError> {
if let #bevy_reflect_path::ReflectRef::TupleStruct(struct_value) = #bevy_reflect_path::Reflect::reflect_ref(value) {
for (i, value) in ::core::iter::Iterator::enumerate(#bevy_reflect_path::TupleStruct::iter_fields(struct_value)) {
#bevy_reflect_path::TupleStruct::field_mut(self, i).map(|v| v.apply(value));
if let #FQOption::Some(v) = #bevy_reflect_path::TupleStruct::field_mut(self, i) {
#bevy_reflect_path::Reflect::try_apply(v, value)?;
}
}
} else {
panic!("Attempted to apply non-TupleStruct type to TupleStruct type.");
return #FQResult::Err(
#bevy_reflect_path::ApplyError::MismatchedKinds {
from_kind: #bevy_reflect_path::Reflect::reflect_kind(value),
to_kind: #bevy_reflect_path::ReflectKind::TupleStruct,
}
);
}
#FQResult::Ok(())
}
#[inline]
fn reflect_kind(&self) -> #bevy_reflect_path::ReflectKind {
#bevy_reflect_path::ReflectKind::TupleStruct
}
#[inline]
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::TupleStruct(self)
}
#[inline]
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::TupleStruct(self)
}
#[inline]
fn reflect_owned(self: #FQBox<Self>) -> #bevy_reflect_path::ReflectOwned {
#bevy_reflect_path::ReflectOwned::TupleStruct(self)
}

View file

@ -85,14 +85,20 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
#FQBox::new(#FQClone::clone(self))
}
#[inline]
fn apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) {
let value = #bevy_reflect_path::Reflect::as_any(value);
if let #FQOption::Some(value) = <dyn #FQAny>::downcast_ref::<Self>(value) {
#[inline]
fn try_apply(&mut self, value: &dyn #bevy_reflect_path::Reflect) -> #FQResult<(), #bevy_reflect_path::ApplyError> {
let any = #bevy_reflect_path::Reflect::as_any(value);
if let #FQOption::Some(value) = <dyn #FQAny>::downcast_ref::<Self>(any) {
*self = #FQClone::clone(value);
} else {
panic!("Value is not {}.", <Self as #bevy_reflect_path::TypePath>::type_path());
return #FQResult::Err(
#bevy_reflect_path::ApplyError::MismatchedTypes {
from_type: ::core::convert::Into::into(#bevy_reflect_path::DynamicTypePath::reflect_type_path(value)),
to_type: ::core::convert::Into::into(<Self as #bevy_reflect_path::TypePath>::type_path()),
}
);
}
#FQResult::Ok(())
}
#[inline]
@ -101,18 +107,22 @@ pub(crate) fn impl_value(meta: &ReflectMeta) -> proc_macro2::TokenStream {
#FQResult::Ok(())
}
#[inline]
fn reflect_kind(&self) -> #bevy_reflect_path::ReflectKind {
#bevy_reflect_path::ReflectKind::Value
}
#[inline]
fn reflect_ref(&self) -> #bevy_reflect_path::ReflectRef {
#bevy_reflect_path::ReflectRef::Value(self)
}
#[inline]
fn reflect_mut(&mut self) -> #bevy_reflect_path::ReflectMut {
#bevy_reflect_path::ReflectMut::Value(self)
}
#[inline]
fn reflect_owned(self: #FQBox<Self>) -> #bevy_reflect_path::ReflectOwned {
#bevy_reflect_path::ReflectOwned::Value(self)
}

View file

@ -1,6 +1,6 @@
use crate::{
self as bevy_reflect, utility::reflect_hasher, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, TypeInfo, TypePath, TypePathTable,
self as bevy_reflect, utility::reflect_hasher, ApplyError, Reflect, ReflectKind, ReflectMut,
ReflectOwned, ReflectRef, TypeInfo, TypePath, TypePathTable,
};
use bevy_reflect_derive::impl_type_path;
use std::{
@ -262,6 +262,10 @@ impl Reflect for DynamicArray {
array_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
array_try_apply(self, value)
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
@ -421,6 +425,38 @@ pub fn array_apply<A: Array>(array: &mut A, reflect: &dyn Reflect) {
}
}
/// Tries to apply the reflected [array](Array) data to the given [array](Array) and
/// returns a Result.
///
/// # Errors
///
/// * Returns an [`ApplyError::DifferentSize`] if the two arrays have differing lengths.
/// * Returns an [`ApplyError::MismatchedKinds`] if the reflected value is not a
/// [valid array](ReflectRef::Array).
/// * Returns any error that is generated while applying elements to each other.
///
#[inline]
pub fn array_try_apply<A: Array>(array: &mut A, reflect: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::Array(reflect_array) = reflect.reflect_ref() {
if array.len() != reflect_array.len() {
return Err(ApplyError::DifferentSize {
from_size: reflect_array.len(),
to_size: array.len(),
});
}
for (i, value) in reflect_array.iter().enumerate() {
let v = array.get_mut(i).unwrap();
v.try_apply(value)?;
}
} else {
return Err(ApplyError::MismatchedKinds {
from_kind: reflect.reflect_kind(),
to_kind: ReflectKind::Array,
});
}
Ok(())
}
/// Compares two [arrays](Array) (one concrete and one reflected) to see if they
/// are equal.
///

View file

@ -1,9 +1,9 @@
use bevy_reflect_derive::impl_type_path;
use crate::{
self as bevy_reflect, enum_debug, enum_hash, enum_partial_eq, DynamicStruct, DynamicTuple,
Enum, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple, TypeInfo,
VariantFieldIter, VariantType,
self as bevy_reflect, enum_debug, enum_hash, enum_partial_eq, ApplyError, DynamicStruct,
DynamicTuple, Enum, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, Struct, Tuple,
TypeInfo, VariantFieldIter, VariantType,
};
use std::any::Any;
use std::fmt::Formatter;
@ -324,7 +324,7 @@ impl Reflect for DynamicEnum {
}
#[inline]
fn apply(&mut self, value: &dyn Reflect) {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::Enum(value) = value.reflect_ref() {
if Enum::variant_name(self) == value.variant_name() {
// Same variant -> just update fields
@ -333,14 +333,14 @@ impl Reflect for DynamicEnum {
for field in value.iter_fields() {
let name = field.name().unwrap();
if let Some(v) = Enum::field_mut(self, name) {
v.apply(field.value());
v.try_apply(field.value())?;
}
}
}
VariantType::Tuple => {
for (index, field) in value.iter_fields().enumerate() {
if let Some(v) = Enum::field_at_mut(self, index) {
v.apply(field.value());
v.try_apply(field.value())?;
}
}
}
@ -369,8 +369,12 @@ impl Reflect for DynamicEnum {
self.set_variant(value.variant_name(), dyn_variant);
}
} else {
panic!("`{}` is not an enum", value.reflect_type_path());
return Err(ApplyError::MismatchedKinds {
from_kind: value.reflect_kind(),
to_kind: ReflectKind::Enum,
});
}
Ok(())
}
#[inline]

View file

@ -307,8 +307,8 @@ impl<'a> VariantField<'a> {
}
pub fn value(&self) -> &'a dyn Reflect {
match self {
Self::Struct(.., value) | Self::Tuple(value) => *value,
match *self {
Self::Struct(_, value) | Self::Tuple(value) => value,
}
}
}

View file

@ -283,7 +283,9 @@ mod tests {
}
#[test]
#[should_panic(expected = "`bevy_reflect::DynamicTuple` is not an enum")]
#[should_panic(
expected = "called `Result::unwrap()` on an `Err` value: MismatchedKinds { from_kind: Tuple, to_kind: Enum }"
)]
fn applying_non_enum_should_panic() {
let mut value = MyEnum::B(0, 0);
let mut dyn_tuple = DynamicTuple::default();
@ -291,6 +293,38 @@ mod tests {
value.apply(&dyn_tuple);
}
#[test]
fn enum_try_apply_should_detect_type_mismatch() {
#[derive(Reflect, Debug, PartialEq)]
enum MyEnumAnalogue {
A(u32),
B(usize, usize),
C { foo: f32, bar: u8 },
}
let mut target = MyEnumAnalogue::A(0);
// === Tuple === //
let result = target.try_apply(&MyEnum::B(0, 1));
assert!(
matches!(result, Err(ApplyError::MismatchedTypes { .. })),
"`result` was {result:?}"
);
// === Struct === //
target = MyEnumAnalogue::C { foo: 0.0, bar: 1 };
let result = target.try_apply(&MyEnum::C {
foo: 1.0,
bar: true,
});
assert!(
matches!(result, Err(ApplyError::MismatchedTypes { .. })),
"`result` was {result:?}"
);
// Type mismatch should occur after partial application.
assert_eq!(target, MyEnumAnalogue::C { foo: 1.0, bar: 1 });
}
#[test]
fn should_skip_ignored_fields() {
#[derive(Reflect, Debug, PartialEq)]

View file

@ -5,9 +5,9 @@ use std::any::Any;
use crate::utility::GenericTypeInfoCell;
use crate::{
self as bevy_reflect, FromReflect, FromType, GetTypeRegistration, List, ListInfo, ListIter,
Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath,
TypeRegistration, Typed,
self as bevy_reflect, ApplyError, FromReflect, FromType, GetTypeRegistration, List, ListInfo,
ListIter, Reflect, ReflectFromPtr, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo,
TypePath, TypeRegistration, Typed,
};
impl<T: smallvec::Array + TypePath + Send + Sync> List for SmallVec<T>
@ -113,6 +113,10 @@ where
crate::list_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
crate::list_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())

View file

@ -1,15 +1,15 @@
use crate::std_traits::ReflectDefault;
use crate::{self as bevy_reflect, ReflectFromPtr, ReflectFromReflect, ReflectOwned, TypeRegistry};
use crate::{
impl_type_path, map_apply, map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap,
FromReflect, FromType, GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter,
Reflect, ReflectDeserialize, ReflectKind, ReflectMut, ReflectRef, ReflectSerialize, TypeInfo,
TypePath, TypeRegistration, Typed, ValueInfo,
};
use crate::utility::{
reflect_hasher, GenericTypeInfoCell, GenericTypePathCell, NonGenericTypeInfoCell,
};
use crate::{
self as bevy_reflect, impl_type_path, map_apply, map_partial_eq, map_try_apply, ApplyError,
Array, ArrayInfo, ArrayIter, DynamicMap, DynamicTypePath, FromReflect, FromType,
GetTypeRegistration, List, ListInfo, ListIter, Map, MapInfo, MapIter, Reflect,
ReflectDeserialize, ReflectFromPtr, ReflectFromReflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, ReflectSerialize, TypeInfo, TypePath, TypeRegistration, TypeRegistry, Typed,
ValueInfo,
};
use bevy_reflect_derive::{impl_reflect, impl_reflect_value};
use std::fmt;
use std::{
@ -314,6 +314,10 @@ macro_rules! impl_reflect_for_veclike {
crate::list_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
crate::list_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -540,6 +544,10 @@ macro_rules! impl_reflect_for_hashmap {
map_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
map_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -765,6 +773,10 @@ where
map_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
map_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -909,6 +921,11 @@ impl<T: Reflect + TypePath + GetTypeRegistration, const N: usize> Reflect for [T
crate::array_apply(self, value);
}
#[inline]
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
crate::array_try_apply(self, value)
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
@ -1063,13 +1080,18 @@ impl Reflect for Cow<'static, str> {
self
}
fn apply(&mut self, value: &dyn Reflect) {
let value = value.as_any();
if let Some(value) = value.downcast_ref::<Self>() {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
let any = value.as_any();
if let Some(value) = any.downcast_ref::<Self>() {
self.clone_from(value);
} else {
panic!("Value is not a {}.", Self::type_path());
return Err(ApplyError::MismatchedTypes {
from_type: value.reflect_type_path().into(),
// If we invoke the reflect_type_path on self directly the borrow checker complains that the lifetime of self must outlive 'static
to_type: Self::type_path().into(),
});
}
Ok(())
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
@ -1253,6 +1275,10 @@ impl<T: FromReflect + Clone + TypePath + GetTypeRegistration> Reflect for Cow<'s
crate::list_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
crate::list_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -1349,13 +1375,17 @@ impl Reflect for &'static str {
self
}
fn apply(&mut self, value: &dyn Reflect) {
let value = value.as_any();
if let Some(&value) = value.downcast_ref::<Self>() {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
let any = value.as_any();
if let Some(&value) = any.downcast_ref::<Self>() {
*self = value;
} else {
panic!("Value is not a {}.", Self::type_path());
return Err(ApplyError::MismatchedTypes {
from_type: value.reflect_type_path().into(),
to_type: Self::type_path().into(),
});
}
Ok(())
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
@ -1451,12 +1481,16 @@ impl Reflect for &'static Path {
self
}
fn apply(&mut self, value: &dyn Reflect) {
let value = value.as_any();
if let Some(&value) = value.downcast_ref::<Self>() {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
let any = value.as_any();
if let Some(&value) = any.downcast_ref::<Self>() {
*self = value;
Ok(())
} else {
panic!("Value is not a {}.", Self::type_path());
Err(ApplyError::MismatchedTypes {
from_type: value.reflect_type_path().into(),
to_type: <Self as DynamicTypePath>::reflect_type_path(self).into(),
})
}
}
@ -1552,12 +1586,16 @@ impl Reflect for Cow<'static, Path> {
self
}
fn apply(&mut self, value: &dyn Reflect) {
let value = value.as_any();
if let Some(value) = value.downcast_ref::<Self>() {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
let any = value.as_any();
if let Some(value) = any.downcast_ref::<Self>() {
self.clone_from(value);
Ok(())
} else {
panic!("Value is not a {}.", Self::type_path());
Err(ApplyError::MismatchedTypes {
from_type: value.reflect_type_path().into(),
to_type: <Self as DynamicTypePath>::reflect_type_path(self).into(),
})
}
}
@ -1789,14 +1827,14 @@ mod tests {
// === None on None === //
let patch = None::<Foo>;
let mut value = None;
let mut value = None::<Foo>;
Reflect::apply(&mut value, &patch);
assert_eq!(patch, value, "None apply onto None");
// === Some on None === //
let patch = Some(Foo(123));
let mut value = None;
let mut value = None::<Foo>;
Reflect::apply(&mut value, &patch);
assert_eq!(patch, value, "Some apply onto None");

View file

@ -172,7 +172,7 @@
//! ## Patching
//!
//! These dynamic types come in handy when needing to apply multiple changes to another type.
//! This is known as "patching" and is done using the [`Reflect::apply`] method.
//! This is known as "patching" and is done using the [`Reflect::apply`] and [`Reflect::try_apply`] methods.
//!
//! ```
//! # use bevy_reflect::{DynamicEnum, Reflect};
@ -613,6 +613,54 @@ mod tests {
use crate::serde::{ReflectDeserializer, ReflectSerializer};
use crate::utility::GenericTypePathCell;
#[test]
fn try_apply_should_detect_kinds() {
#[derive(Reflect, Debug)]
struct Struct {
a: u32,
b: f32,
}
#[derive(Reflect, Debug)]
enum Enum {
A,
B(u32),
}
let mut struct_target = Struct {
a: 0xDEADBEEF,
b: 3.14,
};
let mut enum_target = Enum::A;
let array_src = [8, 0, 8];
let result = struct_target.try_apply(&enum_target);
assert!(
matches!(
result,
Err(ApplyError::MismatchedKinds {
from_kind: ReflectKind::Enum,
to_kind: ReflectKind::Struct
})
),
"result was {result:?}"
);
let result = enum_target.try_apply(&array_src);
assert!(
matches!(
result,
Err(ApplyError::MismatchedKinds {
from_kind: ReflectKind::Array,
to_kind: ReflectKind::Enum
})
),
"result was {result:?}"
);
}
#[test]
fn reflect_struct() {
#[derive(Reflect)]

View file

@ -6,8 +6,8 @@ use bevy_reflect_derive::impl_type_path;
use crate::utility::reflect_hasher;
use crate::{
self as bevy_reflect, FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
TypeInfo, TypePath, TypePathTable,
self as bevy_reflect, ApplyError, FromReflect, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, TypeInfo, TypePath, TypePathTable,
};
/// A trait used to power [list-like] operations via [reflection].
@ -312,6 +312,10 @@ impl Reflect for DynamicList {
list_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
list_try_apply(self, value)
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
@ -436,19 +440,40 @@ pub fn list_hash<L: List>(list: &L) -> Option<u64> {
/// This function panics if `b` is not a list.
#[inline]
pub fn list_apply<L: List>(a: &mut L, b: &dyn Reflect) {
if let Err(err) = list_try_apply(a, b) {
panic!("{err}");
}
}
/// Tries to apply the elements of `b` to the corresponding elements of `a` and
/// returns a Result.
///
/// If the length of `b` is greater than that of `a`, the excess elements of `b`
/// are cloned and appended to `a`.
///
/// # Errors
///
/// This function returns an [`ApplyError::MismatchedKinds`] if `b` is not a list or if
/// applying elements to each other fails.
#[inline]
pub fn list_try_apply<L: List>(a: &mut L, b: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::List(list_value) = b.reflect_ref() {
for (i, value) in list_value.iter().enumerate() {
if i < a.len() {
if let Some(v) = a.get_mut(i) {
v.apply(value);
v.try_apply(value)?;
}
} else {
a.push(value.clone_value());
List::push(a, value.clone_value());
}
}
} else {
panic!("Attempted to apply a non-list type to a list type.");
return Err(ApplyError::MismatchedKinds {
from_kind: b.reflect_kind(),
to_kind: ReflectKind::List,
});
}
Ok(())
}
/// Compares a [`List`] with a [`Reflect`] value.

View file

@ -5,8 +5,8 @@ use bevy_reflect_derive::impl_type_path;
use bevy_utils::{Entry, HashMap};
use crate::{
self as bevy_reflect, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef, TypeInfo,
TypePath, TypePathTable,
self as bevy_reflect, ApplyError, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
TypeInfo, TypePath, TypePathTable,
};
/// A trait used to power [map-like] operations via [reflection].
@ -345,6 +345,10 @@ impl Reflect for DynamicMap {
map_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
map_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -502,17 +506,37 @@ pub fn map_debug(dyn_map: &dyn Map, f: &mut Formatter<'_>) -> std::fmt::Result {
/// This function panics if `b` is not a reflected map.
#[inline]
pub fn map_apply<M: Map>(a: &mut M, b: &dyn Reflect) {
if let Err(err) = map_try_apply(a, b) {
panic!("{err}");
}
}
/// Tries to apply the elements of reflected map `b` to the corresponding elements of map `a`
/// and returns a Result.
///
/// If a key from `b` does not exist in `a`, the value is cloned and inserted.
///
/// # Errors
///
/// This function returns an [`ApplyError::MismatchedKinds`] if `b` is not a reflected map or if
/// applying elements to each other fails.
#[inline]
pub fn map_try_apply<M: Map>(a: &mut M, b: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::Map(map_value) = b.reflect_ref() {
for (key, b_value) in map_value.iter() {
if let Some(a_value) = a.get_mut(key) {
a_value.apply(b_value);
a_value.try_apply(b_value)?;
} else {
a.insert_boxed(key.clone_value(), b_value.clone_value());
}
}
} else {
panic!("Attempted to apply a non-map type to a map type.");
return Err(ApplyError::MismatchedKinds {
from_kind: b.reflect_kind(),
to_kind: ReflectKind::Map,
});
}
Ok(())
}
#[cfg(test)]

View file

@ -8,6 +8,8 @@ use std::{
fmt::Debug,
};
use thiserror::Error;
use crate::utility::NonGenericTypeInfoCell;
macro_rules! impl_reflect_enum {
@ -99,6 +101,42 @@ pub enum ReflectOwned {
}
impl_reflect_enum!(ReflectOwned);
/// A enumeration of all error outcomes that might happen when running [`try_apply`](Reflect::try_apply).
#[derive(Error, Debug)]
pub enum ApplyError {
#[error("attempted to apply `{from_kind}` to `{to_kind}`")]
/// Attempted to apply the wrong [kind](ReflectKind) to a type, e.g. a struct to a enum.
MismatchedKinds {
from_kind: ReflectKind,
to_kind: ReflectKind,
},
#[error("enum variant `{variant_name}` doesn't have a field named `{field_name}`")]
/// Enum variant that we tried to apply to was missing a field.
MissingEnumField {
variant_name: Box<str>,
field_name: Box<str>,
},
#[error("`{from_type}` is not `{to_type}`")]
/// Tried to apply incompatible types.
MismatchedTypes {
from_type: Box<str>,
to_type: Box<str>,
},
#[error("attempted to apply type with {from_size} size to a type with {to_size} size")]
/// Attempted to apply to types with mismatched sizez, e.g. a [u8; 4] to [u8; 3].
DifferentSize { from_size: usize, to_size: usize },
#[error("variant with name `{variant_name}` does not exist on enum `{enum_name}`")]
/// The enum we tried to apply to didn't contain a variant with the give name.
UnknownVariant {
enum_name: Box<str>,
variant_name: Box<str>,
},
}
/// A zero-sized enumuration of the "kinds" of a reflected type.
///
/// A [`ReflectKind`] is obtained via [`Reflect::reflect_kind`],
@ -217,7 +255,20 @@ pub trait Reflect: DynamicTypePath + Any + Send + Sync {
/// - If `T` is any complex type and the corresponding fields or elements of
/// `self` and `value` are not of the same type.
/// - If `T` is a value type and `self` cannot be downcast to `T`
fn apply(&mut self, value: &dyn Reflect);
fn apply(&mut self, value: &dyn Reflect) {
Reflect::try_apply(self, value).unwrap();
}
/// Tries to [`apply`](Reflect::apply) a reflected value to this value.
///
/// Functions the same as the [`apply`](Reflect::apply) function but returns an error instead of
/// panicking.
///
/// # Handling Errors
///
/// This function may leave `self` in a partially mutated state if a error was encountered on the way.
/// consider maintaining a cloned instance of this data you can switch to if a error is encountered.
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError>;
/// Performs a type-checked assignment of a reflected value to this value.
///

View file

@ -1,6 +1,6 @@
use crate::{
self as bevy_reflect, NamedField, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
TypeInfo, TypePath, TypePathTable,
self as bevy_reflect, ApplyError, NamedField, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, TypeInfo, TypePath, TypePathTable,
};
use bevy_reflect_derive::impl_type_path;
use bevy_utils::HashMap;
@ -420,19 +420,24 @@ impl Reflect for DynamicStruct {
self
}
fn apply(&mut self, value: &dyn Reflect) {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::Struct(struct_value) = value.reflect_ref() {
for (i, value) in struct_value.iter_fields().enumerate() {
let name = struct_value.name_at(i).unwrap();
if let Some(v) = self.field_mut(name) {
v.apply(value);
v.try_apply(value)?;
}
}
} else {
panic!("Attempted to apply non-struct type to struct type.");
return Err(ApplyError::MismatchedKinds {
from_kind: value.reflect_kind(),
to_kind: ReflectKind::Struct,
});
}
Ok(())
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())

View file

@ -2,9 +2,9 @@ use bevy_reflect_derive::impl_type_path;
use bevy_utils::all_tuples;
use crate::{
self as bevy_reflect, utility::GenericTypePathCell, FromReflect, GetTypeRegistration, Reflect,
ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath, TypeRegistration, TypeRegistry,
Typed, UnnamedField,
self as bevy_reflect, utility::GenericTypePathCell, ApplyError, FromReflect,
GetTypeRegistration, Reflect, ReflectMut, ReflectOwned, ReflectRef, TypeInfo, TypePath,
TypeRegistration, TypeRegistry, Typed, UnnamedField,
};
use crate::{ReflectKind, TypePathTable};
use std::any::{Any, TypeId};
@ -369,6 +369,10 @@ impl Reflect for DynamicTuple {
Box::new(self.clone_dynamic())
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
tuple_try_apply(self, value)
}
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
tuple_partial_eq(self, value)
}
@ -394,15 +398,33 @@ impl_type_path!((in bevy_reflect) DynamicTuple);
/// This function panics if `b` is not a tuple.
#[inline]
pub fn tuple_apply<T: Tuple>(a: &mut T, b: &dyn Reflect) {
if let Err(err) = tuple_try_apply(a, b) {
panic!("{err}");
}
}
/// Tries to apply the elements of `b` to the corresponding elements of `a` and
/// returns a Result.
///
/// # Errors
///
/// This function returns an [`ApplyError::MismatchedKinds`] if `b` is not a tuple or if
/// applying elements to each other fails.
#[inline]
pub fn tuple_try_apply<T: Tuple>(a: &mut T, b: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::Tuple(tuple) = b.reflect_ref() {
for (i, value) in tuple.iter_fields().enumerate() {
if let Some(v) = a.field_mut(i) {
v.apply(value);
v.try_apply(value)?;
}
}
} else {
panic!("Attempted to apply non-Tuple type to Tuple type.");
return Err(ApplyError::MismatchedKinds {
from_kind: b.reflect_kind(),
to_kind: ReflectKind::Tuple,
});
}
Ok(())
}
/// Compares a [`Tuple`] with a [`Reflect`] value.
@ -545,6 +567,10 @@ macro_rules! impl_reflect_tuple {
crate::tuple_apply(self, value);
}
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
crate::tuple_try_apply(self, value)
}
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())

View file

@ -1,8 +1,8 @@
use bevy_reflect_derive::impl_type_path;
use crate::{
self as bevy_reflect, DynamicTuple, Reflect, ReflectKind, ReflectMut, ReflectOwned, ReflectRef,
Tuple, TypeInfo, TypePath, TypePathTable, UnnamedField,
self as bevy_reflect, ApplyError, DynamicTuple, Reflect, ReflectKind, ReflectMut, ReflectOwned,
ReflectRef, Tuple, TypeInfo, TypePath, TypePathTable, UnnamedField,
};
use std::any::{Any, TypeId};
use std::fmt::{Debug, Formatter};
@ -329,18 +329,23 @@ impl Reflect for DynamicTupleStruct {
self
}
fn apply(&mut self, value: &dyn Reflect) {
fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> {
if let ReflectRef::TupleStruct(tuple_struct) = value.reflect_ref() {
for (i, value) in tuple_struct.iter_fields().enumerate() {
if let Some(v) = self.field_mut(i) {
v.apply(value);
v.try_apply(value)?;
}
}
} else {
panic!("Attempted to apply non-TupleStruct type to TupleStruct type.");
return Err(ApplyError::MismatchedKinds {
from_kind: value.reflect_kind(),
to_kind: ReflectKind::TupleStruct,
});
}
Ok(())
}
#[inline]
fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> {
*self = value.take()?;
Ok(())
@ -371,6 +376,7 @@ impl Reflect for DynamicTupleStruct {
Box::new(self.clone_dynamic())
}
#[inline]
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
tuple_struct_partial_eq(self, value)
}

View file

@ -25,7 +25,7 @@ use std::fmt::Debug;
///
/// ```
/// # use std::any::Any;
/// # use bevy_reflect::{DynamicTypePath, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, StructInfo, TypeInfo, TypePath, ValueInfo};
/// # use bevy_reflect::{DynamicTypePath, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, StructInfo, TypeInfo, TypePath, ValueInfo, ApplyError};
/// # use bevy_reflect::utility::NonGenericTypeInfoCell;
/// use bevy_reflect::Typed;
///
@ -60,7 +60,7 @@ use std::fmt::Debug;
/// # fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> { todo!() }
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
/// # fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> { todo!() }
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }

View file

@ -147,22 +147,27 @@ pub trait DynamicTypePath {
}
impl<T: TypePath> DynamicTypePath for T {
#[inline]
fn reflect_type_path(&self) -> &str {
Self::type_path()
}
#[inline]
fn reflect_short_type_path(&self) -> &str {
Self::short_type_path()
}
#[inline]
fn reflect_type_ident(&self) -> Option<&str> {
Self::type_ident()
}
#[inline]
fn reflect_crate_name(&self) -> Option<&str> {
Self::crate_name()
}
#[inline]
fn reflect_module_path(&self) -> Option<&str> {
Self::module_path()
}

View file

@ -49,7 +49,7 @@ mod sealed {
///
/// ```
/// # use std::any::Any;
/// # use bevy_reflect::{DynamicTypePath, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, StructInfo, Typed, TypeInfo, TypePath};
/// # use bevy_reflect::{DynamicTypePath, NamedField, Reflect, ReflectMut, ReflectOwned, ReflectRef, StructInfo, Typed, TypeInfo, TypePath, ApplyError};
/// use bevy_reflect::utility::NonGenericTypeInfoCell;
///
/// struct Foo {
@ -78,7 +78,7 @@ mod sealed {
/// # fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> { todo!() }
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
/// # fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> { todo!() }
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }
@ -130,7 +130,7 @@ impl<T: TypedProperty> Default for NonGenericTypeCell<T> {
///
/// ```
/// # use std::any::Any;
/// # use bevy_reflect::{DynamicTypePath, Reflect, ReflectMut, ReflectOwned, ReflectRef, TupleStructInfo, Typed, TypeInfo, TypePath, UnnamedField};
/// # use bevy_reflect::{DynamicTypePath, Reflect, ReflectMut, ReflectOwned, ReflectRef, TupleStructInfo, Typed, TypeInfo, TypePath, UnnamedField, ApplyError};
/// use bevy_reflect::utility::GenericTypeInfoCell;
///
/// struct Foo<T>(T);
@ -157,7 +157,7 @@ impl<T: TypedProperty> Default for NonGenericTypeCell<T> {
/// # fn into_reflect(self: Box<Self>) -> Box<dyn Reflect> { todo!() }
/// # fn as_reflect(&self) -> &dyn Reflect { todo!() }
/// # fn as_reflect_mut(&mut self) -> &mut dyn Reflect { todo!() }
/// # fn apply(&mut self, value: &dyn Reflect) { todo!() }
/// # fn try_apply(&mut self, value: &dyn Reflect) -> Result<(), ApplyError> { todo!() }
/// # fn set(&mut self, value: Box<dyn Reflect>) -> Result<(), Box<dyn Reflect>> { todo!() }
/// # fn reflect_ref(&self) -> ReflectRef { todo!() }
/// # fn reflect_mut(&mut self) -> ReflectMut { todo!() }