Skip to content

Commit

Permalink
Extend cloning functionality and add convenience methods to `EntityWo…
Browse files Browse the repository at this point in the history
…rldMut` and `EntityCommands` (#16826)

## 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/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)));
```
  • Loading branch information
JaySpruce authored Dec 16, 2024
1 parent 74e793d commit 5a94beb
Show file tree
Hide file tree
Showing 3 changed files with 489 additions and 70 deletions.
191 changes: 133 additions & 58 deletions crates/bevy_ecs/src/entity/clone_entities.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub struct EntityCloner {
filter_allows_components: bool,
filter: Arc<HashSet<ComponentId>>,
clone_handlers_overrides: Arc<HashMap<ComponentId, ComponentCloneHandler>>,
move_components: bool,
}

impl EntityCloner {
Expand All @@ -35,17 +36,21 @@ impl EntityCloner {
.filter(|id| self.is_cloning_allowed(id)),
);

for component in components {
for component in &components {
let global_handlers = world.components().get_component_clone_handlers();
let handler = match self.clone_handlers_overrides.get(&component) {
None => global_handlers.get_handler(component),
let handler = match self.clone_handlers_overrides.get(component) {
None => global_handlers.get_handler(*component),
Some(ComponentCloneHandler::Default) => global_handlers.get_default_handler(),
Some(ComponentCloneHandler::Ignore) => component_clone_ignore,
Some(ComponentCloneHandler::Custom(handler)) => *handler,
};
self.component_id = Some(component);
self.component_id = Some(*component);
(handler)(&mut world.into(), self);
}

if self.move_components {
world.entity_mut(self.source).remove_by_ids(&components);
}
}

fn is_cloning_allowed(&self, component: &ComponentId) -> bool {
Expand Down Expand Up @@ -145,6 +150,8 @@ pub struct EntityCloneBuilder<'w> {
filter_allows_components: bool,
filter: HashSet<ComponentId>,
clone_handlers_overrides: HashMap<ComponentId, ComponentCloneHandler>,
attach_required_components: bool,
move_components: bool,
}

impl<'w> EntityCloneBuilder<'w> {
Expand All @@ -155,6 +162,8 @@ impl<'w> EntityCloneBuilder<'w> {
filter_allows_components: false,
filter: Default::default(),
clone_handlers_overrides: Default::default(),
attach_required_components: true,
move_components: false,
}
}

Expand All @@ -165,6 +174,7 @@ impl<'w> EntityCloneBuilder<'w> {
filter_allows_components,
filter,
clone_handlers_overrides,
move_components,
..
} = self;

Expand All @@ -175,29 +185,49 @@ impl<'w> EntityCloneBuilder<'w> {
filter_allows_components,
filter: Arc::new(filter),
clone_handlers_overrides: Arc::new(clone_handlers_overrides),
move_components,
}
.clone_entity(world);

world.flush_commands();
}

/// By default, any components allowed/denied through the filter will automatically
/// allow/deny all of their required components.
///
/// This method allows for a scoped mode where any changes to the filter
/// will not involve required components.
pub fn without_required_components(
&mut self,
builder: impl FnOnce(&mut EntityCloneBuilder) + Send + Sync + 'static,
) -> &mut Self {
self.attach_required_components = false;
builder(self);
self.attach_required_components = true;
self
}

/// Sets whether the cloner should remove any components that were cloned,
/// effectively moving them from the source entity to the target.
///
/// This is disabled by default.
///
/// The setting only applies to components that are allowed through the filter
/// at the time [`EntityCloneBuilder::clone_entity`] is called.
pub fn move_components(&mut self, enable: bool) -> &mut Self {
self.move_components = enable;
self
}

/// Adds all components of the bundle to the list of components to clone.
///
/// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call
/// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods.
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);
}
});
} else {
T::get_component_ids(self.world.components(), &mut |id| {
if let Some(id) = id {
self.filter.remove(&id);
}
});
let bundle = self.world.register_bundle::<T>();
let ids = bundle.explicit_components().to_owned();
for id in ids {
self.filter_allow(id);
}
self
}
Expand All @@ -207,12 +237,8 @@ impl<'w> EntityCloneBuilder<'w> {
/// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call
/// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods.
pub fn allow_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
if self.filter_allows_components {
self.filter.extend(ids);
} else {
ids.into_iter().for_each(|id| {
self.filter.remove(&id);
});
for id in ids {
self.filter_allow(id);
}
self
}
Expand All @@ -222,15 +248,10 @@ impl<'w> EntityCloneBuilder<'w> {
/// Note that all components are allowed by default, to clone only explicitly allowed components make sure to call
/// [`deny_all`](`Self::deny_all`) before calling any of the `allow` methods.
pub fn allow_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
let ids = ids
.into_iter()
.filter_map(|id| self.world.components().get_id(id));
if self.filter_allows_components {
self.filter.extend(ids);
} else {
ids.into_iter().for_each(|id| {
self.filter.remove(&id);
});
for type_id in ids {
if let Some(id) = self.world.components().get_id(type_id) {
self.filter_allow(id);
}
}
self
}
Expand All @@ -244,45 +265,28 @@ impl<'w> EntityCloneBuilder<'w> {

/// Disallows all components of the bundle from being cloned.
pub fn deny<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.remove(&id);
}
});
} else {
T::get_component_ids(self.world.components(), &mut |id| {
if let Some(id) = id {
self.filter.insert(id);
}
});
let bundle = self.world.register_bundle::<T>();
let ids = bundle.explicit_components().to_owned();
for id in ids {
self.filter_deny(id);
}
self
}

/// Extends the list of components that shouldn't be cloned.
pub fn deny_by_ids(&mut self, ids: impl IntoIterator<Item = ComponentId>) -> &mut Self {
if self.filter_allows_components {
ids.into_iter().for_each(|id| {
self.filter.remove(&id);
});
} else {
self.filter.extend(ids);
for id in ids {
self.filter_deny(id);
}
self
}

/// Extends the list of components that shouldn't be cloned by type ids.
pub fn deny_by_type_ids(&mut self, ids: impl IntoIterator<Item = TypeId>) -> &mut Self {
let ids = ids
.into_iter()
.filter_map(|id| self.world.components().get_id(id));
if self.filter_allows_components {
ids.into_iter().for_each(|id| {
self.filter.remove(&id);
});
} else {
self.filter.extend(ids);
for type_id in ids {
if let Some(id) = self.world.components().get_id(type_id) {
self.filter_deny(id);
}
}
self
}
Expand Down Expand Up @@ -315,11 +319,52 @@ impl<'w> EntityCloneBuilder<'w> {
}
self
}

/// Helper function that allows a component through the filter.
fn filter_allow(&mut self, id: ComponentId) {
if self.filter_allows_components {
self.filter.insert(id);
} else {
self.filter.remove(&id);
}
if self.attach_required_components {
if let Some(info) = self.world.components().get_info(id) {
for required_id in info.required_components().iter_ids() {
if self.filter_allows_components {
self.filter.insert(required_id);
} else {
self.filter.remove(&required_id);
}
}
}
}
}

/// Helper function that disallows a component through the filter.
fn filter_deny(&mut self, id: ComponentId) {
if self.filter_allows_components {
self.filter.remove(&id);
} else {
self.filter.insert(id);
}
if self.attach_required_components {
if let Some(info) = self.world.components().get_info(id) {
for required_id in info.required_components().iter_ids() {
if self.filter_allows_components {
self.filter.remove(&required_id);
} else {
self.filter.insert(required_id);
}
}
}
}
}
}

#[cfg(test)]
mod tests {
use crate::{self as bevy_ecs, component::Component, entity::EntityCloneBuilder, world::World};
use bevy_ecs_macros::require;

#[cfg(feature = "bevy_reflect")]
#[test]
Expand Down Expand Up @@ -520,4 +565,34 @@ mod tests {
assert!(world.get::<B>(e_clone).is_none());
assert!(world.get::<C>(e_clone).is_none());
}

#[test]
fn clone_entity_with_required_components() {
#[derive(Component, Clone, PartialEq, Debug)]
#[require(B)]
struct A;

#[derive(Component, Clone, PartialEq, Debug, Default)]
#[require(C(|| C(5)))]
struct B;

#[derive(Component, Clone, PartialEq, Debug)]
struct C(u32);

let mut world = World::default();

let e = world.spawn(A).id();
let e_clone = world.spawn_empty().id();

let mut builder = EntityCloneBuilder::new(&mut world);
builder.deny_all();
builder.without_required_components(|builder| {
builder.allow::<B>();
});
builder.clone_entity(e, e_clone);

assert_eq!(world.entity(e_clone).get::<A>(), None);
assert_eq!(world.entity(e_clone).get::<B>(), Some(&B));
assert_eq!(world.entity(e_clone).get::<C>(), Some(&C(5)));
}
}
Loading

0 comments on commit 5a94beb

Please sign in to comment.