Skip to content

The documentation for ReflectComponent is inconsistent with how Reflect is implemented for lists #7129

Open
@johanhelsing

Description

@johanhelsing

Bevy version

v0.9.1

What you did

The documentation for ReflectComponent currently reads:

    /// Uses reflection to set the value of this [`Component`] type in the entity to the given value.

However, internally, the implementation of apply for lists:

/// Applies the elements of `b` to the corresponding elements of `a`.
///
/// If the length of `b` is greater than that of `a`, the excess elements of `b`
/// are cloned and appended to `a`.
///
/// # Panics
///
/// This function panics if `b` is not a list.
#[inline]
pub fn list_apply<L: List>(a: &mut L, b: &dyn Reflect) {
    if let ReflectRef::List(list_value) = b.reflect_ref() {
        for (i, value) in list_value.iter().enumerate() {
            if i < a.len() {
                if let Some(v) = a.get_mut(i) {
                    v.apply(value);
                }
            } else {
                List::push(a, value.clone_value());
            }
        }
    } else {
        panic!("Attempted to apply a non-list type to a list type.");
    }
}

This means List[1, 2, 3].apply(List[4, 5]) == List[4, 5, 3].

So the result of calling ReflectComponent::apply() on a component with a list in it would not actually result in the components being equal afterwards.

In particular, this was causing problems in bevy_ggrs, where the Children component is being rolled back. We used ReflectComponent::apply since that sounded like the right thing to do, but since list_apply didn't remove elements if self.len() > value.len(), we were getting leftover children that shouldn't be there.

To work around this, we used ReflectComponent::remove followed by ReflectComponent::insert. However, by doing this it is causing unnecessary copies of the entire children hierarchy on every snapshot restore, and also needlessly triggering Added queries, causing further performance regressions.

What went wrong

I think the least confusing thing for users, would be if Reflect::apply for lists actually removed excess elements, so List[1, 2, 3].apply(List[4, 5]) == List[4, 5]

Alternatively, the documentation for ReflectComponent::apply should specifically warn about this gotcha, and we need some alternative way of performing in-place updates of components.

Additional information

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ReflectionRuntime information about typesC-DocsAn addition or correction to our documentation

    Type

    No type

    Projects

    Status

    Open

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions