-
-
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
Extend cloning functionality and add convenience methods to EntityWorldMut
and EntityCommands
#16826
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 if
s 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 EntityCommands
EntityWorldMut
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. |
} | ||
.clone_entity(world); | ||
|
||
world.flush_commands(); | ||
} | ||
|
||
/// 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_components
with_default_required_components
replace_required_components
replace_required_components_with_default
without_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
.
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.
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 :)
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
EntityCloneBuilder
now automatically includes required components alongside any component added to or removed from the component filter.Added the following methods to
EntityCloneBuilder
:move_components
move_components(&mut self, enable: bool)
without_required_components
without_required_components(&mut self, builder: impl FnOnce(&mut EntityCloneBuilder))
Added the following methods to
EntityWorldMut
andEntityCommands
:clone_with
clone_with(&mut self, target: Entity, config: impl FnOnce(&mut EntityCloneBuilder))
EntityCloneBuilder
.clone_components
clone_components::<B: Bundle>(&mut self, target: Entity)
move_components
move_components::<B: Bundle>(&mut self, target: Entity)
Also added
clone_and_spawn
andclone_and_spawn_with
toEntityWorldMut
(EntityCommands
already had them).Showcase