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 <49806985+MrGVSV@users.noreply.github.com>
This commit is contained in:
Gino Valente 2022-05-30 16:41:31 +00:00
parent a764d44f17
commit 2f5591ff8c
12 changed files with 402 additions and 39 deletions

View file

@ -15,6 +15,7 @@ use syn::{Meta, NestedMeta, Path};
// The "special" trait idents that are used internally for reflection.
// Received via attributes like `#[reflect(PartialEq, Hash, ...)]`
const DEBUG_ATTR: &str = "Debug";
const PARTIAL_EQ_ATTR: &str = "PartialEq";
const HASH_ATTR: &str = "Hash";
const SERIALIZE_ATTR: &str = "Serialize";
@ -46,6 +47,7 @@ impl Default for TraitImpl {
/// `Reflect` derive macro using the helper attribute: `#[reflect(...)]`.
///
/// The list of special traits are as follows:
/// * `Debug`
/// * `Hash`
/// * `PartialEq`
/// * `Serialize`
@ -101,6 +103,7 @@ impl Default for TraitImpl {
///
#[derive(Default)]
pub(crate) struct ReflectTraits {
debug: TraitImpl,
hash: TraitImpl,
partial_eq: TraitImpl,
serialize: TraitImpl,
@ -123,6 +126,7 @@ impl ReflectTraits {
};
match ident.as_str() {
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,
@ -145,6 +149,7 @@ impl ReflectTraits {
// This should be the ident of the custom function
let trait_func_ident = TraitImpl::Custom(segment.ident.clone());
match ident.as_str() {
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,
@ -239,6 +244,25 @@ impl ReflectTraits {
TraitImpl::NotImplemented => None,
}
}
/// Returns the implementation of `Reflect::debug` as a `TokenStream`.
///
/// If `Debug` was not registered, returns `None`.
pub fn get_debug_impl(&self) -> Option<proc_macro2::TokenStream> {
match &self.debug {
TraitImpl::Implemented => Some(quote! {
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
std::fmt::Debug::fmt(self, f)
}
}),
TraitImpl::Custom(impl_fn) => Some(quote! {
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
#impl_fn(self, f)
}
}),
TraitImpl::NotImplemented => None,
}
}
}
impl Parse for ReflectTraits {

View file

@ -47,6 +47,7 @@ pub(crate) fn impl_struct(derive_data: &ReflectDeriveData) -> TokenStream {
}
}
});
let debug_fn = derive_data.traits().get_debug_impl();
let get_type_registration_impl = derive_data.get_type_registration();
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 {
#partial_eq_fn
#debug_fn
#serialize_fn
}
})
@ -196,6 +199,7 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
}
}
});
let debug_fn = derive_data.traits().get_debug_impl();
let (impl_generics, ty_generics, where_clause) = derive_data.generics().split_for_impl();
TokenStream::from(quote! {
@ -291,6 +295,8 @@ pub(crate) fn impl_tuple_struct(derive_data: &ReflectDeriveData) -> TokenStream
#partial_eq_fn
#debug_fn
#serialize_fn
}
})
@ -307,6 +313,7 @@ pub(crate) fn impl_value(
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();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
TokenStream::from(quote! {
@ -372,6 +379,8 @@ pub(crate) fn impl_value(
#partial_eq_fn
#debug_fn
#serialize_fn
}
})

View file

@ -1,5 +1,6 @@
use crate::{serde::Serializable, Reflect, ReflectMut, ReflectRef};
use serde::ser::SerializeSeq;
use std::fmt::Debug;
use std::{
any::Any,
hash::{Hash, Hasher},
@ -298,3 +299,29 @@ pub fn array_partial_eq<A: Array>(array: &A, reflect: &dyn Reflect) -> Option<bo
Some(true)
}
/// The default debug formatter for [`Array`] types.
///
/// # Example
/// ```
/// use bevy_reflect::Reflect;
///
/// let my_array: &dyn Reflect = &[1, 2, 3];
/// println!("{:#?}", my_array);
///
/// // Output:
///
/// // [
/// // 1,
/// // 2,
/// // 3,
/// // ]
/// ```
#[inline]
pub fn array_debug(dyn_array: &dyn Array, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_list();
for item in dyn_array.iter() {
debug.entry(&item as &dyn Debug);
}
debug.finish()
}

View file

@ -6,14 +6,14 @@ use bevy_reflect_derive::{impl_from_reflect_value, impl_reflect_struct, impl_ref
use glam::*;
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct IVec2 {
x: i32,
y: i32,
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct IVec3 {
x: i32,
y: i32,
@ -21,7 +21,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct IVec4 {
x: i32,
y: i32,
@ -31,14 +31,14 @@ impl_reflect_struct!(
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct UVec2 {
x: u32,
y: u32,
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct UVec3 {
x: u32,
y: u32,
@ -46,7 +46,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct UVec4 {
x: u32,
y: u32,
@ -56,14 +56,14 @@ impl_reflect_struct!(
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Vec2 {
x: f32,
y: f32,
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Vec3 {
x: f32,
y: f32,
@ -71,7 +71,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Vec3A {
x: f32,
y: f32,
@ -79,7 +79,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Vec4 {
x: f32,
y: f32,
@ -89,14 +89,14 @@ impl_reflect_struct!(
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct DVec2 {
x: f64,
y: f64,
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct DVec3 {
x: f64,
y: f64,
@ -104,7 +104,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct DVec4 {
x: f64,
y: f64,
@ -114,7 +114,7 @@ impl_reflect_struct!(
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Mat3 {
x_axis: Vec3,
y_axis: Vec3,
@ -122,7 +122,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct Mat4 {
x_axis: Vec4,
y_axis: Vec4,
@ -132,7 +132,7 @@ impl_reflect_struct!(
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct DMat3 {
x_axis: DVec3,
y_axis: DVec3,
@ -140,7 +140,7 @@ impl_reflect_struct!(
}
);
impl_reflect_struct!(
#[reflect(PartialEq, Serialize, Deserialize, Default)]
#[reflect(Debug, PartialEq, Serialize, Deserialize, Default)]
struct DMat4 {
x_axis: DVec4,
y_axis: DVec4,
@ -153,8 +153,8 @@ impl_reflect_struct!(
// mechanisms for read-only fields. I doubt those mechanisms would be added,
// so for now quaternions will remain as values. They are represented identically
// to Vec4 and DVec4, so you may use those instead and convert between.
impl_reflect_value!(Quat(PartialEq, Serialize, Deserialize, Default));
impl_reflect_value!(DQuat(PartialEq, Serialize, Deserialize, Default));
impl_reflect_value!(Quat(Debug, PartialEq, Serialize, Deserialize, Default));
impl_reflect_value!(DQuat(Debug, PartialEq, Serialize, Deserialize, Default));
impl_from_reflect_value!(Quat);
impl_from_reflect_value!(DQuat);

View file

@ -15,27 +15,27 @@ use std::{
ops::Range,
};
impl_reflect_value!(bool(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(char(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u8(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u16(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u32(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u64(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u128(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(usize(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i8(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i16(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i32(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i64(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i128(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(isize(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(f32(PartialEq, Serialize, Deserialize));
impl_reflect_value!(f64(PartialEq, Serialize, Deserialize));
impl_reflect_value!(String(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(bool(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(char(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u8(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u16(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u32(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u64(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(u128(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(usize(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i8(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i16(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i32(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i64(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(i128(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(isize(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(f32(Debug, PartialEq, Serialize, Deserialize));
impl_reflect_value!(f64(Debug, PartialEq, Serialize, Deserialize));
impl_reflect_value!(String(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(Option<T: Serialize + Clone + for<'de> Deserialize<'de> + Reflect + 'static>(Serialize, Deserialize));
impl_reflect_value!(HashSet<T: Serialize + Hash + Eq + Clone + for<'de> Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize));
impl_reflect_value!(Range<T: Serialize + Clone + for<'de> Deserialize<'de> + Send + Sync + 'static>(Serialize, Deserialize));
impl_reflect_value!(Duration(Hash, PartialEq, Serialize, Deserialize));
impl_reflect_value!(Duration(Debug, Hash, PartialEq, Serialize, Deserialize));
impl_from_reflect_value!(bool);
impl_from_reflect_value!(char);

View file

@ -92,6 +92,7 @@ mod tests {
ser::{to_string_pretty, PrettyConfig},
Deserializer,
};
use std::fmt::{Debug, Formatter};
use super::*;
use crate as bevy_reflect;
@ -489,6 +490,87 @@ mod tests {
let _ = trait_object.as_reflect();
}
#[test]
fn should_reflect_debug() {
#[derive(Reflect)]
struct Test {
value: usize,
list: Vec<String>,
array: [f32; 3],
map: HashMap<i32, f32>,
a_struct: SomeStruct,
a_tuple_struct: SomeTupleStruct,
custom: CustomDebug,
unknown: Option<String>,
#[reflect(ignore)]
#[allow(dead_code)]
ignored: isize,
}
#[derive(Reflect)]
struct SomeStruct {
foo: String,
}
#[derive(Reflect)]
struct SomeTupleStruct(String);
#[derive(Reflect)]
#[reflect(Debug)]
struct CustomDebug;
impl Debug for CustomDebug {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("Cool debug!")
}
}
let mut map = HashMap::new();
map.insert(123, 1.23);
let test = Test {
value: 123,
list: vec![String::from("A"), String::from("B"), String::from("C")],
array: [1.0, 2.0, 3.0],
map,
a_struct: SomeStruct {
foo: String::from("A Struct!"),
},
a_tuple_struct: SomeTupleStruct(String::from("A Tuple Struct!")),
custom: CustomDebug,
unknown: Some(String::from("Enums aren't supported yet :(")),
ignored: 321,
};
let reflected: &dyn Reflect = &test;
let expected = r#"
bevy_reflect::tests::should_reflect_debug::Test {
value: 123,
list: [
"A",
"B",
"C",
],
array: [
1.0,
2.0,
3.0,
],
map: {
123: 1.23,
},
a_struct: bevy_reflect::tests::should_reflect_debug::SomeStruct {
foo: "A Struct!",
},
a_tuple_struct: bevy_reflect::tests::should_reflect_debug::SomeTupleStruct(
"A Tuple Struct!",
),
custom: Cool debug!,
unknown: Reflect(core::option::Option<alloc::string::String>),
}"#;
assert_eq!(expected, format!("\n{:#?}", reflected));
}
#[cfg(feature = "glam")]
mod glam {
use super::*;

View file

@ -1,4 +1,5 @@
use std::any::Any;
use std::fmt::{Debug, Formatter};
use crate::{serde::Serializable, Array, ArrayIter, DynamicArray, Reflect, ReflectMut, ReflectRef};
@ -167,6 +168,18 @@ unsafe impl Reflect for DynamicList {
fn serializable(&self) -> Option<Serializable> {
Some(Serializable::Borrowed(self))
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicList(")?;
list_debug(self, f)?;
write!(f, ")")
}
}
impl Debug for DynamicList {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.debug(f)
}
}
impl serde::Serialize for DynamicList {
@ -239,6 +252,32 @@ pub fn list_partial_eq<L: List>(a: &L, b: &dyn Reflect) -> Option<bool> {
Some(true)
}
/// The default debug formatter for [`List`] types.
///
/// # Example
/// ```
/// use bevy_reflect::Reflect;
///
/// let my_list: &dyn Reflect = &vec![1, 2, 3];
/// println!("{:#?}", my_list);
///
/// // Output:
///
/// // [
/// // 1,
/// // 2,
/// // 3,
/// // ]
/// ```
#[inline]
pub fn list_debug(dyn_list: &dyn List, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_list();
for item in dyn_list.iter() {
debug.entry(&item as &dyn Debug);
}
debug.finish()
}
#[cfg(test)]
mod tests {
use super::DynamicList;

View file

@ -1,4 +1,5 @@
use std::any::Any;
use std::fmt::{Debug, Formatter};
use bevy_utils::{Entry, HashMap};
@ -189,6 +190,18 @@ unsafe impl Reflect for DynamicMap {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
map_partial_eq(self, value)
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicMap(")?;
map_debug(self, f)?;
write!(f, ")")
}
}
impl Debug for DynamicMap {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.debug(f)
}
}
/// An iterator over the key-value pairs of a [`Map`].
@ -255,6 +268,32 @@ pub fn map_partial_eq<M: Map>(a: &M, b: &dyn Reflect) -> Option<bool> {
Some(true)
}
/// The default debug formatter for [`Map`] types.
///
/// # Example
/// ```
/// # use bevy_utils::HashMap;
/// use bevy_reflect::Reflect;
///
/// let mut my_map = HashMap::new();
/// my_map.insert(123, String::from("Hello"));
/// println!("{:#?}", &my_map as &dyn Reflect);
///
/// // Output:
///
/// // {
/// // 123: "Hello",
/// // }
/// ```
#[inline]
pub fn map_debug(dyn_map: &dyn Map, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_map();
for (key, value) in dyn_map.iter() {
debug.entry(&key as &dyn Debug, &value as &dyn Debug);
}
debug.finish()
}
#[cfg(test)]
mod tests {
use super::DynamicMap;

View file

@ -1,4 +1,8 @@
use crate::{serde::Serializable, Array, List, Map, Struct, Tuple, TupleStruct};
use crate::{
array_debug, list_debug, map_debug, serde::Serializable, struct_debug, tuple_debug,
tuple_struct_debug, Array, List, Map, Struct, Tuple, TupleStruct,
};
use std::{any::Any, fmt::Debug};
pub use bevy_utils::AHasher as ReflectHasher;
@ -141,6 +145,25 @@ pub unsafe trait Reflect: Any + Send + Sync {
None
}
/// Debug formatter for the value.
///
/// Any value that is not an implementor of other `Reflect` subtraits
/// (e.g. [`List`], [`Map`]), will default to the format: `"Reflect(type_name)"`,
/// where `type_name` is the [type name] of the underlying type.
///
/// [type name]: Self::type_name
fn debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.reflect_ref() {
ReflectRef::Struct(dyn_struct) => struct_debug(dyn_struct, f),
ReflectRef::TupleStruct(dyn_tuple_struct) => tuple_struct_debug(dyn_tuple_struct, f),
ReflectRef::Tuple(dyn_tuple) => tuple_debug(dyn_tuple, f),
ReflectRef::List(dyn_list) => list_debug(dyn_list, f),
ReflectRef::Array(dyn_array) => array_debug(dyn_array, f),
ReflectRef::Map(dyn_map) => map_debug(dyn_map, f),
_ => write!(f, "Reflect({})", self.type_name()),
}
}
/// Returns a serializable version of the value.
///
/// If the underlying type does not support serialization, returns `None`.
@ -166,7 +189,7 @@ pub trait FromReflect: Reflect + Sized {
impl Debug for dyn Reflect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Reflect({})", self.type_name())
self.debug(f)
}
}

View file

@ -1,5 +1,6 @@
use crate::{Reflect, ReflectMut, ReflectRef};
use bevy_utils::{Entry, HashMap};
use std::fmt::{Debug, Formatter};
use std::{any::Any, borrow::Cow};
/// A reflected Rust regular struct type.
@ -315,6 +316,18 @@ unsafe impl Reflect for DynamicStruct {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
struct_partial_eq(self, value)
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicStruct(")?;
struct_debug(self, f)?;
write!(f, ")")
}
}
impl Debug for DynamicStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.debug(f)
}
}
/// Compares a [`Struct`] with a [`Reflect`] value.
@ -349,3 +362,35 @@ pub fn struct_partial_eq<S: Struct>(a: &S, b: &dyn Reflect) -> Option<bool> {
Some(true)
}
/// The default debug formatter for [`Struct`] types.
///
/// # Example
/// ```
/// use bevy_reflect::Reflect;
/// #[derive(Reflect)]
/// struct MyStruct {
/// foo: usize
/// }
///
/// let my_struct: &dyn Reflect = &MyStruct { foo: 123 };
/// println!("{:#?}", my_struct);
///
/// // Output:
///
/// // MyStruct {
/// // foo: 123,
/// // }
/// ```
#[inline]
pub fn struct_debug(dyn_struct: &dyn Struct, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_struct(dyn_struct.type_name());
for field_index in 0..dyn_struct.field_len() {
let field = dyn_struct.field_at(field_index).unwrap();
debug.field(
dyn_struct.name_at(field_index).unwrap(),
&field as &dyn Debug,
);
}
debug.finish()
}

View file

@ -4,6 +4,7 @@ use crate::{
};
use serde::Deserialize;
use std::any::Any;
use std::fmt::{Debug, Formatter};
/// A reflected Rust tuple.
///
@ -262,6 +263,12 @@ unsafe impl Reflect for DynamicTuple {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
tuple_partial_eq(self, value)
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicTuple(")?;
tuple_debug(self, f)?;
write!(f, ")")
}
}
/// Applies the elements of `b` to the corresponding elements of `a`.
@ -310,6 +317,32 @@ pub fn tuple_partial_eq<T: Tuple>(a: &T, b: &dyn Reflect) -> Option<bool> {
Some(true)
}
/// The default debug formatter for [`Tuple`] types.
///
/// # Example
/// ```
/// use bevy_reflect::Reflect;
///
/// let my_tuple: &dyn Reflect = &(1, 2, 3);
/// println!("{:#?}", my_tuple);
///
/// // Output:
///
/// // (
/// // 1,
/// // 2,
/// // 3,
/// // )
/// ```
#[inline]
pub fn tuple_debug(dyn_tuple: &dyn Tuple, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut debug = f.debug_tuple("");
for field in dyn_tuple.iter_fields() {
debug.field(&field as &dyn Debug);
}
debug.finish()
}
macro_rules! impl_reflect_tuple {
{$($index:tt : $name:tt),*} => {
impl<$($name: Reflect),*> Tuple for ($($name,)*) {

View file

@ -1,5 +1,6 @@
use crate::{Reflect, ReflectMut, ReflectRef};
use std::any::Any;
use std::fmt::{Debug, Formatter};
/// A reflected Rust tuple struct.
///
@ -254,6 +255,18 @@ unsafe impl Reflect for DynamicTupleStruct {
fn reflect_partial_eq(&self, value: &dyn Reflect) -> Option<bool> {
tuple_struct_partial_eq(self, value)
}
fn debug(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "DynamicTupleStruct(")?;
tuple_struct_debug(self, f)?;
write!(f, ")")
}
}
impl Debug for DynamicTupleStruct {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
self.debug(f)
}
}
/// Compares a [`TupleStruct`] with a [`Reflect`] value.
@ -286,3 +299,32 @@ pub fn tuple_struct_partial_eq<S: TupleStruct>(a: &S, b: &dyn Reflect) -> Option
Some(true)
}
/// The default debug formatter for [`TupleStruct`] types.
///
/// # Example
/// ```
/// use bevy_reflect::Reflect;
/// #[derive(Reflect)]
/// struct MyTupleStruct(usize);
///
/// let my_tuple_struct: &dyn Reflect = &MyTupleStruct(123);
/// println!("{:#?}", my_tuple_struct);
///
/// // Output:
///
/// // MyTupleStruct (
/// // 123,
/// // )
/// ```
#[inline]
pub fn tuple_struct_debug(
dyn_tuple_struct: &dyn TupleStruct,
f: &mut std::fmt::Formatter<'_>,
) -> std::fmt::Result {
let mut debug = f.debug_tuple(dyn_tuple_struct.type_name());
for field in dyn_tuple_struct.iter_fields() {
debug.field(&field as &dyn Debug);
}
debug.finish()
}