Skip to main content

bevy_reflect/
attributes.rs

1//! Types and functions for creating, manipulating and querying [`CustomAttributes`].
2
3use crate::Reflect;
4use alloc::boxed::Box;
5use bevy_platform::sync::Arc;
6use bevy_utils::TypeIdIndexMap;
7use core::{
8    any::TypeId,
9    fmt::{Debug, Formatter},
10};
11
12/// A collection of custom attributes for a type, field, or variant.
13///
14/// These attributes can be created with the [`Reflect` derive macro], or with
15/// [`CustomAttributesBuilder`].
16///
17/// Attributes are stored by their [`TypeId`].
18/// Because of this, there can only be one attribute per type.
19///
20/// # Example
21///
22/// ```
23/// # use bevy_reflect::{Reflect, Typed, TypeInfo};
24/// use core::ops::RangeInclusive;
25/// #[derive(Reflect)]
26/// struct Slider {
27///   #[reflect(@RangeInclusive::<f32>::new(0.0, 1.0))]
28///   value: f32
29/// }
30///
31/// let TypeInfo::Struct(info) = <Slider as Typed>::type_info() else {
32///   panic!("expected struct info");
33/// };
34///
35/// let range = info.field("value").unwrap().get_attribute::<RangeInclusive<f32>>().unwrap();
36/// assert_eq!(0.0..=1.0, *range);
37/// ```
38///
39/// [`Reflect` derive macro]: derive@crate::Reflect
40#[derive(Default, Clone)]
41pub struct CustomAttributes {
42    attributes: Option<Arc<TypeIdIndexMap<CustomAttribute>>>,
43}
44
45impl CustomAttributes {
46    fn new(attributes: TypeIdIndexMap<CustomAttribute>) -> Self {
47        Self {
48            attributes: if attributes.is_empty() {
49                None
50            } else {
51                Some(Arc::new(attributes))
52            },
53        }
54    }
55
56    /// Returns `true` if this collection contains a custom attribute of the specified type.
57    pub fn contains<T: Reflect>(&self) -> bool {
58        self.attributes
59            .as_ref()
60            .is_some_and(|a| a.contains_key(&TypeId::of::<T>()))
61    }
62
63    /// Returns `true` if this collection contains a custom attribute with the specified [`TypeId`].
64    pub fn contains_by_id(&self, id: TypeId) -> bool {
65        self.attributes
66            .as_ref()
67            .is_some_and(|a| a.contains_key(&id))
68    }
69
70    /// Gets a custom attribute by type.
71    pub fn get<T: Reflect>(&self) -> Option<&T> {
72        self.attributes
73            .as_ref()
74            .and_then(|a| a.get(&TypeId::of::<T>())?.value::<T>())
75    }
76
77    /// Gets a custom attribute by its [`TypeId`].
78    pub fn get_by_id(&self, id: TypeId) -> Option<&dyn Reflect> {
79        self.attributes
80            .as_ref()
81            .and_then(|a| a.get(&id))
82            .map(CustomAttribute::reflect_value)
83    }
84
85    /// Returns an iterator over all custom attributes.
86    pub fn iter(&self) -> impl Iterator<Item = (&TypeId, &dyn Reflect)> {
87        self.attributes.iter().flat_map(|attributes| {
88            attributes
89                .iter()
90                .map(|(key, value)| (key, value.reflect_value()))
91        })
92    }
93
94    /// Returns the number of custom attributes in this collection.
95    pub fn len(&self) -> usize {
96        self.attributes.as_ref().map(|a| a.len()).unwrap_or(0)
97    }
98
99    /// Returns `true` if this collection is empty.
100    pub fn is_empty(&self) -> bool {
101        self.attributes.is_none()
102    }
103}
104
105impl Debug for CustomAttributes {
106    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
107        if let Some(attributes) = &self.attributes {
108            f.debug_set().entries(attributes.values()).finish()
109        } else {
110            f.debug_set().finish()
111        }
112    }
113}
114
115struct CustomAttribute {
116    value: Box<dyn Reflect>,
117}
118
119impl CustomAttribute {
120    /// Creates a new [`CustomAttribute`] containing `value`.
121    pub fn new<T: Reflect>(value: T) -> Self {
122        Self {
123            value: Box::new(value),
124        }
125    }
126
127    /// Returns a reference to the attribute's value if it is of type `T`, or [`None`] if not.
128    pub fn value<T: Reflect>(&self) -> Option<&T> {
129        self.value.downcast_ref()
130    }
131
132    /// Returns a reference to the attribute's value as a [`Reflect`] trait object.
133    pub fn reflect_value(&self) -> &dyn Reflect {
134        &*self.value
135    }
136}
137
138impl Debug for CustomAttribute {
139    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
140        self.value.debug(f)
141    }
142}
143
144/// Implements methods for accessing custom attributes.
145///
146/// Implements the following methods:
147///
148/// * `fn custom_attributes(&self) -> &CustomAttributes`
149/// * `fn get_attribute<T: Reflect>(&self) -> Option<&T>`
150/// * `fn get_attribute_by_id(&self, id: TypeId) -> Option<&dyn Reflect>`
151/// * `fn has_attribute<T: Reflect>(&self) -> bool`
152/// * `fn has_attribute_by_id(&self, id: TypeId) -> bool`
153///
154/// # Params
155///
156/// * `$self` - The name of the variable containing the custom attributes (usually `self`).
157/// * `$attributes` - The name of the field containing the [`CustomAttributes`].
158/// * `$term` - (Optional) The term used to describe the type containing the custom attributes.
159///   This is purely used to generate better documentation. Defaults to `"item"`.
160macro_rules! impl_custom_attribute_methods {
161    ($self:ident . $attributes:ident, $term:literal) => {
162        $crate::attributes::impl_custom_attribute_methods!($self, &$self.$attributes, "item");
163    };
164    ($self:ident, $attributes:expr, $term:literal) => {
165        #[doc = concat!("Returns the custom attributes for this ", $term, ".")]
166        pub fn custom_attributes(&$self) -> &$crate::attributes::CustomAttributes {
167            $attributes
168        }
169
170        /// Gets a custom attribute by type.
171        ///
172        /// For dynamically accessing an attribute, see [`get_attribute_by_id`](Self::get_attribute_by_id).
173        pub fn get_attribute<T: $crate::Reflect>(&$self) -> Option<&T> {
174            $self.custom_attributes().get::<T>()
175        }
176
177        /// Gets a custom attribute by its [`TypeId`](core::any::TypeId).
178        ///
179        /// This is the dynamic equivalent of [`get_attribute`](Self::get_attribute).
180        pub fn get_attribute_by_id(&$self, id: ::core::any::TypeId) -> Option<&dyn $crate::Reflect> {
181            $self.custom_attributes().get_by_id(id)
182        }
183
184        #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute of the specified type.")]
185        #[doc = "\n\nFor dynamically checking if an attribute exists, see [`has_attribute_by_id`](Self::has_attribute_by_id)."]
186        pub fn has_attribute<T: $crate::Reflect>(&$self) -> bool {
187            $self.custom_attributes().contains::<T>()
188        }
189
190        #[doc = concat!("Returns `true` if this ", $term, " has a custom attribute with the specified [`TypeId`](::core::any::TypeId).")]
191        #[doc = "\n\nThis is the dynamic equivalent of [`has_attribute`](Self::has_attribute)"]
192        pub fn has_attribute_by_id(&$self, id: ::core::any::TypeId) -> bool {
193            $self.custom_attributes().contains_by_id(id)
194        }
195    };
196}
197
198/// Builder for [`CustomAttributes`].
199///
200/// ```
201/// # use bevy_reflect::attributes::CustomAttributesBuilder;
202/// let custom_attributes = CustomAttributesBuilder::new()
203///     .attribute("my attribute")
204///     .attribute(123)
205///     .build();
206/// ```
207#[derive(Default)]
208pub struct CustomAttributesBuilder {
209    attributes: TypeIdIndexMap<CustomAttribute>,
210}
211
212impl CustomAttributesBuilder {
213    /// Creates a new, empty builder.
214    pub fn new() -> Self {
215        Self::default()
216    }
217
218    /// Adds a single attribute to the builder.
219    pub fn attribute<T: Reflect>(self, value: T) -> Self {
220        self.attribute_erased(TypeId::of::<T>(), CustomAttribute::new(value))
221    }
222
223    // Erased version of `attribute` with inlining disabled. This reduces
224    // monomorphization costs, and avoids excessive inlining in cold generated
225    // code.
226    #[inline(never)]
227    fn attribute_erased(mut self, type_id: TypeId, value: CustomAttribute) -> Self {
228        self.attributes.insert(type_id, value);
229        self
230    }
231
232    /// Consumes the builder, returning the final [`CustomAttributes`].
233    pub fn build(self) -> CustomAttributes {
234        CustomAttributes::new(self.attributes)
235    }
236}
237
238pub(crate) use impl_custom_attribute_methods;
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243    use crate::{enums::VariantInfo, info::Typed, TypeInfo};
244    use alloc::{format, string::String};
245    use core::ops::RangeInclusive;
246
247    #[derive(Reflect, PartialEq, Debug)]
248    struct Tooltip(String);
249
250    impl Tooltip {
251        fn new(value: impl Into<String>) -> Self {
252            Self(value.into())
253        }
254    }
255
256    #[test]
257    fn should_get_custom_attribute() {
258        let attributes = CustomAttributesBuilder::new().attribute(0.0..=1.0).build();
259
260        let value = attributes.get::<RangeInclusive<f64>>().unwrap();
261        assert_eq!(&(0.0..=1.0), value);
262    }
263
264    #[test]
265    fn should_get_custom_attribute_dynamically() {
266        let attributes = CustomAttributesBuilder::new()
267            .attribute(String::from("Hello, World!"))
268            .build();
269
270        let value = attributes.get_by_id(TypeId::of::<String>()).unwrap();
271        assert!(value
272            .reflect_partial_eq(&String::from("Hello, World!"))
273            .unwrap());
274    }
275
276    #[test]
277    fn should_iterate_custom_attribute() {
278        let empty_attributes = CustomAttributesBuilder::new().build();
279
280        assert!(empty_attributes.iter().next().is_none());
281
282        let attributes = CustomAttributesBuilder::new()
283            .attribute(1i32)
284            .attribute("string")
285            .build();
286
287        let mut iter = attributes.iter();
288
289        let (type_id, reflected) = iter.next().unwrap();
290
291        assert_eq!(TypeId::of::<i32>(), *type_id);
292        assert_eq!(1i32, *reflected.downcast_ref::<i32>().unwrap());
293
294        let (type_id, reflected) = iter.next().unwrap();
295
296        assert_eq!(TypeId::of::<&str>(), *type_id);
297        assert_eq!("string", *reflected.downcast_ref::<&str>().unwrap());
298
299        assert!(iter.next().is_none());
300    }
301
302    #[test]
303    fn should_debug_custom_attributes() {
304        let attributes = CustomAttributesBuilder::new().build();
305
306        let debug = format!("{attributes:?}");
307
308        assert_eq!(r#"{}"#, debug);
309
310        let attributes = CustomAttributesBuilder::new()
311            .attribute("My awesome custom attribute!")
312            .build();
313
314        let debug = format!("{attributes:?}");
315
316        assert_eq!(r#"{"My awesome custom attribute!"}"#, debug);
317
318        #[derive(Reflect)]
319        struct Foo {
320            value: i32,
321        }
322
323        let attributes = CustomAttributesBuilder::new()
324            .attribute(Foo { value: 42 })
325            .build();
326
327        let debug = format!("{attributes:?}");
328
329        assert_eq!(
330            r#"{bevy_reflect::attributes::tests::Foo { value: 42 }}"#,
331            debug
332        );
333    }
334
335    #[test]
336    fn should_derive_custom_attributes_on_struct_container() {
337        #[derive(Reflect)]
338        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
339        struct Slider {
340            value: f32,
341        }
342
343        let TypeInfo::Struct(info) = Slider::type_info() else {
344            panic!("expected struct info");
345        };
346
347        let tooltip = info.get_attribute::<Tooltip>().unwrap();
348        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
349    }
350
351    #[test]
352    fn should_derive_custom_attributes_on_struct_fields() {
353        #[derive(Reflect)]
354        struct Slider {
355            #[reflect(@0.0..=1.0)]
356            #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
357            value: f32,
358        }
359
360        let TypeInfo::Struct(info) = Slider::type_info() else {
361            panic!("expected struct info");
362        };
363
364        let field = info.field("value").unwrap();
365
366        let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
367        assert_eq!(&(0.0..=1.0), range);
368
369        let tooltip = field.get_attribute::<Tooltip>().unwrap();
370        assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
371    }
372
373    #[test]
374    fn should_derive_custom_attributes_on_tuple_container() {
375        #[derive(Reflect)]
376        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
377        struct Slider(f32);
378
379        let TypeInfo::TupleStruct(info) = Slider::type_info() else {
380            panic!("expected tuple struct info");
381        };
382
383        let tooltip = info.get_attribute::<Tooltip>().unwrap();
384        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
385    }
386
387    #[test]
388    fn should_derive_custom_attributes_on_tuple_struct_fields() {
389        #[derive(Reflect)]
390        struct Slider(
391            #[reflect(@0.0..=1.0)]
392            #[reflect(@Tooltip::new("Range: 0.0 to 1.0"))]
393            f32,
394        );
395
396        let TypeInfo::TupleStruct(info) = Slider::type_info() else {
397            panic!("expected tuple struct info");
398        };
399
400        let field = info.field_at(0).unwrap();
401
402        let range = field.get_attribute::<RangeInclusive<f64>>().unwrap();
403        assert_eq!(&(0.0..=1.0), range);
404
405        let tooltip = field.get_attribute::<Tooltip>().unwrap();
406        assert_eq!(&Tooltip::new("Range: 0.0 to 1.0"), tooltip);
407    }
408
409    #[test]
410    fn should_derive_custom_attributes_on_enum_container() {
411        #[derive(Reflect)]
412        #[reflect(@Tooltip::new("My awesome custom attribute!"))]
413        enum Color {
414            Transparent,
415            Grayscale(f32),
416            Rgb { r: u8, g: u8, b: u8 },
417        }
418
419        let TypeInfo::Enum(info) = Color::type_info() else {
420            panic!("expected enum info");
421        };
422
423        let tooltip = info.get_attribute::<Tooltip>().unwrap();
424        assert_eq!(&Tooltip::new("My awesome custom attribute!"), tooltip);
425    }
426
427    #[test]
428    fn should_derive_custom_attributes_on_enum_variants() {
429        #[derive(Reflect, Debug, PartialEq)]
430        enum Display {
431            Toggle,
432            Slider,
433            Picker,
434        }
435
436        #[derive(Reflect)]
437        enum Color {
438            #[reflect(@Display::Toggle)]
439            Transparent,
440            #[reflect(@Display::Slider)]
441            Grayscale(f32),
442            #[reflect(@Display::Picker)]
443            Rgb { r: u8, g: u8, b: u8 },
444        }
445
446        let TypeInfo::Enum(info) = Color::type_info() else {
447            panic!("expected enum info");
448        };
449
450        let VariantInfo::Unit(transparent_variant) = info.variant("Transparent").unwrap() else {
451            panic!("expected unit variant");
452        };
453
454        let display = transparent_variant.get_attribute::<Display>().unwrap();
455        assert_eq!(&Display::Toggle, display);
456
457        let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
458            panic!("expected tuple variant");
459        };
460
461        let display = grayscale_variant.get_attribute::<Display>().unwrap();
462        assert_eq!(&Display::Slider, display);
463
464        let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
465            panic!("expected struct variant");
466        };
467
468        let display = rgb_variant.get_attribute::<Display>().unwrap();
469        assert_eq!(&Display::Picker, display);
470    }
471
472    #[test]
473    fn should_derive_custom_attributes_on_enum_variant_fields() {
474        #[derive(Reflect)]
475        enum Color {
476            Transparent,
477            Grayscale(#[reflect(@0.0..=1.0_f32)] f32),
478            Rgb {
479                #[reflect(@0..=255u8)]
480                r: u8,
481                #[reflect(@0..=255u8)]
482                g: u8,
483                #[reflect(@0..=255u8)]
484                b: u8,
485            },
486        }
487
488        let TypeInfo::Enum(info) = Color::type_info() else {
489            panic!("expected enum info");
490        };
491
492        let VariantInfo::Tuple(grayscale_variant) = info.variant("Grayscale").unwrap() else {
493            panic!("expected tuple variant");
494        };
495
496        let field = grayscale_variant.field_at(0).unwrap();
497
498        let range = field.get_attribute::<RangeInclusive<f32>>().unwrap();
499        assert_eq!(&(0.0..=1.0), range);
500
501        let VariantInfo::Struct(rgb_variant) = info.variant("Rgb").unwrap() else {
502            panic!("expected struct variant");
503        };
504
505        let field = rgb_variant.field("g").unwrap();
506
507        let range = field.get_attribute::<RangeInclusive<u8>>().unwrap();
508        assert_eq!(&(0..=255), range);
509    }
510
511    #[test]
512    fn should_allow_unit_struct_attribute_values() {
513        #[derive(Reflect)]
514        struct Required;
515
516        #[derive(Reflect)]
517        struct Foo {
518            #[reflect(@Required)]
519            value: i32,
520        }
521
522        let TypeInfo::Struct(info) = Foo::type_info() else {
523            panic!("expected struct info");
524        };
525
526        let field = info.field("value").unwrap();
527        assert!(field.has_attribute::<Required>());
528    }
529
530    #[test]
531    fn should_accept_last_attribute() {
532        #[derive(Reflect)]
533        struct Foo {
534            #[reflect(@false)]
535            #[reflect(@true)]
536            value: i32,
537        }
538
539        let TypeInfo::Struct(info) = Foo::type_info() else {
540            panic!("expected struct info");
541        };
542
543        let field = info.field("value").unwrap();
544        assert!(field.get_attribute::<bool>().unwrap());
545    }
546}