Skip to content

EntityCloner has no concept of required components after configuration which may have surprising effects depending on the target archetype #19324

@urben1680

Description

@urben1680

Bevy version

0.16

What you did

#[derive(Component, PartialEq, Debug, Default, Copy, Clone)]
#[require(Required)]
struct Explicit(u8);

#[derive(Component, PartialEq, Debug, Default, Copy, Clone)]
struct Required(u8);

let mut world = World::new();
let target = world.spawn(Required(10)).id();
world.spawn((Explicit(20), Required(20))).clone_components::<Explicit>(target);
assert_eq!(world.get(target), Some(&Required(10)));

What went wrong

The assertion fails, the target now contains Required(20).
This is surprising, since I did not explicitly ask for that to be cloned and it should not happen if the target already contains it.

The issue here is that EntityCloner has no information of the target entity at the time of configuration. You can either tell it to move Explicit with or without Required, the latter by using EntityCloner::without_required_components. This is not comfortable because you would have to check the target archetype first to pick the correct configuration. There is no automatism.

To make the issue more clear, these are the possible scenarios if I wanted to clone Explicit with or without Required:

use without_required_components target before target after
no Explicit(10), Required(10) Explicit(20), Required(20)
no Required(10) Explicit(20), Required(20)
no Explicit(20), Required(20)
yes Explicit(10), Required(10) Explicit(20), Required(10)
yes Required(10) Explicit(20), Required(10)
yes Explicit(20), Required(0)

As you can see, if you only want to clone Required if it does not exist at the target yet, like insert behaves where it only constructs a default Required when needed, you need to check the target archetype first and pick the correct cloner configuration based on that.

This makes this API uncomfortable.

A proper fix, if we want to keep EntityCloner agnostic to the target archetype (since it may be used on multiple source/target pairs as I saw in the code), it should at least know which ComponentId were given as explicit or implicit.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-BugAn unexpected or incorrect behaviorS-Needs-TriageThis issue needs to be labelled

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions