-
-
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
Remove Event: Component
trait bound
#17333
Remove Event: Component
trait bound
#17333
Conversation
/// | ||
/// For read-only access, see [`Components::component_id`]. | ||
pub fn generate_component_id<T: ?Sized + 'static>(&mut self) -> ComponentId { | ||
if let Some(component_id) = self.indices.get(&TypeId::of::<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.
Is .entry
faster in general? I find this style a bit clearer.
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.
It would save the second lookup, but that lookup only happens once per type anyways.
let mut components = Vec::new(); | ||
B::component_ids(&mut world.components, &mut world.storages, &mut |id| { | ||
components.push(id); | ||
}); | ||
let mut descriptor = ObserverDescriptor { | ||
events: vec![event_type], | ||
events: vec![event_id], |
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.
Renamed for consistency.
/// Each event type has a unique [`ComponentId`] associated with it, which is used to store the event in the world. | ||
/// This can be obtained using [`World::generate_component_id<Self>`]. | ||
/// | ||
/// Each individual event sent has an [`EventId`] associated with it, which can be used to trace the flow of an event. |
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.
Bonus docs!
if let Some(component_id) = self.indices.get(&TypeId::of::<T>()) { | ||
component_id.clone() | ||
} else { | ||
let id = ComponentId(self.components.len()); |
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.
If we aren't registering a component info won't this return the same ID multiple times since the length of self.components does not increase?
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 like the general idea.
However, as james has pointed out this doesn't work because we'd return the same id multiple times. On top of that, if the user calls generate_component_id::<Foo>
before the first time register_component::<Foo>
is called, the initialization of the component descriptor will be skipped.
I see two solutions:
- Add a
HasComponentId
trait that can optionally return a component descriptor (no specialization sadge) and removegenerate_component_id
; keep track of maximum free id and allocate ids of non-components from high to low so the component descriptor array is kept dense (I'd favor this version) - or have component descriptors be stored in a sparse array, with
register_component
doing the initialization even if the id is already allocated (but there isn't an entry in the array)
Code compiles, and alien_cake_addict works. If something goes wrong here everything will break.
The reason alien_cake_addict still works is that it doesn't use observers, and even if it did World::bootstrap
still uses register_component
for events.
/// | ||
/// For read-only access, see [`Components::component_id`]. | ||
pub fn generate_component_id<T: ?Sized + 'static>(&mut self) -> ComponentId { | ||
if let Some(component_id) = self.indices.get(&TypeId::of::<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.
It would save the second lookup, but that lookup only happens once per type anyways.
It would be a little unfortunate to be unable to record any type information for these. A third solution is to do what |
I think I'm going to go with solution 3 here: it's the simplest incremental change. I really don't like that you can get duplicate IDs for different types, but that's a pre-existing problem. |
How so? |
The resources and component indexes are incremented independently now. |
Closing in favor of #17380, which is even simpler. |
…Component (#17380) # Objective As raised in #17317, the `Event: Component` trait bound is confusing to users. In general, a type `E` (like `AppExit`) which implements `Event` should not: - be stored as a component on an entity - be a valid option for `Query<&AppExit>` - require the storage type and other component metadata to be specified Events are not components (even if they one day use some of the same internal mechanisms), and this trait bound is confusing to users. We're also automatically generating `Component` impls with our derive macro, which should be avoided when possible to improve explicitness and avoid conflicts with user impls. Closes #17317, closes #17333 ## Solution - We only care that each unique event type gets a unique `ComponentId` - dynamic events need their own tools for getting identifiers anyways - This avoids complicating the internals of `ComponentId` generation. - Clearly document why this cludge-y solution exists. In the medium term, I think that either a) properly generalizing `ComponentId` (and moving it into `bevy_reflect?) or b) using a new-typed `Entity` as the key for events is more correct. This change is stupid simple though, and removes the offending trait bound in a way that doesn't introduce complex tech debt and does not risk changes to the internals. This change does not: - restrict our ability to implement dynamic buffered events (the main improvement over #17317) - there's still a fair bit of work to do, but this is a step in the right direction - limit our ability to store event metadata on entities in the future - make it harder for users to work with types that are both events and components (just add the derive / trait bound) ## Migration Guide The `Event` trait no longer requires the `Component` trait. If you were relying on this behavior, change your trait bounds from `Event` to `Event + Component`. If you also want your `Event` type to implement `Component`, add a derive. --------- Co-authored-by: Chris Russell <[email protected]>
Objective
As raised in #17317, the
Event: Component
trait bound is confusing to users.In general, a type
E
(likeAppExit
) which implementsEvent
should not:Query<&AppExit>
Events are not components (even if they one day use some of the same internal mechanisms), and this trait bound is confusing to users.
We're also automatically generating
Component
impls with our derive macro, which should be avoided when possible to improve explicitness and avoid conflicts with user impls.P.S. This prompted a larger hackmd design from me about the core principles of "make everything entities and components".
Context
Dynamic events (which observers support IIRC) require a mapping from
TypeId
to "some arbitrary stable ID". We decided to useComponentId
there when implementing this, as the code / concept already exists. The easiest way to get the observers API compiling as a result was to just add aComponent
trait bound.Solution
ComponentId
for any Rust types.ComponentId
for events (inside of the observers API) using this new method.Component
trait bound onEvent
toSend + Sync + 'static
.Unlike I initially thought, we cannot (and should not) relax the
Component
trait bound onregister_component
, as other work is done to make sure the component is properly set up (like registering hooks).This change does not:
Future work
generate_component_id
inside of our existing methods to improve clarity and reduce duplication.ComponentId
to something that isn't component-specific.Testing
Code compiles, and
alien_cake_addict
works. If something goes wrong here everything will break.Migration Guide
The
Event
trait no longer requires theComponent
trait. If you were relying on this behavior, change your trait bounds fromEvent
toEvent + Component
. If you also want yourEvent
type to implementComponent
, add a derive.