-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
bevy_reflect: Generic parameter info #15475
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bevy_reflect: Generic parameter info #15475
Conversation
The `HashMap` not only takes up more memory, but is actually slightly slower in the common case.
Also switched to derived `Debug` impl
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, just some questions
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice API for knowing Generics of a type, LGTM
# Objective Currently, reflecting a generic type provides no information about the generic parameters. This means that you can't get access to the type of `T` in `Foo<T>` without creating custom type data (we do this for [`ReflectHandle`](https://docs.rs/bevy/0.14.2/bevy/asset/struct.ReflectHandle.html#method.asset_type_id)). ## Solution This PR makes it so that generic type parameters and generic const parameters are tracked in a `Generics` struct stored on the `TypeInfo` for a type. For example, `struct Foo<T, const N: usize>` will store `T` and `N` as a `TypeParamInfo` and `ConstParamInfo`, respectively. The stored information includes: - The name of the generic parameter (i.e. `T`, `N`, etc.) - The type of the generic parameter (remember that we're dealing with monomorphized types, so this will actually be a concrete type) - The default type/value, if any (e.g. `f32` in `T = f32` or `10` in `const N: usize = 10`) ### Caveats The only requirement for this to work is that the user does not opt-out of the automatic `TypePath` derive with `#[reflect(type_path = false)]`. Doing so prevents the macro code from 100% knowing that the generic type implements `TypePath`. This in turn means the generated `Typed` impl can't add generics to the type. There are two solutions for this—both of which I think we should explore in a future PR: 1. We could just not use `TypePath`. This would mean that we can't store the `Type` of the generic, but we can at least store the `TypeId`. 2. We could provide a way to opt out of the automatic `Typed` derive with a `#[reflect(typed = false)]` attribute. This would allow users to manually implement `Typed` to add whatever generic information they need (e.g. skipping a parameter that can't implement `TypePath` while the rest can). I originally thought about making `Generics` an enum with `Generic`, `NonGeneric`, and `Unavailable` variants to signify whether there are generics, no generics, or generics that cannot be added due to opting out of `TypePath`. I ultimately decided against this as I think it adds a bit too much complexity for such an uncommon problem. Additionally, user's don't necessarily _have_ to know the generics of a type, so just skipping them should generally be fine for now. ## Testing You can test locally by running: ``` cargo test --package bevy_reflect ``` --- ## Showcase You can now access generic parameters via `TypeInfo`! ```rust #[derive(Reflect)] struct MyStruct<T, const N: usize>([T; N]); let generics = MyStruct::<f32, 10>::type_info().generics(); // Get by index: let t = generics.get(0).unwrap(); assert_eq!(t.name(), "T"); assert!(t.ty().is::<f32>()); assert!(!t.is_const()); // Or by name: let n = generics.get_named("N").unwrap(); assert_eq!(n.name(), "N"); assert!(n.ty().is::<usize>()); assert!(n.is_const()); ``` You can even access parameter defaults: ```rust #[derive(Reflect)] struct MyStruct<T = String, const N: usize = 10>([T; N]); let generics = MyStruct::<f32, 5>::type_info().generics(); let GenericInfo::Type(info) = generics.get_named("T").unwrap() else { panic!("expected a type parameter"); }; let default = info.default().unwrap(); assert!(default.is::<String>()); let GenericInfo::Const(info) = generics.get_named("N").unwrap() else { panic!("expected a const parameter"); }; let default = info.default().unwrap(); assert_eq!(default.downcast_ref::<usize>().unwrap(), &10); ```
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1705 if you'd like to help out. |
Objective
Currently, reflecting a generic type provides no information about the generic parameters. This means that you can't get access to the type of
T
inFoo<T>
without creating custom type data (we do this forReflectHandle
).Solution
This PR makes it so that generic type parameters and generic const parameters are tracked in a
Generics
struct stored on theTypeInfo
for a type.For example,
struct Foo<T, const N: usize>
will storeT
andN
as aTypeParamInfo
andConstParamInfo
, respectively.The stored information includes:
T
,N
, etc.)f32
inT = f32
or10
inconst N: usize = 10
)Caveats
The only requirement for this to work is that the user does not opt-out of the automatic
TypePath
derive with#[reflect(type_path = false)]
.Doing so prevents the macro code from 100% knowing that the generic type implements
TypePath
. This in turn means the generatedTyped
impl can't add generics to the type.There are two solutions for this—both of which I think we should explore in a future PR:
TypePath
. This would mean that we can't store theType
of the generic, but we can at least store theTypeId
.Typed
derive with a#[reflect(typed = false)]
attribute. This would allow users to manually implementTyped
to add whatever generic information they need (e.g. skipping a parameter that can't implementTypePath
while the rest can).I originally thought about making
Generics
an enum withGeneric
,NonGeneric
, andUnavailable
variants to signify whether there are generics, no generics, or generics that cannot be added due to opting out ofTypePath
. I ultimately decided against this as I think it adds a bit too much complexity for such an uncommon problem.Additionally, users don't necessarily have to know the generics of a type, so just skipping them should generally be fine for now.
Testing
You can test locally by running:
Showcase
You can now access generic parameters via
TypeInfo
!You can even access parameter defaults: