Skip to content

Moving components instead of duplicating should lift the Clone/Reflect requirement #18079

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

Open
urben1680 opened this issue Feb 27, 2025 · 5 comments
Labels
A-ECS Entities, components, systems, and events C-Bug An unexpected or incorrect behavior D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Uncontroversial This work is generally agreed upon

Comments

@urben1680
Copy link
Contributor

urben1680 commented Feb 27, 2025

What problem does this solve or what need does it fill?

With EntityCloner, one can give an entity the component of another. By default the original entity keeps it's component and this operation requires the component to be clone-able.

However, if the component is moved, so the original entity loses the component, this should be no requirement. But it currently is. Move operations just fail silently

#[derive(Component)] // adding Clone or Reflect makes the assertions not fail
struct NoCloneImpl;

let mut world = World::new();
let entity1 = world.spawn_empty().id();
let entity2 = world
    .spawn(NoCloneImpl)
    .move_components::<NoCloneImpl>(entity1) // fails silently
    .id();

assert_eq!(world.entity(entity1).contains::<NoCloneImpl>(), true); // panic
assert_eq!(world.entity(entity2).contains::<NoCloneImpl>(), false); // panic

(The same effect can be observed with EntityCloner)

What solution would you like?

Make it possible to move components without them implementing Clone nor Reflect.

Additional context

This was an open todo from @eugineerd, mentioning that it is tricky to not have the component be dropped twice. I just wanted to open this issue so it is written down somewhere.

(Not sure if this is a bug issue or feature request issue)

@urben1680 urben1680 added C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Feb 27, 2025
@urben1680 urben1680 changed the title Moving components via EntityCloner instead of cuplicating should lift the clone bound Moving components instead of duplicating should lift the clone requirement Feb 27, 2025
@urben1680 urben1680 changed the title Moving components instead of duplicating should lift the clone requirement Moving components instead of duplicating should lift the Clone/Reflect requirement Feb 27, 2025
@eugineerd
Copy link
Contributor

eugineerd commented Feb 28, 2025

To give some context about the problem:
move_components in EntityCloner works by using EntityWorldMut::remove_by_ids, but this drops the components' values. What we want is actually something like EntityWorldMut::forget_by_ids (not too difficult to implement). The real problem is that we would still (probably?) want to run remove hooks and observers on moved components.
To do that, EntityWorldMut::forget_by_ids would have to run these hooks just like EntityWorldMut::remove_by_ids does:

  1. copy component data from source
  2. call forget_by_ids
  3. insert component data into target

However, hooks and observers can move data out of components, so after step 2 we might have invalid component data stored. Instead, the correct way to do it looks something like this:

  1. run remove hooks/observes
  2. copy component data from source
  3. call forget_by_ids without running any hooks/observers
  4. insert component data into target

So the question really is how to design such interface without having to reimplement most of EntityWorldMut::remove_bundle.

@grind086
Copy link
Contributor

grind086 commented Mar 2, 2025

I noticed a couple of TODO: BundleRemover? comments and gave that a shot. It seems like it'd be a good way to go, but I borked something when I tried it.

I think it's possible to clean up a lot of the repetitive *_with_caller methods this way too. Rather than repetitive special-cased internal methods, those things could be configured on the BundleRemover. A few helper functions could also be turned into methods on BundleRemover (or just moved to bundle.rs).

@alice-i-cecile alice-i-cecile added C-Bug An unexpected or incorrect behavior A-ECS Entities, components, systems, and events S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Uncontroversial This work is generally agreed upon D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes and removed C-Feature A new feature, making something new possible S-Needs-Triage This issue needs to be labelled labels Mar 6, 2025
@urben1680
Copy link
Contributor Author

urben1680 commented Mar 22, 2025

This change could also require some adjustments on relations.

The cloning behavior currently is to skip Relationship(Target) components. If moving becomes unconditional then this could break it, like when moving such a component into an entity where it is already present. RelationshipTargets may be possible to be combined, but not Relationship. A mechanism needs to ensure these still are not moved.

@urben1680
Copy link
Contributor Author

Does this become easier now that there is a BundleRemover thanks to #18521?

@eugineerd
Copy link
Contributor

Does this become easier now that there is a BundleRemover thanks to #18521?

Yes, as far as I can see. It would require restructuring the current EntityCloner::clone_entity_internal code to run component cloning inside the BundleRemover's pre_remove function, which shouldn't be too difficult to do.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Bug An unexpected or incorrect behavior D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Needs-Design This issue requires design work to think about how it would best be accomplished X-Uncontroversial This work is generally agreed upon
Projects
None yet
Development

No branches or pull requests

4 participants