-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Extend cloning functionality and add convenience methods to EntityWorldMut and EntityCommands
#16826
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
Conversation
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 would suggest moving most of the functionality to EntityCloneBuilder which should allow to use it in clone_and_spawn methods as well. Also I'm a bit worried about the combinatorial explosion of methods - that is why I made EntityCloneBuilder in the first place. ( Now It's my time to nitpick :P )
| pub fn clone_components_with_requires<B: Bundle>(&mut self, target: Entity) -> &mut Self { | ||
| self.assert_not_despawned(); | ||
|
|
||
| let storages = &mut self.world.storages; | ||
| let components = &mut self.world.components; | ||
| let bundles = &mut self.world.bundles; | ||
|
|
||
| let bundle_id = bundles.register_contributed_bundle_info::<B>(components, storages); | ||
| // SAFETY: the `BundleInfo` for this `BundleId` is initialized above | ||
| let bundle_info = unsafe { bundles.get_unchecked(bundle_id) }; | ||
| let component_ids = bundle_info.contributed_components().to_owned(); | ||
|
|
||
| let mut builder = EntityCloneBuilder::new(self.world); | ||
| builder.deny_all().allow_by_ids(component_ids); | ||
| builder.clone_entity(self.entity, target); | ||
|
|
||
| self.world.flush(); | ||
| self.update_location(); | ||
| self | ||
| } |
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'm pretty sure allowing and denying components with their required components can be done as a mode for EntityCloneBuilder, which should be more flexible:
builder.with_required_components(|builder| {
builder
.allow::<B>() // Calling with_required_components can set an internal flag which would
.allow_by_ids(ids); // make all allow* and deny* methods also include contributed_components
});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.
Something like this?
Code
pub fn with_required_components(
&mut self,
f: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static,
) -> &mut Self {
self.attach_required_components = true;
f(self);
self.attach_required_components = false;
self
}
pub fn allow<T: Bundle>(&mut self) -> &mut Self {
if self.filter_allows_components {
T::get_component_ids(self.world.components(), &mut |id| {
if let Some(id) = id {
self.filter.insert(id);
if self.attach_required_components {
if let Some(info) = self.world.components().get_info(id) {
for required in info.required_components().iter_ids() {
self.filter.insert(required);
}
}
}
}
});
} else {
T::get_component_ids(self.world.components(), &mut |id| {
if let Some(id) = id {
self.filter.remove(&id);
if self.attach_required_components {
if let Some(info) = self.world.components().get_info(id) {
for required in info.required_components().iter_ids() {
self.filter.remove(&required);
}
}
}
}
});
}
self
}
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 think this code can actually be deduplicated a bit if we replace if self.filter_allows_components with something like this:
fn apply_filter(
&mut self,
id: ComponentId,
) {
if self.filter_allows_components {
self.filter.insert(id);
} else {
self.filter.remove(&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.
I can't get around the borrow checker to get that to work, but moving the ifs around is still an improvement
| pub fn move_components<B: Bundle>(&mut self, target: Entity) -> &mut Self { | ||
| self.assert_not_despawned(); | ||
|
|
||
| let mut builder = EntityCloneBuilder::new(self.world); | ||
| builder.deny_all().allow::<B>(); | ||
| builder.clone_entity(self.entity, target); | ||
|
|
||
| let storages = &mut self.world.storages; | ||
| let components = &mut self.world.components; | ||
| let bundles = &mut self.world.bundles; | ||
| let bundle_id = bundles.register_info::<B>(components, storages); | ||
|
|
||
| // SAFETY: the `BundleInfo` for this `BundleId` is initialized above | ||
| unsafe { self.remove_bundle(bundle_id) }; | ||
|
|
||
| self.world.flush(); | ||
| self.update_location(); | ||
| self | ||
| } |
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.
Moving components can also be a configuration option for EntityCloneBuilder that would call remove_bundle as a final step in clone_entity. Theoretically, it would also allow us to add an optimization in the future that would allow EntityCloner to move components that don't implement Clone or Reflect.
| /// | ||
| /// - If this entity has been despawned while this `EntityWorldMut` is still alive. | ||
| /// - If the target entity does not exist. | ||
| pub fn move_components_with_requires<B: Bundle>(&mut self, target: Entity) -> &mut Self { |
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.
Maybe we can avoid combinatorial explosion of methods by just having move_components and move_components_with similar to spawn_and_clone? Although I'm not sure if configuring through EntityCloneBuilder is a good idea - it would allow to set cloning hierarchy which would not make much sense in the context of cloning components from one entity to the next. Maybe there should be a ComponentCloneBuilder or something similar that would re-expose only the methods that should be allowed?
|
All true, I think I'll cut it down to these:
I'll add the required components stuff to the builder, and tinker with the moving functionality too |
|
I really like this direction, and @eugineerd's feedback is excellent. Ping me when you need a review please :) |
clone_components and move_components (and variants) to EntityWorldMut and EntityCommandsEntityWorldMut and EntityCommands
|
I added the helper functions for the filter, so the allow/deny methods are slimmer now. Required components work as discussed, the Moving components is just a bool in This is the full syntax: Also added I think we're good to go for round 2, @alice-i-cecile & @eugineerd. |
|
|
||
| /// Allows for a scoped mode where any changes to the filter that allow/deny components | ||
| /// will also allow/deny those components' required components, recursively. | ||
| pub fn with_required_components( |
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.
Very cool. @cart is almost certainly going to want to want this on by default, possibly without the ability to disable it. The vision is to treat these as "one indivisible object" wherever possible, at least for "required" components :)
I agree with that philosophy, and I think the toggle should basically be "replace required components with their default values". The default value should be "copy all required components that you can".
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.
A bit off-topic for this PR, but I wonder if DynamicSceneBuilder should also extract required components in that case?
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.
That's essentially what the toggle is, since components are ultimately inserted with normal insert methods. I can switch the default though.
Here's a couple colors for the bikeshed:
default_required_componentswith_default_required_componentsreplace_required_componentsreplace_required_components_with_defaultwithout_required_components
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.
Yeah, we should probably be unifying the mechanisms between this and the scenes stuff. Another PR / issue though! As for the bikeshed, go with the long and clear one please: this is quite niche :)
| self | ||
| } | ||
|
|
||
| /// The default setting for the cloner. The source entity will keep |
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.
The bit about this being the default should be in the second line, not the first.
| /// | ||
| /// # Panics | ||
| /// | ||
| /// The command will panic when applied if either of the entities do not exist. |
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.
Gah more panics :( Not your problem: this needs a holistic solution.
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 do have another PR for that :P (not holistic, just commands). Not sure how it'll interact with work in the fallible systems area though
| /// [`Clone`] or [`Reflect`](bevy_reflect::Reflect). | ||
| /// | ||
| /// Configure through [`EntityCloneBuilder`] as follows: | ||
| /// ```ignore |
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.
Don't ignore this: make it work (possibly hiding lines for importing dependencies / boilerplate). Ignored doc tests are extremely brittle.
| /// | ||
| /// This only applies to components that are allowed through the filter | ||
| /// at the time [`EntityCloneBuilder::clone_entity`] is called. | ||
| pub fn enable_move_on_clone(&mut self) -> &mut Self { |
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.
Bikeshed: I think these methods might be clearer if they were move_components and keep_components. The enable/disable terminology was confusing to me at first.
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 think this should just be one method: move_components that takes a bool. That way it would be in line with the other EntityCloneBuilder options (like add_observers). It was designed to look somewhat similar to std::fs::OpenOptions.
eugineerd
left a comment
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.
Looks good to me! I like the addition of various convenience methods and move component functionality is pretty useful.
I have also prepared an optimization based on code from #16717 for move_components that would allow us to move components that don't implement Reflect or Clone after both PRs are merged.
alice-i-cecile
left a comment
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'm happy with the state of this now; let's merge and refine in follow-up :)
…rldMut` and `EntityCommands` (bevyengine#16826) ## Objective Thanks to @eugineerd's work on entity cloning (bevyengine#16132), we now have a robust way to copy components between entities. We can extend this to implement some useful functionality that would have been more complicated before. Closes bevyengine#15350. ## Solution `EntityCloneBuilder` now automatically includes required components alongside any component added/removed from the component filter. Added the following methods to `EntityCloneBuilder`: - `move_components` - `without_required_components` Added the following methods to `EntityWorldMut` and `EntityCommands`: - `clone_with` - `clone_components` - `move_components` Also added `clone_and_spawn` and `clone_and_spawn_with` to `EntityWorldMut` (`EntityCommands` already had them). ## Showcase ``` assert_eq!(world.entity(entity_a).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_b).get::<B>(), None); world.entity_mut(entity_a).clone_components::<B>(entity_b); assert_eq!(world.entity(entity_a).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_b).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_a).get::<C>(), Some(&C(5))); assert_eq!(world.entity(entity_b).get::<C>(), None); world.entity_mut(entity_a).move_components::<C>(entity_b); assert_eq!(world.entity(entity_a).get::<C>(), None); assert_eq!(world.entity(entity_b).get::<C>(), Some(&C(5))); ```
…rldMut` and `EntityCommands` (bevyengine#16826) ## Objective Thanks to @eugineerd's work on entity cloning (bevyengine#16132), we now have a robust way to copy components between entities. We can extend this to implement some useful functionality that would have been more complicated before. Closes bevyengine#15350. ## Solution `EntityCloneBuilder` now automatically includes required components alongside any component added/removed from the component filter. Added the following methods to `EntityCloneBuilder`: - `move_components` - `without_required_components` Added the following methods to `EntityWorldMut` and `EntityCommands`: - `clone_with` - `clone_components` - `move_components` Also added `clone_and_spawn` and `clone_and_spawn_with` to `EntityWorldMut` (`EntityCommands` already had them). ## Showcase ``` assert_eq!(world.entity(entity_a).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_b).get::<B>(), None); world.entity_mut(entity_a).clone_components::<B>(entity_b); assert_eq!(world.entity(entity_a).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_b).get::<B>(), Some(&B)); assert_eq!(world.entity(entity_a).get::<C>(), Some(&C(5))); assert_eq!(world.entity(entity_b).get::<C>(), None); world.entity_mut(entity_a).move_components::<C>(entity_b); assert_eq!(world.entity(entity_a).get::<C>(), None); assert_eq!(world.entity(entity_b).get::<C>(), Some(&C(5))); ```
|
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#1972 if you'd like to help out. |
Objective
Thanks to @eugineerd's work on entity cloning (#16132), we now have a robust way to copy components between entities. We can extend this to implement some useful functionality that would have been more complicated before.
Closes #15350.
Solution
EntityCloneBuildernow automatically includes required components alongside any component added to or removed from the component filter.Added the following methods to
EntityCloneBuilder:move_componentsmove_components(&mut self, enable: bool)without_required_componentswithout_required_components(&mut self, builder: impl FnOnce(&mut EntityCloneBuilder))Added the following methods to
EntityWorldMutandEntityCommands:clone_withclone_with(&mut self, target: Entity, config: impl FnOnce(&mut EntityCloneBuilder))EntityCloneBuilder.clone_componentsclone_components::<B: Bundle>(&mut self, target: Entity)move_componentsmove_components::<B: Bundle>(&mut self, target: Entity)Also added
clone_and_spawnandclone_and_spawn_withtoEntityWorldMut(EntityCommandsalready had them).Showcase