-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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: Allow parameters to be passed to type data #13723
base: main
Are you sure you want to change the base?
Conversation
Replaces `FromType` with `CreateTypeData`
/// | ||
/// This is used by the `#[derive(Reflect)]` macro to generate an implementation | ||
/// of [`TypeData`] to pass to [`TypeRegistration::insert`]. | ||
pub trait FromType<T> { |
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.
I wonder if it's desirable to keep FromType
and have a generic implementation like
impl <T, D> CreateTypeData<T, ()> for D where D: FromType<T> + TypeData {
fn create_type_data(_input: ()) -> Self {
D::from_type()
}
}
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.
Oh and mark it as deprecated
? That's probably better than flat out removing it 🤔
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.
Agreed, this is an important trait after all, maybe deprecating it for now is better.
Objective
Addresses comments regarding #7317 (note that this doesn't replace #7317, there are still some great improvements there besides this syntactical problem).
There currently exist some "special" type data registrations that can be registered like other type data (e.g.
#[reflect(Hash)]
) or can use a "special" syntax to allow specifying custom implementations (e.g.#[reflect(Hash(custom_hash_fn))]
). And there may be more to follow (#13432).What's interesting is that most of these special cased registrations don't actually come with any type data type. Instead, they simply modify methods on
Reflect
(e.g.Reflect::reflect_hash
).#7317 sought to distinguish between these "special" registrations by making them lowercase and use a more conventional attribute style:
#[reflect(hash = "custom_hash_fn")]
.However, while this did help distinguish these registrations and make them a bit prettier, they now require the user to actually know which traits are "special" and which are not (as pointed out here).
Ideally, users shouldn't have to know which traits are "special" until they need to. For most users, they should just know that they need to register their trait in order for certain things to work. And the special-casing may be easier to follow if we open up the configuration abilities to all type data.
Solution
This PR introduces
CreateTypeData
which replacesFromType
. This was done for two reasons.Firstly,
FromType
isn't very descriptive as to what it should be used for. We are creating type data from a type, but it's not immediately clear this is even for type data. Renaming toCreateTypeData
should hopefully make this much clearer.Secondly, in order to support type data with parameters like the
custom_hash_fn
inreflect(Hash(custom_hash_fn))
, an additionalInput
type parameter had to be added. This makes the new signatureCreateTypeData<T, Input = ()>
.We can now create type data that accepts input!
And then register them with the special function-like syntax:
The above code will compile into the following registration:
Notice how the macro automatically generates the tuple for us, so we don't have to add an additional layer of parentheses.
Multiple Input Types
You might be wondering why we're using a type parameter instead of an associated type to specify the input type.
An associated type would limit us to a single implementation. This means that if we want to support the type data with optional parameters (e.g. support both
Hash
andHash(custom_hash_fn)
), then all type data must take inOption<Self::Input>
, regardless of whether or not aNone
case is supported.This is important because the macro has to be pass in something, whether that be
()
orNone
.By using a type parameter we open the door to type data with required input:
However, this may be something we don't necessarily care about since users could also get away with this using custom input enums. And the required-input case could be deferred until runtime (i.e. maybe a panic in the
None
case).Adding
ReflectPartialEq
andReflectHash
I had originally considered adding
ReflectPartialEq
andReflectHash
type data to further decrease the differences between the "special" registrations and the regular ones. However, I chose not to do that to (1) reduce the complexity of this PR and (2) we may end up removing these entirely due to #8695.What else is this good for?
Another question you might have is what else this is good for beyond just making things a bit more consistent.
I'm not sure exactly how the community will use it, but I can see it being used for things like feature gating certain functionality:
Or to emulate specialization via reflection:
Note that all of the above could always be done with manual registration. However, due to them requiring input, some cases could only be done with manual registration.
This PR mainly opens the door to doing more of this interesting stuff with type data via the macro registration. It not only unifies "special" and regular registrations, but also manual and automatic registrations.
Testing
The tests for this feature are split into doctests (for the docs on
CreateTypeData
) and in the compile-fail tests.These will both be verified automatically by CI.
Changelog
FromType<T>
withCreateTypeData<T, Input = ()>
#[reflect(MyTrait(...))]
syntaxTypeRegistry::register_type_data_with
methodMigration Guide
FromType<T>
has been replaced byCreateTypeData<T, Input = ()>
. Implementors ofFromType<T>
will need to update their implementation:Additionally, any calls made to
FromType::from_type
will need to be updated as well: