bevy_reflect: put serialize into external ReflectSerialize type (#4782)

builds on top of #4780 

# Objective

`Reflect` and `Serialize` are currently very tied together because `Reflect` has a `fn serialize(&self) -> Option<Serializable<'_>>` method. Because of that, we can either implement `Reflect` for types like `Option<T>` with `T: Serialize` and have `fn serialize` be implemented, or without the bound but having `fn serialize` return `None`.

By separating `ReflectSerialize` into a separate type (like how it already is for `ReflectDeserialize`, `ReflectDefault`), we could separately `.register::<Option<T>>()` and `.register_data::<Option<T>, ReflectSerialize>()` only if the type `T: Serialize`.

This PR does not change the registration but allows it to be changed in a future PR.

## Solution

- add the type
```rust
struct ReflectSerialize { .. }
impl<T: Reflect + Serialize> FromType<T> for ReflectSerialize { .. }
```

- remove `#[reflect(Serialize)]` special casing. 

- when serializing reflect value types, look for `ReflectSerialize` in the `TypeRegistry` instead of calling `value.serialize()`
This commit is contained in:
Jakob Hellermann 2022-06-20 17:18:58 +00:00
parent bb1d524833
commit 218b0fd3b6
18 changed files with 77 additions and 85 deletions

View file

@ -10,7 +10,7 @@ use crate::{
Asset, Assets,
};
use bevy_ecs::{component::Component, reflect::ReflectComponent};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_utils::Uuid;
use crossbeam_channel::{Receiver, Sender};
use serde::{Deserialize, Serialize};

View file

@ -1,4 +1,4 @@
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_utils::AHasher;
use serde::{Deserialize, Serialize};
use std::{

View file

@ -1,6 +1,6 @@
use bevy_derive::{Deref, DerefMut};
use bevy_ecs::prelude::*;
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{color::Color, extract_resource::ExtractResource};
use serde::{Deserialize, Serialize};

View file

@ -1,6 +1,6 @@
use crate::clear_color::ClearColorConfig;
use bevy_ecs::{prelude::*, query::QueryItem};
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::{
camera::{Camera, CameraRenderGraph, Projection},
extract_component::ExtractComponent,

View file

@ -8,6 +8,7 @@ use crate::{
};
use bevy_reflect::{
impl_from_reflect_value, impl_reflect_value, FromType, Reflect, ReflectDeserialize,
ReflectSerialize,
};
#[derive(Clone)]

View file

@ -18,7 +18,6 @@ use syn::{Meta, NestedMeta, Path};
const DEBUG_ATTR: &str = "Debug";
const PARTIAL_EQ_ATTR: &str = "PartialEq";
const HASH_ATTR: &str = "Hash";
const SERIALIZE_ATTR: &str = "Serialize";
// The traits listed below are not considered "special" (i.e. they use the `ReflectMyTrait` syntax)
// but useful to know exist nonetheless
@ -54,7 +53,6 @@ impl Default for TraitImpl {
/// * `Debug`
/// * `Hash`
/// * `PartialEq`
/// * `Serialize`
///
/// When registering a trait, there are a few things to keep in mind:
/// * Traits must have a valid `Reflect{}` struct in scope. For example, `Default`
@ -110,7 +108,6 @@ pub(crate) struct ReflectTraits {
debug: TraitImpl,
hash: TraitImpl,
partial_eq: TraitImpl,
serialize: TraitImpl,
idents: Vec<Ident>,
}
@ -133,7 +130,6 @@ impl ReflectTraits {
DEBUG_ATTR => traits.debug = TraitImpl::Implemented,
PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented,
HASH_ATTR => traits.hash = TraitImpl::Implemented,
SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented,
// We only track reflected idents for traits not considered special
_ => traits.idents.push(utility::get_reflect_ident(&ident)),
}
@ -156,7 +152,6 @@ impl ReflectTraits {
DEBUG_ATTR => traits.debug = trait_func_ident,
PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident,
HASH_ATTR => traits.hash = trait_func_ident,
SERIALIZE_ATTR => traits.serialize = trait_func_ident,
_ => {}
}
}
@ -230,25 +225,6 @@ impl ReflectTraits {
}
}
/// Returns the implementation of `Reflect::serializable` as a `TokenStream`.
///
/// If `Serialize` was not registered, returns `None`.
pub fn get_serialize_impl(&self, bevy_reflect_path: &Path) -> Option<proc_macro2::TokenStream> {
match &self.serialize {
TraitImpl::Implemented => Some(quote! {
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
Some(#bevy_reflect_path::serde::Serializable::Borrowed(self))
}
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
fn serializable(&self) -> Option<#bevy_reflect_path::serde::Serializable> {
Some(#impl_fn(self))
}
}),
TraitImpl::NotImplemented => None,
}
}
/// Returns the implementation of `Reflect::debug` as a `TokenStream`.
///
/// If `Debug` was not registered, returns `None`.

View file

@ -40,7 +40,6 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path);
let serialize_fn = derive_data.traits().get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = derive_data
.traits()
.get_partial_eq_impl(bevy_reflect_path)
@ -192,8 +191,6 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
#partial_eq_fn
#debug_fn
#serialize_fn
}
})
}
@ -216,7 +213,6 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
let field_indices = (0..field_count).collect::<Vec<usize>>();
let hash_fn = derive_data.traits().get_hash_impl(bevy_reflect_path);
let serialize_fn = derive_data.traits().get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = derive_data
.traits()
.get_partial_eq_impl(bevy_reflect_path)
@ -344,8 +340,6 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
#partial_eq_fn
#debug_fn
#serialize_fn
}
})
}
@ -359,7 +353,6 @@ pub(crate) fn impl_value(
reflect_traits: &ReflectTraits,
) -> TokenStream {
let hash_fn = reflect_traits.get_hash_impl(bevy_reflect_path);
let serialize_fn = reflect_traits.get_serialize_impl(bevy_reflect_path);
let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path);
let debug_fn = reflect_traits.get_debug_impl();
@ -445,8 +438,6 @@ pub(crate) fn impl_value(
#partial_eq_fn
#debug_fn
#serialize_fn
}
})
}

View file

@ -1,6 +1,5 @@
use crate::{
serde::Serializable, utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut,
ReflectRef, TypeInfo, Typed,
utility::NonGenericTypeInfoCell, DynamicInfo, Reflect, ReflectMut, ReflectRef, TypeInfo, Typed,
};
use std::{
any::{Any, TypeId},
@ -217,10 +216,6 @@ unsafe impl Reflect for DynamicArray {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
array_partial_eq(self, value)
}
fn serializable(&self) -> Option<Serializable> {
None
}
}
impl Array for DynamicArray {

View file

@ -1,7 +1,7 @@
use crate as bevy_reflect;
use crate::prelude::ReflectDefault;
use crate::reflect::Reflect;
use crate::ReflectDeserialize;
use crate::{ReflectDeserialize, ReflectSerialize};
use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_reflect_value};
use glam::*;

View file

@ -1,8 +1,8 @@
use crate as bevy_reflect;
use crate::{
map_partial_eq, serde::Serializable, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect,
FromType, GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect,
ReflectDeserialize, ReflectMut, ReflectRef, TypeInfo, TypeRegistration, Typed, ValueInfo,
map_partial_eq, Array, ArrayInfo, ArrayIter, DynamicMap, FromReflect, FromType,
GetTypeRegistration, List, ListInfo, Map, MapInfo, MapIter, Reflect, ReflectDeserialize,
ReflectMut, ReflectRef, ReflectSerialize, TypeInfo, TypeRegistration, Typed, ValueInfo,
};
use crate::utility::{GenericTypeInfoCell, NonGenericTypeInfoCell};
@ -159,10 +159,6 @@ unsafe impl<T: FromReflect> Reflect for Vec<T> {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
crate::list_partial_eq(self, value)
}
fn serializable(&self) -> Option<Serializable> {
None
}
}
impl<T: FromReflect> Typed for Vec<T> {
@ -420,11 +416,6 @@ unsafe impl<T: Reflect, const N: usize> Reflect for [T; N] {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
crate::array_partial_eq(self, value)
}
#[inline]
fn serializable(&self) -> Option<Serializable> {
None
}
}
impl<T: FromReflect, const N: usize> FromReflect for [T; N] {
@ -541,10 +532,6 @@ unsafe impl Reflect for Cow<'static, str> {
Some(false)
}
}
fn serializable(&self) -> Option<Serializable> {
Some(Serializable::Borrowed(self))
}
}
impl Typed for Cow<'static, str> {
@ -558,6 +545,7 @@ impl GetTypeRegistration for Cow<'static, str> {
fn get_type_registration() -> TypeRegistration {
let mut registration = TypeRegistration::of::<Cow<'static, str>>();
registration.insert::<ReflectDeserialize>(FromType::<Cow<'static, str>>::from_type());
registration.insert::<ReflectSerialize>(FromType::<Cow<'static, str>>::from_type());
registration
}
}
@ -570,13 +558,19 @@ impl FromReflect for Cow<'static, str> {
#[cfg(test)]
mod tests {
use crate::Reflect;
use crate::{Reflect, ReflectSerialize, TypeRegistry};
use bevy_utils::HashMap;
use std::f32::consts::{PI, TAU};
#[test]
fn can_serialize_duration() {
assert!(std::time::Duration::ZERO.serializable().is_some());
let mut type_registry = TypeRegistry::default();
type_registry.register::<std::time::Duration>();
let reflect_serialize = type_registry
.get_type_data::<ReflectSerialize>(std::any::TypeId::of::<std::time::Duration>())
.unwrap();
let _serializable = reflect_serialize.get_serializable(&std::time::Duration::ZERO);
}
#[test]

View file

@ -34,8 +34,8 @@ pub mod prelude {
pub use crate::std_traits::*;
#[doc(hidden)]
pub use crate::{
reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize, Struct,
TupleStruct,
reflect_trait, GetField, GetTupleStructField, Reflect, ReflectDeserialize,
ReflectSerialize, Struct, TupleStruct,
};
}
@ -877,7 +877,8 @@ bevy_reflect::tests::should_reflect_debug::Test {
let v = vec3(12.0, 3.0, -6.9);
let mut registry = TypeRegistry::default();
registry.add_registration(Vec3::get_type_registration());
registry.register::<f32>();
registry.register::<Vec3>();
let ser = ReflectSerializer::new(&v, &registry);

View file

@ -3,8 +3,8 @@ use std::fmt::{Debug, Formatter};
use crate::utility::NonGenericTypeInfoCell;
use crate::{
serde::Serializable, Array, ArrayIter, DynamicArray, DynamicInfo, FromReflect, Reflect,
ReflectMut, ReflectRef, TypeInfo, Typed,
Array, ArrayIter, DynamicArray, DynamicInfo, FromReflect, Reflect, ReflectMut, ReflectRef,
TypeInfo, Typed,
};
/// An ordered, mutable list of [Reflect] items. This corresponds to types like [`std::vec::Vec`].
@ -229,10 +229,6 @@ unsafe impl Reflect for DynamicList {
list_partial_eq(self, value)
}
fn serializable(&self) -> Option<Serializable> {
None
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicList(")?;
list_debug(self, f)?;

View file

@ -1,6 +1,6 @@
use crate::{
serde::type_fields, Array, List, Map, Reflect, ReflectRef, Struct, Tuple, TupleStruct,
TypeRegistry,
serde::type_fields, Array, List, Map, Reflect, ReflectRef, ReflectSerialize, Struct, Tuple,
TupleStruct, TypeRegistry,
};
use serde::{
ser::{SerializeMap, SerializeSeq},
@ -22,13 +22,19 @@ impl<'a> Serializable<'a> {
}
}
fn get_serializable<E: serde::ser::Error>(reflect_value: &dyn Reflect) -> Result<Serializable, E> {
reflect_value.serializable().ok_or_else(|| {
serde::ser::Error::custom(format_args!(
"Type '{}' does not support ReflectValue serialization",
reflect_value.type_name()
))
})
fn get_serializable<'a, E: serde::ser::Error>(
reflect_value: &'a dyn Reflect,
type_registry: &TypeRegistry,
) -> Result<Serializable<'a>, E> {
let reflect_serialize = type_registry
.get_type_data::<ReflectSerialize>(reflect_value.type_id())
.ok_or_else(|| {
serde::ser::Error::custom(format_args!(
"Type '{}' did not register ReflectSerialize",
reflect_value.type_name()
))
})?;
Ok(reflect_serialize.get_serializable(reflect_value))
}
pub struct ReflectSerializer<'a> {
@ -101,7 +107,7 @@ impl<'a> Serialize for ReflectValueSerializer<'a> {
state.serialize_entry(type_fields::TYPE, self.value.type_name())?;
state.serialize_entry(
type_fields::VALUE,
get_serializable::<S::Error>(self.value)?.borrow(),
get_serializable::<S::Error>(self.value, self.registry)?.borrow(),
)?;
state.end()
}

View file

@ -1,3 +1,4 @@
use crate::serde::Serializable;
use crate::{Reflect, TypeInfo, Typed};
use bevy_utils::{HashMap, HashSet};
use downcast_rs::{impl_downcast, Downcast};
@ -389,6 +390,35 @@ pub trait FromType<T> {
fn from_type() -> Self;
}
/// A struct used to serialize reflected instances of a type.
///
/// A `ReflectSerialize` for type `T` can be obtained via
/// [`FromType::from_type`].
#[derive(Clone)]
pub struct ReflectSerialize {
get_serializable: for<'a> fn(value: &'a dyn Reflect) -> Serializable,
}
impl<T: Reflect + erased_serde::Serialize> FromType<T> for ReflectSerialize {
fn from_type() -> Self {
ReflectSerialize {
get_serializable: |value| {
let value = value.downcast_ref::<T>().unwrap_or_else(|| {
panic!("ReflectSerialize::get_serialize called with type `{}`, even though it was created for `{}`", value.type_name(), std::any::type_name::<T>())
});
Serializable::Borrowed(value)
},
}
}
}
impl ReflectSerialize {
/// Turn the value into a serializable representation
pub fn get_serializable<'a>(&self, value: &'a dyn Reflect) -> Serializable<'a> {
(self.get_serializable)(value)
}
}
/// A struct used to deserialize reflected instances of a type.
///
/// A `ReflectDeserialize` for type `T` can be obtained via

View file

@ -4,7 +4,9 @@ use super::DepthCalculation;
use bevy_app::{App, CoreStage, Plugin, StartupStage};
use bevy_ecs::{prelude::*, reflect::ReflectComponent};
use bevy_math::Mat4;
use bevy_reflect::{std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize};
use bevy_reflect::{
std_traits::ReflectDefault, GetTypeRegistration, Reflect, ReflectDeserialize, ReflectSerialize,
};
use bevy_window::ModifiesWindows;
use serde::{Deserialize, Serialize};

View file

@ -4,7 +4,7 @@ pub use colorspace::*;
use crate::color::{HslRepresentation, SrgbColorSpace};
use bevy_math::{Vec3, Vec4};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize};
use bevy_reflect::{FromReflect, Reflect, ReflectDeserialize, ReflectSerialize};
use serde::{Deserialize, Serialize};
use std::ops::{Add, AddAssign, Mul, MulAssign};
use thiserror::Error;

View file

@ -7,7 +7,7 @@ use bevy_ecs::{
};
use bevy_input::{mouse::MouseButton, touch::Touches, Input};
use bevy_math::Vec2;
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_transform::components::GlobalTransform;
use bevy_utils::FloatOrd;
use bevy_window::Windows;

View file

@ -7,7 +7,7 @@ use bevy_ecs::{
reflect::ReflectComponent,
system::{Query, Res},
};
use bevy_reflect::{Reflect, ReflectDeserialize};
use bevy_reflect::{Reflect, ReflectDeserialize, ReflectSerialize};
use bevy_render::texture::Image;
use serde::{Deserialize, Serialize};