Skip to content

Commit 2f5591f

Browse files
committed
bevy_reflect: Improve debug formatting for reflected types (#4218)
# Objective Debugging reflected types can be somewhat frustrating since all `dyn Reflect` trait objects return something like `Reflect(core::option::Option<alloc::string::String>)`. It would be much nicer to be able to see the actual value— or even use a custom `Debug` implementation. ## Solution Added `Reflect::debug` which allows users to customize the debug output. It sets defaults for all `ReflectRef` subtraits and falls back to `Reflect(type_name)` if no `Debug` implementation was registered. To register a custom `Debug` impl, users can add `#[reflect(Debug)]` like they can with other traits. ### Example Using the following structs: ```rust #[derive(Reflect)] pub struct Foo { a: usize, nested: Bar, #[reflect(ignore)] _ignored: NonReflectedValue, } #[derive(Reflect)] pub struct Bar { value: Vec2, tuple_value: (i32, String), list_value: Vec<usize>, // We can't determine debug formatting for Option<T> yet unknown_value: Option<String>, custom_debug: CustomDebug } #[derive(Reflect)] #[reflect(Debug)] struct CustomDebug; impl Debug for CustomDebug { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "This is a custom debug!") } } pub struct NonReflectedValue { _a: usize, } ``` We can do: ```rust let value = Foo { a: 1, _ignored: NonReflectedValue { _a: 10 }, nested: Bar { value: Vec2::new(1.23, 3.21), tuple_value: (123, String::from("Hello")), list_value: vec![1, 2, 3], unknown_value: Some(String::from("World")), custom_debug: CustomDebug }, }; let reflected_value: &dyn Reflect = &value; println!("{:#?}", reflected_value) ``` Which results in: ```rust Foo { a: 2, nested: Bar { value: Vec2( 1.23, 3.21, ), tuple_value: ( 123, "Hello", ), list_value: [ 1, 2, 3, ], unknown_value: Reflect(core::option::Option<alloc::string::String>), custom_debug: This is a custom debug!, }, } ``` Notice that neither `Foo` nor `Bar` implement `Debug`, yet we can still deduce it. This might be a concern if we're worried about leaking internal values. If it is, we might want to consider a way to exclude fields (possibly with a `#[reflect(hide)]` macro) or make it purely opt in (as opposed to the default implementation automatically handled by ReflectRef subtraits). Co-authored-by: Gino Valente <[email protected]>
1 parent a764d44 commit 2f5591f

File tree

12 files changed

+402
-39
lines changed

12 files changed

+402
-39
lines changed

crates/bevy_reflect/bevy_reflect_derive/src/container_attributes.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use syn::{Meta, NestedMeta, Path};
1515

1616
// The "special" trait idents that are used internally for reflection.
1717
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
18+
const DEBUG_ATTR: &str = "Debug";
1819
const PARTIAL_EQ_ATTR: &str = "PartialEq";
1920
const HASH_ATTR: &str = "Hash";
2021
const SERIALIZE_ATTR: &str = "Serialize";
@@ -46,6 +47,7 @@ impl Default for TraitImpl {
4647
/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.
4748
///
4849
/// The list of special traits are as follows:
50+
/// * `Debug`
4951
/// * `Hash`
5052
/// * `PartialEq`
5153
/// * `Serialize`
@@ -101,6 +103,7 @@ impl Default for TraitImpl {
101103
///
102104
#[derive(Default)]
103105
pub(crate) struct ReflectTraits {
106+
debug: TraitImpl,
104107
hash: TraitImpl,
105108
partial_eq: TraitImpl,
106109
serialize: TraitImpl,
@@ -123,6 +126,7 @@ impl ReflectTraits {
123126
};
124127

125128
match ident.as_str() {
129+
DEBUG_ATTR => traits.debug = TraitImpl::Implemented,
126130
PARTIAL_EQ_ATTR => traits.partial_eq = TraitImpl::Implemented,
127131
HASH_ATTR => traits.hash = TraitImpl::Implemented,
128132
SERIALIZE_ATTR => traits.serialize = TraitImpl::Implemented,
@@ -145,6 +149,7 @@ impl ReflectTraits {
145149
// This should be the ident of the custom function
146150
let trait_func_ident = TraitImpl::Custom(segment.ident.clone());
147151
match ident.as_str() {
152+
DEBUG_ATTR => traits.debug = trait_func_ident,
148153
PARTIAL_EQ_ATTR => traits.partial_eq = trait_func_ident,
149154
HASH_ATTR => traits.hash = trait_func_ident,
150155
SERIALIZE_ATTR => traits.serialize = trait_func_ident,
@@ -239,6 +244,25 @@ impl ReflectTraits {
239244
TraitImpl::NotImplemented => None,
240245
}
241246
}
247+
248+
/// Returns the implementation of `Reflect::debug` as a `TokenStream`.
249+
///
250+
/// If `Debug` was not registered, returns `None`.
251+
pub fn get_debug_impl(&self) -> Option<proc_macro2::TokenStream> {
252+
match &self.debug {
253+
TraitImpl::Implemented => Some(quote! {
254+
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
255+
std::fmt::Debug::fmt(self, f)
256+
}
257+
}),
258+
TraitImpl::Custom(impl_fn) => Some(quote! {
259+
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260+
#impl_fn(self, f)
261+
}
262+
}),
263+
TraitImpl::NotImplemented => None,
264+
}
265+
}
242266
}
243267

244268
impl Parse for ReflectTraits {

crates/bevy_reflect/bevy_reflect_derive/src/impls.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
4747
}
4848
}
4949
});
50+
let debug_fn = derive_data.traits().get_debug_impl();
5051

5152
let get_type_registration_impl = derive_data.get_type_registration();
5253
let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl();
@@ -166,6 +167,8 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
166167

167168
#partial_eq_fn
168169

170+
#debug_fn
171+
169172
#serialize_fn
170173
}
171174
})
@@ -196,6 +199,7 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
196199
}
197200
}
198201
});
202+
let debug_fn = derive_data.traits().get_debug_impl();
199203

200204
let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl();
201205
TokenStream::from(quote! {
@@ -291,6 +295,8 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
291295

292296
#partial_eq_fn
293297

298+
#debug_fn
299+
294300
#serialize_fn
295301
}
296302
})
@@ -307,6 +313,7 @@ pub(crate) fn impl_value(
307313
let hash_fn = reflect_traits.get_hash_impl(bevy_reflect_path);
308314
let serialize_fn = reflect_traits.get_serialize_impl(bevy_reflect_path);
309315
let partial_eq_fn = reflect_traits.get_partial_eq_impl(bevy_reflect_path);
316+
let debug_fn = reflect_traits.get_debug_impl();
310317

311318
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
312319
TokenStream::from(quote! {
@@ -372,6 +379,8 @@ pub(crate) fn impl_value(
372379

373380
#partial_eq_fn
374381

382+
#debug_fn
383+
375384
#serialize_fn
376385
}
377386
})

crates/bevy_reflect/src/array.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef};
22
use serde::ser::SerializeSeq;
3+
use std::fmt::Debug;
34
use std::{
45
any::Any,
56
hash::{Hash, Hasher},
@@ -298,3 +299,29 @@ pub fn array_partial_eq<A: Array>(array: &A, reflect: &dyn Reflect) -> Option<bo
298299

299300
Some(true)
300301
}
302+
303+
/// The default debug formatter for [`Array`] types.
304+
///
305+
/// # Example
306+
/// ```
307+
/// use bevy_reflect::Reflect;
308+
///
309+
/// let my_array: &dyn Reflect = &[1, 2, 3];
310+
/// println!("{:#?}", my_array);
311+
///
312+
/// // Output:
313+
///
314+
/// // [
315+
/// // 1,
316+
/// // 2,
317+
/// // 3,
318+
/// // ]
319+
/// ```
320+
#[inline]
321+
pub fn array_debug(dyn_array: &dyn Array, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322+
let mut debug = f.debug_list();
323+
for item in dyn_array.iter() {
324+
debug.entry(&item as &dyn Debug);
325+
}
326+
debug.finish()
327+
}

crates/bevy_reflect/src/impls/glam.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@ use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_ref
66
use glam::*;
77

88
impl_reflect_struct!(
9-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
9+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
1010
struct IVec2 {
1111
x: i32,
1212
y: i32,
1313
}
1414
);
1515
impl_reflect_struct!(
16-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
16+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
1717
struct IVec3 {
1818
x: i32,
1919
y: i32,
2020
z: i32,
2121
}
2222
);
2323
impl_reflect_struct!(
24-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
24+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
2525
struct IVec4 {
2626
x: i32,
2727
y: i32,
@@ -31,22 +31,22 @@ impl_reflect_struct!(
3131
);
3232

3333
impl_reflect_struct!(
34-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
34+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
3535
struct UVec2 {
3636
x: u32,
3737
y: u32,
3838
}
3939
);
4040
impl_reflect_struct!(
41-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
41+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
4242
struct UVec3 {
4343
x: u32,
4444
y: u32,
4545
z: u32,
4646
}
4747
);
4848
impl_reflect_struct!(
49-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
49+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
5050
struct UVec4 {
5151
x: u32,
5252
y: u32,
@@ -56,30 +56,30 @@ impl_reflect_struct!(
5656
);
5757

5858
impl_reflect_struct!(
59-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
59+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
6060
struct Vec2 {
6161
x: f32,
6262
y: f32,
6363
}
6464
);
6565
impl_reflect_struct!(
66-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
66+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
6767
struct Vec3 {
6868
x: f32,
6969
y: f32,
7070
z: f32,
7171
}
7272
);
7373
impl_reflect_struct!(
74-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
74+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
7575
struct Vec3A {
7676
x: f32,
7777
y: f32,
7878
z: f32,
7979
}
8080
);
8181
impl_reflect_struct!(
82-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
82+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
8383
struct Vec4 {
8484
x: f32,
8585
y: f32,
@@ -89,22 +89,22 @@ impl_reflect_struct!(
8989
);
9090

9191
impl_reflect_struct!(
92-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
92+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
9393
struct DVec2 {
9494
x: f64,
9595
y: f64,
9696
}
9797
);
9898
impl_reflect_struct!(
99-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
99+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
100100
struct DVec3 {
101101
x: f64,
102102
y: f64,
103103
z: f64,
104104
}
105105
);
106106
impl_reflect_struct!(
107-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
107+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
108108
struct DVec4 {
109109
x: f64,
110110
y: f64,
@@ -114,15 +114,15 @@ impl_reflect_struct!(
114114
);
115115

116116
impl_reflect_struct!(
117-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
117+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
118118
struct Mat3 {
119119
x_axis: Vec3,
120120
y_axis: Vec3,
121121
z_axis: Vec3,
122122
}
123123
);
124124
impl_reflect_struct!(
125-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
125+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
126126
struct Mat4 {
127127
x_axis: Vec4,
128128
y_axis: Vec4,
@@ -132,15 +132,15 @@ impl_reflect_struct!(
132132
);
133133

134134
impl_reflect_struct!(
135-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
135+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
136136
struct DMat3 {
137137
x_axis: DVec3,
138138
y_axis: DVec3,
139139
z_axis: DVec3,
140140
}
141141
);
142142
impl_reflect_struct!(
143-
#[reflect(PartialEq, Serialize, Deserialize, Default)]
143+
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
144144
struct DMat4 {
145145
x_axis: DVec4,
146146
y_axis: DVec4,
@@ -153,8 +153,8 @@ impl_reflect_struct!(
153153
// mechanisms for read-only fields. I doubt those mechanisms would be added,
154154
// so for now quaternions will remain as values. They are represented identically
155155
// to Vec4 and DVec4, so you may use those instead and convert between.
156-
impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize, Default));
157-
impl_reflect_value!(DQuat(PartialEq, Serialize, Deserialize, Default));
156+
impl_reflect_value!(Quat(Debug, PartialEq, Serialize, Deserialize, Default));
157+
impl_reflect_value!(DQuat(Debug, PartialEq, Serialize, Deserialize, Default));
158158

159159
impl_from_reflect_value!(Quat);
160160
impl_from_reflect_value!(DQuat);

crates/bevy_reflect/src/impls/std.rs

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,27 +15,27 @@ use std::{
1515
ops::Range,
1616
};
1717

18-
impl_reflect_value!(bool(Hash, PartialEq, Serialize, Deserialize));
19-
impl_reflect_value!(char(Hash, PartialEq, Serialize, Deserialize));
20-
impl_reflect_value!(u8(Hash, PartialEq, Serialize, Deserialize));
21-
impl_reflect_value!(u16(Hash, PartialEq, Serialize, Deserialize));
22-
impl_reflect_value!(u32(Hash, PartialEq, Serialize, Deserialize));
23-
impl_reflect_value!(u64(Hash, PartialEq, Serialize, Deserialize));
24-
impl_reflect_value!(u128(Hash, PartialEq, Serialize, Deserialize));
25-
impl_reflect_value!(usize(Hash, PartialEq, Serialize, Deserialize));
26-
impl_reflect_value!(i8(Hash, PartialEq, Serialize, Deserialize));
27-
impl_reflect_value!(i16(Hash, PartialEq, Serialize, Deserialize));
28-
impl_reflect_value!(i32(Hash, PartialEq, Serialize, Deserialize));
29-
impl_reflect_value!(i64(Hash, PartialEq, Serialize, Deserialize));
30-
impl_reflect_value!(i128(Hash, PartialEq, Serialize, Deserialize));
31-
impl_reflect_value!(isize(Hash, PartialEq, Serialize, Deserialize));
32-
impl_reflect_value!(f32(PartialEq, Serialize, Deserialize));
33-
impl_reflect_value!(f64(PartialEq, Serialize, Deserialize));
34-
impl_reflect_value!(String(Hash, PartialEq, Serialize, Deserialize));
18+
impl_reflect_value!(bool(Debug, Hash, PartialEq, Serialize, Deserialize));
19+
impl_reflect_value!(char(Debug, Hash, PartialEq, Serialize, Deserialize));
20+
impl_reflect_value!(u8(Debug, Hash, PartialEq, Serialize, Deserialize));
21+
impl_reflect_value!(u16(Debug, Hash, PartialEq, Serialize, Deserialize));
22+
impl_reflect_value!(u32(Debug, Hash, PartialEq, Serialize, Deserialize));
23+
impl_reflect_value!(u64(Debug, Hash, PartialEq, Serialize, Deserialize));
24+
impl_reflect_value!(u128(Debug, Hash, PartialEq, Serialize, Deserialize));
25+
impl_reflect_value!(usize(Debug, Hash, PartialEq, Serialize, Deserialize));
26+
impl_reflect_value!(i8(Debug, Hash, PartialEq, Serialize, Deserialize));
27+
impl_reflect_value!(i16(Debug, Hash, PartialEq, Serialize, Deserialize));
28+
impl_reflect_value!(i32(Debug, Hash, PartialEq, Serialize, Deserialize));
29+
impl_reflect_value!(i64(Debug, Hash, PartialEq, Serialize, Deserialize));
30+
impl_reflect_value!(i128(Debug, Hash, PartialEq, Serialize, Deserialize));
31+
impl_reflect_value!(isize(Debug, Hash, PartialEq, Serialize, Deserialize));
32+
impl_reflect_value!(f32(Debug, PartialEq, Serialize, Deserialize));
33+
impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize));
34+
impl_reflect_value!(String(Debug, Hash, PartialEq, Serialize, Deserialize));
3535
impl_reflect_value!(Option<T: Serialize + Clone + for<'de> Deserialize<'de> + Reflect + 'static>(Serialize, Deserialize));
3636
impl_reflect_value!(HashSet<T: Serialize + Hash + Eq + Clone + for<'de> Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize));
3737
impl_reflect_value!(Range<T: Serialize + Clone + for<'de> Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize));
38-
impl_reflect_value!(Duration(Hash, PartialEq, Serialize, Deserialize));
38+
impl_reflect_value!(Duration(Debug, Hash, PartialEq, Serialize, Deserialize));
3939

4040
impl_from_reflect_value!(bool);
4141
impl_from_reflect_value!(char);

0 commit comments

Comments
 (0)