Skip to content

Commit 276815a

Browse files
authored
examples: Add Type Data reflection example (#13903)
# Objective Type data is a **super** useful tool to know about when working with reflection. However, most users don't fully understand how it works or that you can use it for more than just object-safe traits. This is unfortunate because it can be surprisingly simple to manually create your own type data. We should have an example detailing how type works, how users can define their own, and how thy can be used. ## Solution Added a `type_data` example. This example goes through all the major points about type data: - Why we need them - How they can be defined - The two ways they can be registered - A list of common/important type data provided by Bevy I also thought it might be good to go over the `#[reflect_trait]` macro as part of this example since it has all the other context, including how to define type data in places where `#[reflect_trait]` won't work. Because of this, I removed the `trait_reflection` example. ## Testing You can run the example locally with the following command: ``` cargo run --example type_data ``` --- ## Changelog - Added the `type_data` example - Removed the `trait_reflection` example
1 parent bf53cf3 commit 276815a

File tree

4 files changed

+162
-68
lines changed

4 files changed

+162
-68
lines changed

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2192,13 +2192,13 @@ category = "Reflection"
21922192
wasm = false
21932193

21942194
[[example]]
2195-
name = "trait_reflection"
2196-
path = "examples/reflection/trait_reflection.rs"
2195+
name = "type_data"
2196+
path = "examples/reflection/type_data.rs"
21972197
doc-scrape-examples = true
21982198

2199-
[package.metadata.example.trait_reflection]
2200-
name = "Trait Reflection"
2201-
description = "Allows reflection with trait objects"
2199+
[package.metadata.example.type_data]
2200+
name = "Type Data"
2201+
description = "Demonstrates how to create and use type data"
22022202
category = "Reflection"
22032203
wasm = false
22042204

examples/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,7 +356,7 @@ Example | Description
356356
[Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection
357357
[Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types
358358
[Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available
359-
[Trait Reflection](../examples/reflection/trait_reflection.rs) | Allows reflection with trait objects
359+
[Type Data](../examples/reflection/type_data.rs) | Demonstrates how to create and use type data
360360

361361
## Scene
362362

examples/reflection/trait_reflection.rs

Lines changed: 0 additions & 62 deletions
This file was deleted.

examples/reflection/type_data.rs

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
//! The example demonstrates what type data is, how to create it, and how to use it.
2+
3+
use bevy::prelude::*;
4+
use bevy::reflect::{FromType, TypeRegistry};
5+
6+
// It's recommended to read this example from top to bottom.
7+
// Comments are provided to explain the code and its purpose as you go along.
8+
fn main() {
9+
trait Damageable {
10+
type Health;
11+
fn damage(&mut self, damage: Self::Health);
12+
}
13+
14+
#[derive(Reflect, PartialEq, Debug)]
15+
struct Zombie {
16+
health: u32,
17+
}
18+
19+
impl Damageable for Zombie {
20+
type Health = u32;
21+
fn damage(&mut self, damage: Self::Health) {
22+
self.health -= damage;
23+
}
24+
}
25+
26+
// Let's say we have a reflected value.
27+
// Here we know it's a `Zombie`, but for demonstration purposes let's pretend we don't.
28+
// Pretend it's just some `Box<dyn Reflect>` value.
29+
let mut value: Box<dyn Reflect> = Box::new(Zombie { health: 100 });
30+
31+
// We think `value` might contain a type that implements `Damageable`
32+
// and now we want to call `Damageable::damage` on it.
33+
// How can we do this without knowing in advance the concrete type is `Zombie`?
34+
35+
// This is where type data comes in.
36+
// Type data is a way of associating type-specific data with a type for use in dynamic contexts.
37+
// This type data can then be used at runtime to perform type-specific operations.
38+
39+
// Let's create a type data struct for `Damageable` that we can associate with `Zombie`!
40+
41+
// Firstly, type data must be cloneable.
42+
#[derive(Clone)]
43+
// Next, they are usually named with the `Reflect` prefix (we'll see why in a bit).
44+
struct ReflectDamageable {
45+
// Type data can contain whatever you want, but it's common to include function pointers
46+
// to the type-specific operations you want to perform (such as trait methods).
47+
// Just remember that we're working with `Reflect` data,
48+
// so we can't use `Self`, generics, or associated types.
49+
// In those cases, we'll have to use `dyn Reflect` trait objects.
50+
damage: fn(&mut dyn Reflect, damage: Box<dyn Reflect>),
51+
}
52+
53+
// Now, we can create a blanket implementation of the `FromType` trait to construct our type data
54+
// for any type that implements `Reflect` and `Damageable`.
55+
impl<T: Reflect + Damageable<Health: Reflect>> FromType<T> for ReflectDamageable {
56+
fn from_type() -> Self {
57+
Self {
58+
damage: |reflect, damage| {
59+
// This requires that `reflect` is `T` and not a dynamic representation like `DynamicStruct`.
60+
// We could have the function pointer return a `Result`, but we'll just `unwrap` for simplicity.
61+
let damageable = reflect.downcast_mut::<T>().unwrap();
62+
let damage = damage.take::<T::Health>().unwrap();
63+
damageable.damage(damage);
64+
},
65+
}
66+
}
67+
}
68+
69+
// It's also common to provide convenience methods for calling the type-specific operations.
70+
impl ReflectDamageable {
71+
pub fn damage(&self, reflect: &mut dyn Reflect, damage: Box<dyn Reflect>) {
72+
(self.damage)(reflect, damage);
73+
}
74+
}
75+
76+
// With all this done, we're ready to make use of `ReflectDamageable`!
77+
// It starts with registering our type along with its type data:
78+
let mut registry = TypeRegistry::default();
79+
registry.register::<Zombie>();
80+
registry.register_type_data::<Zombie, ReflectDamageable>();
81+
82+
// Then at any point we can retrieve the type data from the registry:
83+
let type_id = value.get_represented_type_info().unwrap().type_id();
84+
let reflect_damageable = registry
85+
.get_type_data::<ReflectDamageable>(type_id)
86+
.unwrap();
87+
88+
// And call our method:
89+
reflect_damageable.damage(value.as_reflect_mut(), Box::new(25u32));
90+
assert_eq!(value.take::<Zombie>().unwrap(), Zombie { health: 75 });
91+
92+
// This is a simple example, but type data can be used for much more complex operations.
93+
// Bevy also provides some useful shorthand for working with type data.
94+
95+
// For example, we can have the type data be automatically registered when we register the type
96+
// by using the `#[reflect(MyTrait)]` attribute when defining our type.
97+
#[derive(Reflect)]
98+
// Notice that we don't need to type out `ReflectDamageable`.
99+
// This is why we named it with the `Reflect` prefix:
100+
// the derive macro will automatically look for a type named `ReflectDamageable` in the current scope.
101+
#[reflect(Damageable)]
102+
struct Skeleton {
103+
health: u32,
104+
}
105+
106+
impl Damageable for Skeleton {
107+
type Health = u32;
108+
fn damage(&mut self, damage: Self::Health) {
109+
self.health -= damage;
110+
}
111+
}
112+
113+
// This will now register `Skeleton` along with its `ReflectDamageable` type data.
114+
registry.register::<Skeleton>();
115+
116+
// And for object-safe traits (see https://doc.rust-lang.org/reference/items/traits.html#object-safety),
117+
// Bevy provides a convenience macro for generating type data that converts `dyn Reflect` into `dyn MyTrait`.
118+
#[reflect_trait]
119+
trait Health {
120+
fn health(&self) -> u32;
121+
}
122+
123+
impl Health for Skeleton {
124+
fn health(&self) -> u32 {
125+
self.health
126+
}
127+
}
128+
129+
// Using the `#[reflect_trait]` macro we're able to automatically generate a `ReflectHealth` type data struct,
130+
// which can then be registered like any other type data:
131+
registry.register_type_data::<Skeleton, ReflectHealth>();
132+
133+
// Now we can use `ReflectHealth` to convert `dyn Reflect` into `dyn Health`:
134+
let value: Box<dyn Reflect> = Box::new(Skeleton { health: 50 });
135+
136+
let type_id = value.get_represented_type_info().unwrap().type_id();
137+
let reflect_health = registry.get_type_data::<ReflectHealth>(type_id).unwrap();
138+
139+
// Type data generated by `#[reflect_trait]` comes with a `get`, `get_mut`, and `get_boxed` method,
140+
// which convert `&dyn Reflect` into `&dyn MyTrait`, `&mut dyn Reflect` into `&mut dyn MyTrait`,
141+
// and `Box<dyn Reflect>` into `Box<dyn MyTrait>`, respectively.
142+
let value: &dyn Health = reflect_health.get(value.as_reflect()).unwrap();
143+
assert_eq!(value.health(), 50);
144+
145+
// Lastly, here's a list of some useful type data provided by Bevy that you might want to register for your types:
146+
// - `ReflectDefault` for types that implement `Default`
147+
// - `ReflectFromWorld` for types that implement `FromWorld`
148+
// - `ReflectComponent` for types that implement `Component`
149+
// - `ReflectResource` for types that implement `Resource`
150+
// - `ReflectSerialize` for types that implement `Serialize`
151+
// - `ReflectDeserialize` for types that implement `Deserialize`
152+
//
153+
// And here are some that are automatically registered by the `Reflect` derive macro:
154+
// - `ReflectFromPtr`
155+
// - `ReflectFromReflect` (if not `#[reflect(from_reflect = false)]`)
156+
}

0 commit comments

Comments
 (0)