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