-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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: Reflect remote types #6042
Conversation
b840db9
to
626fd81
Compare
e2383bb
to
f86db5e
Compare
This sounds really useful for any sort of editor or inspector in the future. Especially if a particular bevy plugin or non-bevy crate does not use the reflect types. I don't feel qualified to fully review this, as I don't know much about bevy's reflection stuff, but this PR is something that is needed! |
I definitely need this for bevy_synthizer. Some of my types wrap theirs so I can use them as components. Unfortunately, for the moment I can't use any of these types in scenes, which is important since some of these components are audio-related properties that require specific tweaking per entity. |
bdac2e4
to
d1f0286
Compare
It appears that nesting remote reflect with use bevy::reflect::reflect_remote;
mod remote {
pub struct A;
pub struct B {
pub a: A,
}
}
#[reflect_remote(remote::A, FromReflect)]
struct LocalA;
#[reflect_remote(remote::B, FromReflect)] // Error: the trait `FromReflect` is not implemented for `remote::A`
struct LocalB {
#[reflect(remote = "LocalA")]
pub a: remote::A,
} |
Attributes on fields are lost when mod remote {
pub struct A {
#[cfg(oof)]
pub a: i32,
}
}
#[reflect_remote(remote::A)]
pub struct LocalA {
#[cfg(oof)] // Should be grayed out in IDE but isn't after adding reflect_remote
pub a: i32, // Error: no field `a` on type `remote::A`
} |
@devil-ira The nesting issue should be resolved (as well as an issue with generics that I discovered while trying to fix it). Unfortunately, I'm not sure how to go about fixing the attribute issue. It seems the best way to handle this (without adding complexity/specialized logic) would be to just add the attribute to the entire item: mod remote {
pub struct A {
#[cfg(oof)]
pub a: i32,
}
}
#[cfg(not(oof))]
#[reflect_remote(remote::A)]
pub struct LocalA {}
#[cfg(oof)]
#[reflect_remote(remote::A)]
pub struct LocalA {
pub a: i32,
} I think if we want to fix this, it should probably happen in a followup PR. |
# Objective There isn't really a way to test that code using bevy_reflect compiles or doesn't compile for certain scenarios. This would be especially useful for macro-centric PRs like #6511 and #6042. ## Solution Using `bevy_ecs_compile_fail_tests` as reference, added the `bevy_reflect_compile_fail_tests` crate. Currently, this crate contains a very simple test case. This is so that we can get the basic foundation of this crate agreed upon and merged so that more tests can be added by other PRs. ### Open Questions - [x] Should this be added to CI? (Answer: Yes) --- ## Changelog - Added the `bevy_reflect_compile_fail_tests` crate for testing compilation errors
Added two more things:
|
65b1c56
to
3462335
Compare
# Objective There isn't really a way to test that code using bevy_reflect compiles or doesn't compile for certain scenarios. This would be especially useful for macro-centric PRs like bevyengine#6511 and bevyengine#6042. ## Solution Using `bevy_ecs_compile_fail_tests` as reference, added the `bevy_reflect_compile_fail_tests` crate. Currently, this crate contains a very simple test case. This is so that we can get the basic foundation of this crate agreed upon and merged so that more tests can be added by other PRs. ### Open Questions - [x] Should this be added to CI? (Answer: Yes) --- ## Changelog - Added the `bevy_reflect_compile_fail_tests` crate for testing compilation errors
# Objective There isn't really a way to test that code using bevy_reflect compiles or doesn't compile for certain scenarios. This would be especially useful for macro-centric PRs like bevyengine#6511 and bevyengine#6042. ## Solution Using `bevy_ecs_compile_fail_tests` as reference, added the `bevy_reflect_compile_fail_tests` crate. Currently, this crate contains a very simple test case. This is so that we can get the basic foundation of this crate agreed upon and merged so that more tests can be added by other PRs. ### Open Questions - [x] Should this be added to CI? (Answer: Yes) --- ## Changelog - Added the `bevy_reflect_compile_fail_tests` crate for testing compilation errors
3462335
to
62a3bee
Compare
Example |
62a3bee
to
f3e8119
Compare
f3e8119
to
bca08b1
Compare
Flipped the order of the types when using reflect_remote with mismatched types. Before this was displaying "expected MyType, found TheirType" when it makes more sense to say "expected TheirType, found MyType".
Also hide it from docs (in the case of --document-private-items)
We need to provide a way for users to reference their wrapper type for use in #[reflect(remote = ...)] attributes
I like the last minute reflect remote value addition! Let's get this merged :) |
Head branch was pushed to by a user without write access
Head branch was pushed to by a user without write access
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#1674 if you'd like to help out. |
Objective
The goal with this PR is to allow the use of types that don't implement
Reflect
within the reflection API.Rust's orphan rule prevents implementing a trait on an external type when neither type nor trait are owned by the implementor. This means that if a crate,
cool_rust_lib
, defines a type,Foo
, then a user cannot use it with reflection. What this means is that we have to ignore it most of the time:Obviously, it's impossible to implement
Reflect
onFoo
. But does it have to be?Most of reflection doesn't deal with concrete types— it's almost all using
dyn Reflect
. And being very metadata-driven, it should theoretically be possible. I mean,serde
does it.Solution
Taking a page out of
serde
's book, this PR adds the ability to easily use "remote types" with reflection. In this context, a "remote type" is the external type for which we have no ability to implementReflect
.This adds the
#[reflect_remote(...)]
attribute macro, which is used to generate "remote type wrappers". All you have to do is define the wrapper exactly the same as the remote type's definition:The macro takes this user-defined item and transforms it into a newtype wrapper around the external type, marking it as
#[repr(transparent)]
. The fields/variants defined by the user are simply used to build out the reflection impls.Additionally, it generates an implementation of the new trait,
ReflectRemote
, which helps prevent accidental misuses of this API.Therefore, the output generated by the macro would look something like:
Internally, the reflection API will pass around the
FooWrapper
and transmute it where necessary. All we have to do is then tellReflect
to do that. So rather than ignoring the field, we tellReflect
to use our wrapper using the#[reflect(remote = ...)]
field attribute:Other Macros & Type Data
Because this macro consumes the defined item and generates a new one, we can't just put our macros anywhere. All macros that should be passed to the generated struct need to come below this macro. For example, to derive
Default
and register its associated type data:Generics
Generics are forwarded to the generated struct as well. They should also be defined in the same order:
Nesting
And, yes, you can nest remote types:
Assertions
This macro will also generate some compile-time assertions to ensure that the correct types are used. It's important we catch this early so users don't have to wait for something to panic. And it also helps keep our
unsafe
a little safer.For example, a wrapper definition that does not match its corresponding remote type will result in an error:
Generated Assertion
Additionally, using the incorrect type in a
#[reflect(remote = ...)]
attribute should result in an error:Generated Assertion
Discussion
There are a couple points that I think still need discussion or validation.
1.
Any
shenanigansIf we wanted to downcast our remote type from adyn Reflect
, we'd have to first downcast to the wrapper then extract the inner type. This PR has a commit that addresses this by making all theReflect::*any
methods return the inner type rather than the wrapper type. This allows us to downcast directly to our remote type.However, I'm not sure if this is something we want to do. For unknowing users, it could be confusing and seemingly inconsistent. Is it worth keeping? Or should this behavior be removed?I think this should be fine. The remote wrapper is an implementation detail and users should not need to downcast to the wrapper type. Feel free to let me know if there are other opinions on this though!
2. Implementing
Deref/DerefMut
andFrom
We don't currently do this, but should we implement other traits on the generated transparent struct? We could implementAs mentioned in the comments, we probably don't need to do this. Again, the remote wrapper is an implementation detail, and should generally not be used directly.Deref
/DerefMut
to easily access the inner type. And we could implementFrom
for easier conversion between the two types (e.g.T: Into<Foo>
).3.
Should we define a getter/setter field attribute in this PR as well or leave it for a future one?I think this should be saved for a future PR4. Any foreseeable issues with this implementation?
Alternatives
One alternative to defining our own
ReflectRemote
would be to use bytemuck'sTransparentWrapper
(as suggested by @danielhenrymantilla).This is definitely a viable option, as
ReflectRemote
is pretty much the same thing asTransparentWrapper
. However, the cost would be bringing in a new crate— though, it is already in use in a few other sub-crates like bevy_render.I think we're okay just defining
ReflectRemote
ourselves, but we can go the bytemuck route if we'd prefer offloading that work to another crate.Changelog
#[reflect_remote(...)]
attribute macro to allowReflect
to be used on remote typesReflectRemote
trait for ensuring proper remote wrapper usage