mirror of
https://github.com/bevyengine/bevy
synced 2024-11-26 06:30:19 +00:00
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:
parent
a764d44f17
commit
2f5591ff8c
12 changed files with 402 additions and 39 deletions
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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::*;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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,)*) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue