-
-
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
Entity cloning #16132
Entity cloning #16132
Conversation
I have wanted this feature for years. Thank you so much for taking a crack at it! I don't have time to thoroughly review this right now, but please pester me during 0.16 to make sure this gets 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.
This seems really useful! I have some nits and suggestions, but nothing that should block it.
@@ -1834,3 +1926,106 @@ impl RequiredComponents { | |||
} | |||
} | |||
} | |||
|
|||
/// Component [clone handler function](ComponentCloneFn) implemented using the [`Clone`] trait. | |||
/// Can be [set](ComponentCloneHandlers::set_component_handler) as clone handler for the specific component it is implemented for. |
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.
Would it make sense to have a helper method that calls set_component_handler(id, component_clone_via_clone::<C>)
with the correct id
? You couldn't put it on ComponentCloneHandlers
since you can't get the component_id
from that, but you could put it on World
or 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.
I'm not sure how useful that will be. I though that most users would not need to add component_clone_via_clone
handlers at runtime, instead they would manually impl Clone
for a component or set a handler in get_component_clone_handler
. Is it for components with conditional Clone
implementation with generics?
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, I was thinking about the conditional Clone
implementations. One of the reasons it seems perfectly fine to use default cloning in that case is that anyone who really needs component_clone_via_clone
can just register it for the concrete component types! But I agree that's likely to be rare, so it's probably not worth adding extra methods for it unless we see folks doing that in the wild.
I did some basic profiling to see how cloning compares to just spawning an entity and performance is not that great (all time is relative to total runtime, as reported by
I think optimizations for |
Oh, sorry, I bet this was due to my suggestion to use
I'm not a maintainer, but I vote for merging this first and doing performance improvements as a follow-up, especially if you don't expect the perf improvements to change the public API. That lets it get used sooner in places where the current perf is already good enough! |
I think from the current public API only custom clone handlers ( Thinking about it a bit more though, maybe the current flexible API can co-exist with the new more optimized but limited API that will be implemented later. |
…ty cloning implementation later
Made all clone handlers use |
Really impressive work: this is well-documented and engineered, and strikes a great balance between sensible defaults and the ability to configure more complex use cases. Merging! We can optimize this in follow-up. |
## Objective I was resolving a conflict between #16132 and my PR #15929 and thought the `clone_entity` commands made more sense in `EntityCommands`. ## Solution Moved `Commands::clone_entity` to `EntityCommands::clone`, moved `Commands::clone_entity_with` to `EntityCommands::clone_with`. ## Testing Ran the two tests that used the old methods. ## Showcase ``` // Create a new entity and keep its EntityCommands. let mut entity = commands.spawn((ComponentA(10), ComponentB(20))); // Create a clone of the first entity let mut entity_clone = entity.clone(); ``` The only potential downside is that the method name is now the same as the one from the `Clone` trait. `EntityCommands` doesn't implement `Clone` though, so there's no actual conflict. Maybe I'm biased because this'll work better with my PR, but I think the UX is nicer regardless.
…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))); ```
# Objective #16132 introduced entity cloning functionality, and while it works and is useful, it can be made faster. This is the promised follow-up to improve performance. ## Solution **PREFACE**: This is my first time writing `unsafe` in rust and I have only vague idea about what I'm doing. I would encourage reviewers to scrutinize `unsafe` parts in particular. The solution is to clone component data to an intermediate buffer and use `EntityWorldMut::insert_by_ids` to insert components without additional archetype moves. To facilitate this, `EntityCloner::clone_entity` now reads all components of the source entity and provides clone handlers with the ability to read component data straight from component storage using `read_source_component` and write to an intermediate buffer using `write_target_component`. `ComponentId` is used to check that requested type corresponds to the type available on source entity. Reflect-based handler is a little trickier to pull of: we only have `&dyn Reflect` and no direct access to the underlying data. `ReflectFromPtr` can be used to get `&dyn Reflect` from concrete component data, but to write it we need to create a clone of the underlying data using `Reflect`. For this reason only components that have `ReflectDefault` or `ReflectFromReflect` or `ReflectFromWorld` can be cloned, all other components will be skipped. The good news is that this is actually only a temporary limitation: once #13432 lands we will be able to clone component without requiring one of these `type data`s. This PR also introduces `entity_cloning` benchmark to better compare changes between the PR and main, you can see the results in the **showcase** section. ## Testing - All previous tests passing - Added test for fast reflect clone path (temporary, will be removed after reflection-based cloning lands) - Ran miri ## Showcase Here's a table demonstrating the improvement: | **benchmark** | **main, avg** | **PR, avg** | **change, avg** | | ----------------------- | ------------- | ----------- | --------------- | | many components reflect | 18.505 µs | 2.1351 µs | -89.095% | | hierarchy wide reflect* | 22.778 ms | 4.1875 ms | -81.616% | | hierarchy tall reflect* | 107.24 µs | 26.322 µs | -77.141% | | hierarchy many reflect | 78.533 ms | 9.7415 ms | -87.596% | | many components clone | 1.3633 µs | 758.17 ns | -45.937% | | hierarchy wide clone* | 2.7716 ms | 3.3411 ms | +20.546% | | hierarchy tall clone* | 17.646 µs | 20.190 µs | +17.379% | | hierarchy many clone | 5.8779 ms | 4.2650 ms | -27.439% | *: these benchmarks have entities with only 1 component ## Considerations Once #10154 is resolved a large part of the functionality in this PR will probably become obsolete. It might still be a little bit faster than using command batching, but the complexity might not be worth it. ## Migration Guide - `&EntityCloner` in component clone handlers is changed to `&mut ComponentCloneCtx` to better separate data. - Changed `EntityCloneHandler` from enum to struct and added convenience functions to add default clone and reflect handler more easily. --------- Co-authored-by: Alice Cecile <[email protected]> Co-authored-by: Chris Russell <[email protected]>
Objective
Fixes #1515
This PR implements a flexible entity cloning system. The primary use case for it is to clone dynamically-generated entities.
Example:
Solution
Commands
Add a
clone_entity
command to create a clone of an entity with all components that can be cloned. Components that can't be cloned will be ignored.If there is a need to configure the cloning process (like set to clone recursively), there is a second command:
Both of these commands return
EntityCommands
of the cloned entity, so the copy can be modified afterwards.Builder
All these commands use
EntityCloneBuilder
internally. If there is a need to clone an entity usingWorld
instead, it is also possible:Builder has methods to
allow
ordeny
certain components during cloning if required and can be extended by implementing traits on it. This PR includes twoEntityCloneBuilder
extensions:CloneEntityWithObserversExt
to configure adding cloned entity to observers of the original entity, andCloneEntityRecursiveExt
to configure cloning an entity recursively.Clone implementations
By default, all components that implement either
Clone
orReflect
will be cloned (withClone
-based implementation preferred in case component implements both).This can be overriden on a per-component basis:
ComponentCloneHandlers
Clone implementation specified in
get_component_clone_handler
will get registered inComponentCloneHandlers
(stored inbevy_ecs::component::Components
) at component registration time.The clone handler implementation provided by a component can be overriden after registration like so:
The default clone handler for all components that do not explicitly define one (or don't derive
Component
) iscomponent_clone_via_reflect
ifbevy_reflect
feature is enabled, andcomponent_clone_ignore
(noop) otherwise.Default handler can be overriden using
ComponentCloneHandlers::set_default_handler
Handlers
Component clone handlers can be used to modify component cloning behavior. The general signature for a handler that can be used in
ComponentCloneHandler::Custom
is as follows:The
EntityCloner
implementation (used internally byEntityCloneBuilder
) assumes that after calling this custom handler, thetarget
entity has the desired version of the component from thesource
entity.Builder handler overrides
Besides component-defined and world-overriden handlers,
EntityCloneBuilder
also has a way to override handlers locally. It is mainly used to allow configuration methods likerecursive
andadd_observers
.Testing
Includes some basic functionality tests and doctests.
Performance-wise this feature is the same as calling
clone
followed byinsert
for every entity component. There is also some inherent overhead due to every component clone handler having to access component data throughWorld
, but this can be reduced without breaking current public API in a later PR.