Skip to content
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

Reflection allows users to clone arbitrary reflected components, but Reflect does not have a Clone bound #3372

Closed
alice-i-cecile opened this issue Dec 18, 2021 · 5 comments
Labels
A-ECS Entities, components, systems, and events A-Reflection Runtime information about types C-Code-Quality A section of code that is hard to understand or change

Comments

@alice-i-cecile
Copy link
Member

alice-i-cecile commented Dec 18, 2021

Problem

  1. ReflectComponent::copy_component allows users to copy any arbitrary component data to different entities, and across worlds, as long as it is reflect-able.
  2. Neither Reflect nor Component has a Clone bound, and so we can sneakily clone components that are not expecting to be cloned. I'm pretty sure this is ultimately done by just copying their raw memory.
  3. Components are effectively cloned in a way that bypasses the Clone method, which can cause issues with things like ref-counting (used for Handle).

Possible Solutions

  1. Give Reflect and/or Component a Clone bound. This fixes problem 2, but not problem 3, and may be painful in niche use cases (although I have not seen any concrete use cases for components that cannot be cloned raised, see Command to clone entities #1515).

Furthermore, not all fields of a component should be reflected. Cloning components with some cloneable field and defaulting the others is a reasonable strategy here, but does not permit a Clone bound.

  1. Use the appropriate Clone methods when they exist.

  2. Leave the existing behavior in place, and very clearly document what's going on and how to use it safely.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change A-Reflection Runtime information about types labels Dec 18, 2021
@DJMcNab
Copy link
Member

DJMcNab commented Dec 18, 2021

I believe this copy would happen based on creating a new default, then reflecting the old onto the new, not be some extremely bad UB

@alice-i-cecile
Copy link
Member Author

@mockersf points out that this Clone-bypassing behavior is not uniquely isolated to this reflection method.

If we implement both Serialize and Deserialize for a type (which we need to, for reflection to be useful to store scenes), we can (tediously) clone arbitrary structs, bypassing any limitations. I've documented this behavior in #3366 at least, and I suspect that's probably the best we can do.

This does however make me increasingly convinced that we should have either Reflect: Clone or Component: Clone, as uncloneable structs cannot be reasonably reflected, and almost all components should be both reflected and cloneable.

@Davier
Copy link
Contributor

Davier commented Dec 18, 2021

This does however make me increasingly convinced that we should have either Reflect: Clone or Component: Clone, as uncloneable structs cannot be reasonably reflected, and almost all components should be both reflected and cloneable.

FromWorld, which is a superset of Default, is required to use ReflectComponent:

impl<C: Component + Reflect + FromWorld> FromType<C> for ReflectComponent {

impl<T: Default> FromWorld for T {
fn from_world(_world: &mut World) -> Self {
T::default()
}
}

@alice-i-cecile
Copy link
Member Author

alice-i-cecile commented Dec 18, 2021

FromWorld, which is a superset of Default, is required to use ReflectComponent:

Yes, which is a nice feature (although I still like FromReflect from #1395). This should avoid unsoundness, but won't save us from logic errors around things like ref-counting. Handle for example could easily implement Default, but handles cloned in this fashion wouldn't keep assets alive in the proper feature.

As Clone is not a subtrait of Default, we could also have Default-able components that aren't clone-able at all, although it's again hard to imagine what those might be used for.

@alice-i-cecile
Copy link
Member Author

Yeah, this is just an ecosystem-wide "problem". Closing.

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 A-Reflection Runtime information about types C-Code-Quality A section of code that is hard to understand or change
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants