Skip to content

Bulk operations on entity relationships #18041

@viridia

Description

@viridia

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

Before relationships, we had the replace_children method, which wasn't particularly efficient (it removed all existing children, even the ones that were carried over, and then replaced them with the new list), but it was useful. There's no method in the new API which provides the same level of functionality.

For implementing reactions (as illustrated in cart's discussion post as well as my various libraries) it is a very common operation to want to replace all children of an entity with a new list of children, where the two lists may have elements in common. Currently this requires a hacky workaround: removing the Children component from the parent, and then adding the new children via add_related.

Ideally what we want to have happen is:

  • Remove the ChildOf component from any existing children which are not in the new list.
  • Add ChildOf components to any new children which are not in the old list.
  • For elements which are in both the old and new lists, do nothing - don't modify the ChildOf components.
  • Update the Children component to contain the new list with the correct ordering of entities.

Note that in many cases the first step can be omitted, since the old children are about to be despawned anyway.

Now, it might sound that in order to do this correctly, you would need to allocate memory - effectively what's described in the preceding steps are set operations (all A not in B) which would require an EntitySet. In other words, you'd start by initializing the set to the existing children, and remove from the set any children that are being carried over. What's left is the children that need to be unlinked. Unfortunately, set operations allocate memory.

However, we can avoid this by making the caller do the work. The reactive framework has in all likelyhood already done this calculation (determining which children are new and which are to be removed), so there's no need for the low-level relationship API to do this computation again. What the caller cannot (and should not) do, however, is directly modify the contents of Children and ChildOf components. So we need an API which allows the caller to provide the results of this calculation.

What solution would you like?

What I am envisioning is a method on EntityWorldMut which accepts three arguments (plus self), all of which are entity slices:

  • The replacement entity list, which will become the new contents of Children.
  • A list of entities which are being added. A ChildOf component will be added to each entity in this list.
  • A list of entities which are being removed. The ChildOf component will be removed from each entity in this list.

It's the caller's responsibility to ensure that the arguments are consistent: the list of newly added entities should be a subset of the list of replacements, and the list of newly removed entities should be a subset of the exiting children.

In cases where the removed entities are being immediately despawned, we can get by with passing in an empty slice as the third argument - this means that the work of removing the ChildOf components can be skipped.

We may also want to have a variant of this which replaces just a sub-range of children, specified by starting index and length. So there would be 4 arguments: the three given above, plus a range argument.

What alternative(s) have you considered?

It's possible to do this one entity at a time with current APIs, but it's not very efficient. The current hacky workaround also works, but does needless archetype moves (deleting the ChildOf component and then immediately adding it back again).

Assuming we can agree on the design of the API, the implementation should be relatively easy.

Additional context

See the discussion on in-place reactions #17917

@cart @alice-i-cecile

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-PerformanceA change motivated by improving speed, memory usage or compile timesC-UsabilityA targeted quality-of-life change that makes Bevy easier to useD-ModestA "normal" level of difficulty; suitable for simple features or challenging fixesS-Ready-For-ImplementationThis issue is ready for an implementation PR. Go for it!X-UncontroversialThis work is generally agreed upon

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions