-
-
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
Automatic registration of ECS types #12332
base: main
Are you sure you want to change the base?
Conversation
Definitely valuable, definitely cursed. I would really like to find a solid way to make this work. |
Found another very big blocker: generic types do not play well with this without generic type bounds. Found this out with |
Added in the generics exclusion for now and it seems to be building just fine sans the still broken tests from the |
Maybe add some autoderef magic to make it even more cursed 😄 |
I don't think that will work in a generic context like we have in |
Fixed the trivial test errors, but it seems like this potentially makes all of the scenes APIs, dependent on ReflectResource and ReflectComponent, cause the RwLock on the TypeRegistryArc to become reentrant and may risk deadlocking. EDIT: fixed this by switching to a read-lock on failing to acquire a write-lock, and then returning if the type is already registered, panicking if and only if it needs to register but can't. |
ac92be3
to
3013029
Compare
//! | ||
//! * Automatic registration is only supported via the derive macros. Manual implementations of | ||
//! `Component`, `Resource`, or `Reflect` must be manually registered. | ||
//! * The associated ECS trait must be reflected (via `reflect(Component)` or `reflect(Resource)`). |
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.
This requirement is so that we don't automatically register to the World non-ECS-related Reflect types?
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.
This was requested so that users could opt out of this, such as avoiding serialization in scenes. I personally think we should find other ways of opting out of each use case and just move towards registration by default.
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.
We could also consider an opt-out attribute. There could be other reasons to exclude a type from the registry besides opting out of scenes. For larger projects, you may only want to register types you know you’ll need (to save on memory and reduce startup times). You might also be using a third party crate that uses the registry for things you want to opt out of as well. Or maybe you don't want to "leak" type information to downstream crates.
Whatever the reason, we need a way to opt out of automatic registration since registration is intentionally a one-way process: you can't undo or remove a registration. This is so that guarantees about the registered type are never broken without the user being aware of it (unless, of course, a mischievous plugin goes nuclear and replaces the entire AppTypeRegistry
resource but that's a whole separate issue haha).
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.
+1 for the opt-out attribute. It's more explicit, and allows this to work even when the user forgets to reflect Component
/Resource
I think the questions are:
Things like this seem like serious footguns: #[derive(Component, Reflect)]
#[reflect(Component)]
struct Foo;
app.add_systems(Update, foo);
fn foo(foos: Query<&Foo>) {} During development, developer comments out system. Foo scenes stop working / editor loses track of Foo // app.add_systems(Update, foo); During development, developer changes query to something else, Foo scenes stop working / editor loses track of Foo fn foo(bar: Query<&Bar>) {} Plugin registers a system only if a setting is enabled. When setting is disabled, Foo scenes stop working / editor loses track of Foo if self.should_foo {
app.add_systems(Update, foo)
} To protect against these scenarios, I think as a best practice we would still encourage people to manually register types. It definitely seems prudent to do that for built-in Bevy types. If its not something we're comfortable using in upstream plugins, can we really give it to users? Which "recommended workflow" is better?
(1) Is predictable and trains people to think about registration at the cost of boilerplate and one-time friction when forgotten. |
Objective
Registering types is error prone. Forgetting to register is a common error when working with scenes. #4154 is proof that it's a huge UX footgun. It'd be nice if ECS types (Components, Resources, Bundles) automatically registered the types upon use.
Solution
Make the
Component
andResource
derive macro check for thereflect
attribute. Add a hidden function to the traits that has an empty default impl that generates code to register the type if the type has areflect
attribute. For any reasonable type that implementsReflect
via the derive macro, thereflect
attribute should be present to allow reflecting the ECS trait (i.e.reflect(Component)
.Use the
TypeRegistryArc
embedded within the World to initializeAppTypeRegistry
, which now implementsFromWorld
instead ofDefault
.Gate all of this behind the bevy_ecs feature for bevy_reflect.
There should be no performance impact since these functions are only called when initializing new Components.
TODO: Do the same with bundles.
This PR is all sorts of cursed. There's a huge number of caveats here:
Major caveats (no easy mitigation or solution currently):
Plugin::finish
), which may create some obtuse timing errors. This poses a major UX issue in working with a hypothetical editor where a developer declares a new component, but it doesn't show up until it's used by a registered system or manually registered.Minor caveats (not significant, already mitigated, and/or has potential solution):
reflect
attribute with aComponent
derived type without implementing Reflect a compile error with no good way to resolve it. This is largely mitigated by requiringreflect(Component)
, which is not really going to be valid without theReflect
derive macro.derive(Reflect)
but rather the attribute used by theReflect
derive macro. This is largely mitigated by requiringreflect(Component)
, which is not really going to be valid without theReflect
derive macro.This PR, however, allows us to almost transparently handle full top-down type registration for scenes in the steady state without manual type registrations, now that #4154 has been merged, so the UX wins here may be worth the caveats, at least until specialization or some
ctor
equivalent for global link-time registration becomes available.Changelog
Changed: Components and Resources that implement their trait and Reflect via the derive macros, and reflect their corresponding trait, will be automatically registered in the
World
'sAppTypeRegistry
resource upon first use (i..e spawning, inserting a resource, adding a system that uses it).Migration Guide
TODO