rg3d_core/
inspect.rs

1//! Lightweight read-only runtime reflection.
2//!
3//! See [`Inspect`] for more info.
4
5#![warn(missing_docs)]
6
7use std::{
8    any::{Any, TypeId},
9    fmt::{self, Debug},
10};
11
12/// A value of a property.
13pub trait PropertyValue: Any + Debug {
14    /// Casts `self` to a `&dyn Any`
15    fn as_any(&self) -> &dyn Any;
16}
17
18impl<T: Debug + 'static> PropertyValue for T {
19    fn as_any(&self) -> &dyn Any {
20        self
21    }
22}
23
24/// An error that can occur during "type casting"
25#[derive(Debug)]
26pub enum CastError {
27    /// Given type does not match expected.
28    TypeMismatch {
29        /// A name of the property.
30        property_name: String,
31
32        /// Expected type identifier.
33        expected_type_id: TypeId,
34
35        /// Actual type identifier.
36        actual_type_id: TypeId,
37    },
38}
39
40/// Information about a property of an object.
41pub struct PropertyInfo<'a> {
42    /// A type id of the owner of the property.
43    pub owner_type_id: TypeId,
44
45    /// A name of the property.
46    pub name: &'a str,
47
48    /// A human-readable name of the property.
49    pub display_name: &'static str,
50
51    /// An reference to the actual value of the property.
52    pub value: &'a dyn PropertyValue,
53
54    /// A property is not meant to be edited.
55    pub read_only: bool,
56
57    /// A minimal value of the property. Works only with numeric properties!
58    pub min_value: Option<f64>,
59
60    /// A minimal value of the property. Works only with numeric properties!
61    pub max_value: Option<f64>,
62
63    /// A minimal value of the property. Works only with numeric properties!
64    pub step: Option<f64>,
65
66    /// Maximum amount of decimal places for a numeric property.
67    pub precision: Option<usize>,
68
69    /// Description of the property.
70    pub description: String,
71}
72
73impl<'a> PartialEq<Self> for PropertyInfo<'a> {
74    fn eq(&self, other: &Self) -> bool {
75        let value_ptr_a = &*self.value as *const _ as *const ();
76        let value_ptr_b = &*other.value as *const _ as *const ();
77
78        self.owner_type_id == other.owner_type_id
79            && self.name == other.name
80            && self.display_name == other.display_name
81            && std::ptr::eq(value_ptr_a, value_ptr_b)
82            && self.read_only == other.read_only
83            && self.min_value == other.min_value
84            && self.max_value == other.max_value
85            && self.step == other.step
86            && self.precision == other.precision
87            && self.description == other.description
88    }
89}
90
91impl<'a> fmt::Debug for PropertyInfo<'a> {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        f.debug_struct("PropertyInfo")
94            .field("owner_type_id", &self.owner_type_id)
95            .field("name", &self.name)
96            .field("display_name", &self.display_name)
97            .field("value", &format_args!("{:?}", self.value as *const _))
98            .field("read_only", &self.read_only)
99            .field("min_value", &self.min_value)
100            .field("max_value", &self.max_value)
101            .field("step", &self.step)
102            .field("precision", &self.precision)
103            .field("description", &self.description)
104            .finish()
105    }
106}
107
108impl<'a> PropertyInfo<'a> {
109    /// Tries to cast a value to a given type.
110    pub fn cast_value<T: 'static>(&self) -> Result<&T, CastError> {
111        match self.value.as_any().downcast_ref::<T>() {
112            Some(value) => Ok(value),
113            None => Err(CastError::TypeMismatch {
114                property_name: self.name.to_string(),
115                expected_type_id: TypeId::of::<T>(),
116                actual_type_id: self.value.type_id(),
117            }),
118        }
119    }
120}
121
122/// A trait that allows you to "look inside" an object that implements it. It is used for lightweight
123/// runtime read-only reflection. The most common use case for it is various editors.
124///
125/// It is not advised to manually implement this trait. You should use `#[derive(Inspect)]` whenever
126/// possible.
127///
128/// ## `#[derive(Inspect)]`
129///
130/// The proc macro reduces amount of boilerplate code to the minimum and significantly reduces a
131/// change of error.
132///
133/// ### Supported attributes
134///
135/// - `#[inspect(name = "new_field_name")]` - override field name.
136/// - `#[inspect(display_name = "Human-readable Name")]` - override display name.
137/// - `#[inspect(group = "Group Name")]` - override group name.
138/// - `#[inspect(expand)]` - extends the list of properties in case of composition, in other words it
139/// "flattens" and exposes the properties of an inner object. Useful when you have a structure that
140/// has some fields that are complex objects that implements `Inspect` too.
141pub trait Inspect {
142    /// Returns information about "public" properties.
143    fn properties(&self) -> Vec<PropertyInfo<'_>>;
144}
145
146impl<T: Inspect> Inspect for Option<T> {
147    fn properties(&self) -> Vec<PropertyInfo<'_>> {
148        match self {
149            Some(v) => v.properties(),
150            None => vec![],
151        }
152    }
153}
154
155impl<T: Inspect> Inspect for Box<T> {
156    fn properties(&self) -> Vec<PropertyInfo<'_>> {
157        (**self).properties()
158    }
159}
160
161macro_rules! impl_self_inspect {
162    ($ty:ty, $min:expr, $max:expr, $step:expr, $precision:expr) => {
163        impl Inspect for $ty {
164            fn properties(&self) -> Vec<PropertyInfo<'_>> {
165                vec![PropertyInfo {
166                    owner_type_id: TypeId::of::<Self>(),
167                    name: "Value",
168                    display_name: "Value",
169                    value: self,
170                    read_only: false,
171                    min_value: Some($min),
172                    max_value: Some($max),
173                    step: Some($step),
174                    precision: Some($precision),
175                    description: "".to_string(),
176                }]
177            }
178        }
179    };
180}
181
182impl_self_inspect!(f32, f32::MIN as f64, f32::MAX as f64, 1.0, 7);
183impl_self_inspect!(f64, f64::MIN, f64::MAX, 1.0, 15);
184impl_self_inspect!(i64, i64::MIN as f64, i64::MAX as f64, 1.0, 0);
185impl_self_inspect!(u64, u64::MIN as f64, u64::MAX as f64, 1.0, 0);
186impl_self_inspect!(i32, i32::MIN as f64, i32::MAX as f64, 1.0, 0);
187impl_self_inspect!(u32, u32::MIN as f64, u32::MAX as f64, 1.0, 0);
188impl_self_inspect!(i16, i16::MIN as f64, i16::MAX as f64, 1.0, 0);
189impl_self_inspect!(u16, u16::MIN as f64, u16::MAX as f64, 1.0, 0);
190impl_self_inspect!(i8, i8::MIN as f64, i8::MAX as f64, 1.0, 0);
191impl_self_inspect!(u8, u8::MIN as f64, u8::MAX as f64, 1.0, 0);
192
193pub use rg3d_core_derive::Inspect;