Skip to content

Commit eaa37f3

Browse files
MrGVSValice-i-cecileaecsocket
authored
bevy_reflect: Add DeserializeWithRegistry and SerializeWithRegistry (#8611)
# Objective ### The Problem Currently, the reflection deserializers give little control to users for how a type is deserialized. The most control a user can have is to register `ReflectDeserialize`, which will use a type's `Deserialize` implementation. However, there are times when a type may require slightly more control. For example, let's say we want to make Bevy's `Mesh` easier to deserialize via reflection (assume `Mesh` actually implemented `Reflect`). Since we want this to be extensible, we'll make it so users can use their own types so long as they satisfy `Into<Mesh>`. The end result should allow users to define a RON file like: ```rust { "my_game::meshes::Sphere": ( radius: 2.5 ) } ``` ### The Current Solution Since we don't know the types ahead of time, we'll need to use reflection. Luckily, we can access type information dynamically via the type registry. Let's make a custom type data struct that users can register on their types: ```rust pub struct ReflectIntoMesh { // ... } impl<T: FromReflect + Into<Mesh>> FromType<T> for ReflectIntoMesh { fn from_type() -> Self { // ... } } ``` Now we'll need a way to use this type data during deserialization. Unfortunately, we can't use `Deserialize` since we need access to the registry. This is where `DeserializeSeed` comes in handy: ```rust pub struct MeshDeserializer<'a> { pub registry: &'a TypeRegistry } impl<'a, 'de> DeserializeSeed<'de> for MeshDeserializer<'a> { type Value = Mesh; fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error> where D: serde::Deserializer<'de>, { struct MeshVisitor<'a> { registry: &'a TypeRegistry } impl<'a, 'de> Visitor<'de> for MeshVisitor<'a> { fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { write!(formatter, "map containing mesh information") } fn visit_map<A>(self, mut map: A) -> Result<Self::Value, serde::de::Error> where A: MapAccess<'de> { // Parse the type name let type_name = map.next_key::<String>()?.unwrap(); // Deserialize the value based on the type name let registration = self.registry .get_with_name(&type_name) .expect("should be registered"); let value = map.next_value_seed(TypedReflectDeserializer { registration, registry: self.registry, })?; // Convert the deserialized value into a `Mesh` let into_mesh = registration.data::<ReflectIntoMesh>().unwrap(); Ok(into_mesh.into(value)) } } } } ``` ### The Problem with the Current Solution The solution above works great when all we need to do is deserialize `Mesh` directly. But now, we want to be able to deserialize a struct like this: ```rust struct Fireball { damage: f32, mesh: Mesh, } ``` This might look simple enough and should theoretically be no problem for the reflection deserializer to handle, but this is where our `MeshDeserializer` solution starts to break down. In order to use `MeshDeserializer`, we need to have access to the registry. The reflection deserializers have access to that, but we have no way of borrowing it for our own deserialization since they have no way of knowing about `MeshDeserializer`. This means we need to implement _another_ `DeserializeSeed`— this time for `Fireball`! And if we decided to put `Fireball` inside another type, well now we need one for that type as well. As you can see, this solution does not scale well and results in a lot of unnecessary boilerplate for the user. ## Solution > [!note] > This PR originally only included the addition of `DeserializeWithRegistry`. Since then, a corresponding `SerializeWithRegistry` trait has also been added. The reasoning and usage is pretty much the same as the former so I didn't bother to update the full PR description. Created the `DeserializeWithRegistry` trait and `ReflectDeserializeWithRegistry` type data. The `DeserializeWithRegistry` trait works like a standard `Deserialize` but provides access to the registry. And by registering the `ReflectDeserializeWithRegistry` type data, the reflection deserializers will automatically use the `DeserializeWithRegistry` implementation, just like it does for `Deserialize`. All we need to do is make the following changes: ```diff #[derive(Reflect)] + #[reflect(DeserializeWithRegistry)] struct Mesh { // ... } - impl<'a, 'de> DeserializeSeed<'de> for MeshDeserializer<'a> { - type Value = Mesh; - fn deserialize<D>(self, deserializer: D) -> Result<Self::Value, D::Error> + impl<'de> DeserializeWithRegistry<'de> for Mesh { + fn deserialize<D>(deserializer: D, registry: &TypeRegistry) -> Result<Self, D::Error> where D: serde::Deserializer<'de>, { // ... } } ``` Now, any time the reflection deserializer comes across `Mesh`, it will opt to use its `DeserializeWithRegistry` implementation. And this means we no longer need to create a whole slew of `DeserializeSeed` types just to deserialize `Mesh`. ### Why not a trait like `DeserializeSeed`? While this would allow for anyone to define a deserializer for `Mesh`, the problem is that it means __anyone can define a deserializer for `Mesh`.__ This has the unfortunate consequence that users can never be certain that their registration of `ReflectDeserializeSeed` is the one that will actually be used. We could consider adding something like that in the future, but I think this PR's solution is much safer and follows the example set by `ReflectDeserialize`. ### What if we made the `TypeRegistry` globally available? This is one potential solution and has been discussed before (#6101). However, that change is much more controversial and comes with its own set of disadvantages (can't have multiple registries such as with multiple worlds, likely some added performance cost with each access, etc.). ### Followup Work Once this PR is merged, we should consider merging `ReflectDeserialize` into `DeserializeWithRegistry`. ~~There is already a blanket implementation to make this transition generally pretty straightforward.~~ The blanket implementations were removed for the sake of this PR and will need to be re-added in the followup. I would propose that we first mark `ReflectDeserialize` as deprecated, though, before we outright remove it in a future release. --- ## Changelog - Added the `DeserializeReflect` trait and `ReflectDeserializeReflect` type data - Added the `SerializeReflect` trait and `ReflectSerializeReflect` type data - Added `TypedReflectDeserializer::of` convenience constructor --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: aecsocket <[email protected]>
1 parent f86ee32 commit eaa37f3

File tree

10 files changed

+509
-70
lines changed

10 files changed

+509
-70
lines changed

crates/bevy_reflect/src/impls/std.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ impl_reflect_opaque!(::core::num::NonZeroI8(
201201
));
202202
impl_reflect_opaque!(::core::num::Wrapping<T: Clone + Send + Sync>());
203203
impl_reflect_opaque!(::core::num::Saturating<T: Clone + Send + Sync>());
204-
impl_reflect_opaque!(::alloc::sync::Arc<T: Send + Sync>);
204+
impl_reflect_opaque!(::alloc::sync::Arc<T: Send + Sync + ?Sized>);
205205

206206
// `Serialize` and `Deserialize` only for platforms supported by serde:
207207
// https://github.com/serde-rs/serde/blob/3ffb86fc70efd3d329519e2dddfa306cc04f167c/serde/src/de/impls.rs#L1732
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
use crate::serde::de::error_utils::make_custom_error;
2+
use crate::{FromType, PartialReflect, TypeRegistry};
3+
use serde::Deserializer;
4+
5+
/// Trait used to provide finer control when deserializing a reflected type with one of
6+
/// the reflection deserializers.
7+
///
8+
/// This trait is the reflection equivalent of `serde`'s [`Deserialize`] trait.
9+
/// The main difference is that this trait provides access to the [`TypeRegistry`],
10+
/// which means that we can use the registry and all its stored type information
11+
/// to deserialize our type.
12+
///
13+
/// This can be useful when writing a custom reflection deserializer where we may
14+
/// want to handle parts of the deserialization process, but temporarily pass control
15+
/// to the standard reflection deserializer for other parts.
16+
///
17+
/// For the serialization equivalent of this trait, see [`SerializeWithRegistry`].
18+
///
19+
/// # Rationale
20+
///
21+
/// Without this trait and its associated [type data], such a deserializer would have to
22+
/// write out all of the deserialization logic itself, possibly including
23+
/// unnecessary code duplication and trivial implementations.
24+
///
25+
/// This is because a normal [`Deserialize`] implementation has no knowledge of the
26+
/// [`TypeRegistry`] and therefore cannot create a reflection-based deserializer for
27+
/// nested items.
28+
///
29+
/// # Implementors
30+
///
31+
/// In order for this to work with the reflection deserializers like [`TypedReflectDeserializer`]
32+
/// and [`ReflectDeserializer`], implementors should be sure to register the
33+
/// [`ReflectDeserializeWithRegistry`] type data.
34+
/// This can be done [via the registry] or by adding `#[reflect(DeserializeWithRegistry)]` to
35+
/// the type definition.
36+
///
37+
/// [`Deserialize`]: ::serde::Deserialize
38+
/// [`SerializeWithRegistry`]: crate::serde::SerializeWithRegistry
39+
/// [type data]: ReflectDeserializeWithRegistry
40+
/// [`TypedReflectDeserializer`]: crate::serde::TypedReflectDeserializer
41+
/// [`ReflectDeserializer`]: crate::serde::ReflectDeserializer
42+
/// [via the registry]: TypeRegistry::register_type_data
43+
pub trait DeserializeWithRegistry<'de>: PartialReflect + Sized {
44+
fn deserialize<D>(deserializer: D, registry: &TypeRegistry) -> Result<Self, D::Error>
45+
where
46+
D: Deserializer<'de>;
47+
}
48+
49+
/// Type data used to deserialize a [`PartialReflect`] type with a custom [`DeserializeWithRegistry`] implementation.
50+
#[derive(Clone)]
51+
pub struct ReflectDeserializeWithRegistry {
52+
deserialize: fn(
53+
deserializer: &mut dyn erased_serde::Deserializer,
54+
registry: &TypeRegistry,
55+
) -> Result<Box<dyn PartialReflect>, erased_serde::Error>,
56+
}
57+
58+
impl ReflectDeserializeWithRegistry {
59+
/// Deserialize a [`PartialReflect`] type with this type data's custom [`DeserializeWithRegistry`] implementation.
60+
pub fn deserialize<'de, D>(
61+
&self,
62+
deserializer: D,
63+
registry: &TypeRegistry,
64+
) -> Result<Box<dyn PartialReflect>, D::Error>
65+
where
66+
D: Deserializer<'de>,
67+
{
68+
let mut erased = <dyn erased_serde::Deserializer>::erase(deserializer);
69+
(self.deserialize)(&mut erased, registry).map_err(make_custom_error)
70+
}
71+
}
72+
73+
impl<T: PartialReflect + for<'de> DeserializeWithRegistry<'de>> FromType<T>
74+
for ReflectDeserializeWithRegistry
75+
{
76+
fn from_type() -> Self {
77+
Self {
78+
deserialize: |deserializer, registry| {
79+
Ok(Box::new(T::deserialize(deserializer, registry)?))
80+
},
81+
}
82+
}
83+
}

crates/bevy_reflect/src/serde/de/deserializer.rs

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#[cfg(feature = "debug_stack")]
22
use crate::serde::de::error_utils::TYPE_INFO_STACK;
3+
use crate::serde::ReflectDeserializeWithRegistry;
34
use crate::{
45
serde::{
56
de::{
@@ -9,7 +10,7 @@ use crate::{
910
},
1011
TypeRegistrationDeserializer,
1112
},
12-
PartialReflect, ReflectDeserialize, TypeInfo, TypeRegistration, TypeRegistry,
13+
PartialReflect, ReflectDeserialize, TypeInfo, TypePath, TypeRegistration, TypeRegistry,
1314
};
1415
use core::{fmt, fmt::Formatter};
1516
use serde::de::{DeserializeSeed, Error, IgnoredAny, MapAccess, Visitor};
@@ -231,6 +232,7 @@ pub struct TypedReflectDeserializer<'a> {
231232
}
232233

233234
impl<'a> TypedReflectDeserializer<'a> {
235+
/// Creates a new [`TypedReflectDeserializer`] for the given type registration.
234236
pub fn new(registration: &'a TypeRegistration, registry: &'a TypeRegistry) -> Self {
235237
#[cfg(feature = "debug_stack")]
236238
TYPE_INFO_STACK.set(crate::type_info_stack::TypeInfoStack::new());
@@ -241,6 +243,22 @@ impl<'a> TypedReflectDeserializer<'a> {
241243
}
242244
}
243245

246+
/// Creates a new [`TypedReflectDeserializer`] for the given type `T`.
247+
///
248+
/// # Panics
249+
///
250+
/// Panics if `T` is not registered in the given [`TypeRegistry`].
251+
pub fn of<T: TypePath>(registry: &'a TypeRegistry) -> Self {
252+
let registration = registry
253+
.get(core::any::TypeId::of::<T>())
254+
.unwrap_or_else(|| panic!("no registration found for type `{}`", T::type_path()));
255+
256+
Self {
257+
registration,
258+
registry,
259+
}
260+
}
261+
244262
/// An internal constructor for creating a deserializer without resetting the type info stack.
245263
pub(super) fn new_internal(
246264
registration: &'a TypeRegistration,
@@ -269,6 +287,13 @@ impl<'a, 'de> DeserializeSeed<'de> for TypedReflectDeserializer<'a> {
269287
return Ok(value.into_partial_reflect());
270288
}
271289

290+
if let Some(deserialize_reflect) =
291+
self.registration.data::<ReflectDeserializeWithRegistry>()
292+
{
293+
let value = deserialize_reflect.deserialize(deserializer, self.registry)?;
294+
return Ok(value);
295+
}
296+
272297
match self.registration.type_info() {
273298
TypeInfo::Struct(struct_info) => {
274299
let mut dynamic_struct = deserializer.deserialize_struct(

crates/bevy_reflect/src/serde/de/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
pub use deserialize_with_registry::*;
12
pub use deserializer::*;
23
pub use registrations::*;
34

45
mod arrays;
6+
mod deserialize_with_registry;
57
mod deserializer;
68
mod enums;
79
mod error_utils;

0 commit comments

Comments
 (0)