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
This commit is contained in:
Gino Valente 2024-07-15 07:19:50 -07:00 committed by GitHub
parent bf53cf30c7
commit 276815a9a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 162 additions and 68 deletions

View file

@ -2192,13 +2192,13 @@ category = "Reflection"
wasm = false
[[example]]
name = "trait_reflection"
path = "examples/reflection/trait_reflection.rs"
name = "type_data"
path = "examples/reflection/type_data.rs"
doc-scrape-examples = true
[package.metadata.example.trait_reflection]
name = "Trait Reflection"
description = "Allows reflection with trait objects"
[package.metadata.example.type_data]
name = "Type Data"
description = "Demonstrates how to create and use type data"
category = "Reflection"
wasm = false

View file

@ -356,7 +356,7 @@ Example | Description
[Generic Reflection](../examples/reflection/generic_reflection.rs) | Registers concrete instances of generic types that may be used with reflection
[Reflection](../examples/reflection/reflection.rs) | Demonstrates how reflection in Bevy provides a way to dynamically interact with Rust types
[Reflection Types](../examples/reflection/reflection_types.rs) | Illustrates the various reflection types available
[Trait Reflection](../examples/reflection/trait_reflection.rs) | Allows reflection with trait objects
[Type Data](../examples/reflection/type_data.rs) | Demonstrates how to create and use type data
## Scene

View file

@ -1,62 +0,0 @@
//! Allows reflection with trait objects.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.register_type::<MyType>()
.add_systems(Startup, setup)
.run();
}
#[derive(Reflect)]
#[reflect(DoThing)]
struct MyType {
value: String,
}
impl DoThing for MyType {
fn do_thing(&self) -> String {
format!("{} World!", self.value)
}
}
#[reflect_trait]
trait DoThing {
fn do_thing(&self) -> String;
}
fn setup(type_registry: Res<AppTypeRegistry>) {
// First, lets box our type as a Box<dyn Reflect>
let reflect_value: Box<dyn Reflect> = Box::new(MyType {
value: "Hello".to_string(),
});
// This means we no longer have direct access to MyType or its methods. We can only call Reflect
// methods on reflect_value. What if we want to call `do_thing` on our type? We could
// downcast using reflect_value.downcast_ref::<MyType>(), but what if we don't know the type
// at compile time?
// Normally in rust we would be out of luck at this point. Lets use our new reflection powers to
// do something cool!
let type_registry = type_registry.read();
// The #[reflect] attribute we put on our DoThing trait generated a new `ReflectDoThing` struct,
// which implements TypeData. This was added to MyType's TypeRegistration.
let reflect_do_thing = type_registry
.get_type_data::<ReflectDoThing>(reflect_value.type_id())
.unwrap();
// We can use this generated type to convert our `&dyn Reflect` reference to a `&dyn DoThing`
// reference
let my_trait: &dyn DoThing = reflect_do_thing.get(&*reflect_value).unwrap();
// Which means we can now call do_thing(). Magic!
info!("{}", my_trait.do_thing());
// This works because the #[reflect(MyTrait)] we put on MyType informed the Reflect derive to
// insert a new instance of ReflectDoThing into MyType's registration. The instance knows
// how to cast &dyn Reflect to &dyn MyType, because it knows that &dyn Reflect should first
// be downcasted to &MyType, which can then be safely casted to &dyn MyType
}

View file

@ -0,0 +1,156 @@
//! The example demonstrates what type data is, how to create it, and how to use it.
use bevy::prelude::*;
use bevy::reflect::{FromType, TypeRegistry};
// It's recommended to read this example from top to bottom.
// Comments are provided to explain the code and its purpose as you go along.
fn main() {
trait Damageable {
type Health;
fn damage(&mut self, damage: Self::Health);
}
#[derive(Reflect, PartialEq, Debug)]
struct Zombie {
health: u32,
}
impl Damageable for Zombie {
type Health = u32;
fn damage(&mut self, damage: Self::Health) {
self.health -= damage;
}
}
// Let's say we have a reflected value.
// Here we know it's a `Zombie`, but for demonstration purposes let's pretend we don't.
// Pretend it's just some `Box<dyn Reflect>` value.
let mut value: Box<dyn Reflect> = Box::new(Zombie { health: 100 });
// We think `value` might contain a type that implements `Damageable`
// and now we want to call `Damageable::damage` on it.
// How can we do this without knowing in advance the concrete type is `Zombie`?
// This is where type data comes in.
// Type data is a way of associating type-specific data with a type for use in dynamic contexts.
// This type data can then be used at runtime to perform type-specific operations.
// Let's create a type data struct for `Damageable` that we can associate with `Zombie`!
// Firstly, type data must be cloneable.
#[derive(Clone)]
// Next, they are usually named with the `Reflect` prefix (we'll see why in a bit).
struct ReflectDamageable {
// Type data can contain whatever you want, but it's common to include function pointers
// to the type-specific operations you want to perform (such as trait methods).
// Just remember that we're working with `Reflect` data,
// so we can't use `Self`, generics, or associated types.
// In those cases, we'll have to use `dyn Reflect` trait objects.
damage: fn(&mut dyn Reflect, damage: Box<dyn Reflect>),
}
// Now, we can create a blanket implementation of the `FromType` trait to construct our type data
// for any type that implements `Reflect` and `Damageable`.
impl<T: Reflect + Damageable<Health: Reflect>> FromType<T> for ReflectDamageable {
fn from_type() -> Self {
Self {
damage: |reflect, damage| {
// This requires that `reflect` is `T` and not a dynamic representation like `DynamicStruct`.
// We could have the function pointer return a `Result`, but we'll just `unwrap` for simplicity.
let damageable = reflect.downcast_mut::<T>().unwrap();
let damage = damage.take::<T::Health>().unwrap();
damageable.damage(damage);
},
}
}
}
// It's also common to provide convenience methods for calling the type-specific operations.
impl ReflectDamageable {
pub fn damage(&self, reflect: &mut dyn Reflect, damage: Box<dyn Reflect>) {
(self.damage)(reflect, damage);
}
}
// With all this done, we're ready to make use of `ReflectDamageable`!
// It starts with registering our type along with its type data:
let mut registry = TypeRegistry::default();
registry.register::<Zombie>();
registry.register_type_data::<Zombie, ReflectDamageable>();
// Then at any point we can retrieve the type data from the registry:
let type_id = value.get_represented_type_info().unwrap().type_id();
let reflect_damageable = registry
.get_type_data::<ReflectDamageable>(type_id)
.unwrap();
// And call our method:
reflect_damageable.damage(value.as_reflect_mut(), Box::new(25u32));
assert_eq!(value.take::<Zombie>().unwrap(), Zombie { health: 75 });
// This is a simple example, but type data can be used for much more complex operations.
// Bevy also provides some useful shorthand for working with type data.
// For example, we can have the type data be automatically registered when we register the type
// by using the `#[reflect(MyTrait)]` attribute when defining our type.
#[derive(Reflect)]
// Notice that we don't need to type out `ReflectDamageable`.
// This is why we named it with the `Reflect` prefix:
// the derive macro will automatically look for a type named `ReflectDamageable` in the current scope.
#[reflect(Damageable)]
struct Skeleton {
health: u32,
}
impl Damageable for Skeleton {
type Health = u32;
fn damage(&mut self, damage: Self::Health) {
self.health -= damage;
}
}
// This will now register `Skeleton` along with its `ReflectDamageable` type data.
registry.register::<Skeleton>();
// And for object-safe traits (see https://doc.rust-lang.org/reference/items/traits.html#object-safety),
// Bevy provides a convenience macro for generating type data that converts `dyn Reflect` into `dyn MyTrait`.
#[reflect_trait]
trait Health {
fn health(&self) -> u32;
}
impl Health for Skeleton {
fn health(&self) -> u32 {
self.health
}
}
// Using the `#[reflect_trait]` macro we're able to automatically generate a `ReflectHealth` type data struct,
// which can then be registered like any other type data:
registry.register_type_data::<Skeleton, ReflectHealth>();
// Now we can use `ReflectHealth` to convert `dyn Reflect` into `dyn Health`:
let value: Box<dyn Reflect> = Box::new(Skeleton { health: 50 });
let type_id = value.get_represented_type_info().unwrap().type_id();
let reflect_health = registry.get_type_data::<ReflectHealth>(type_id).unwrap();
// Type data generated by `#[reflect_trait]` comes with a `get`, `get_mut`, and `get_boxed` method,
// which convert `&dyn Reflect` into `&dyn MyTrait`, `&mut dyn Reflect` into `&mut dyn MyTrait`,
// and `Box<dyn Reflect>` into `Box<dyn MyTrait>`, respectively.
let value: &dyn Health = reflect_health.get(value.as_reflect()).unwrap();
assert_eq!(value.health(), 50);
// Lastly, here's a list of some useful type data provided by Bevy that you might want to register for your types:
// - `ReflectDefault` for types that implement `Default`
// - `ReflectFromWorld` for types that implement `FromWorld`
// - `ReflectComponent` for types that implement `Component`
// - `ReflectResource` for types that implement `Resource`
// - `ReflectSerialize` for types that implement `Serialize`
// - `ReflectDeserialize` for types that implement `Deserialize`
//
// And here are some that are automatically registered by the `Reflect` derive macro:
// - `ReflectFromPtr`
// - `ReflectFromReflect` (if not `#[reflect(from_reflect = false)]`)
}