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

Fix transform propagation of orphaned entities #7264

Merged
merged 4 commits into from
Apr 9, 2023

Conversation

nicopap
Copy link
Contributor

@nicopap nicopap commented Jan 18, 2023

Objective

This has nothing to do with #7024. This is for the case where the
user opted to not keep the same global transform on update.

Solution

  • Add a RemovedComponent<Parent> to propagate_transforms
  • Add a RemovedComponent<Parent> and Local<Vec<Entity>> to sync_simple_transforms
  • Add test to make sure all of this works.

Performance note

This should only incur a cost in cases where a parent is removed.

A minimal overhead (one look up in the removed_components
sparse set) per root entities without children which transform didn't
change. A Vec the size of the largest number of entities removed
with a Parent component in a single frame, and a binary search on
a Vec per root entities.

It could slow up considerably in situations where a lot of entities are
orphaned consistently during every frame, since
sync_simple_transforms is not parallel. But in this situation,
it is likely that the overhead of archetype updates overwhelms everything.


Changelog

  • Fix the GlobalTransform not getting updated when Parent is removed

Migration Guide

  • If you called bevy_transform::systems::sync_simple_transforms and bevy_transform::systems::propagate_transforms (which is not re-exported by bevy) you need to account for the additional RemovedComponents<Parent> parameter.

@nicopap nicopap added C-Bug An unexpected or incorrect behavior A-Transform Translations, rotations and scales A-Hierarchy Parent-child entity hierarchies labels Jan 18, 2023
root_query.par_for_each_mut(
// The differing depths and sizes of hierarchy trees causes the work for each root to be
// different. A batch size of 1 ensures that each tree gets it's own task and multiple
// large trees are not clumped together.
1,
|(entity, children, transform, mut global_transform)| {
let changed = transform.is_changed();
let changed = transform.is_changed() || orphaned.binary_search(&entity).is_ok();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may actually be faster to do a sparse set contains check here than do the binary search. It'd also avoid the allocation and sort.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would this look? RemovedComponents only exposes the iter() method. all other fields private. I guess the best way would be to add a method on RemovedComponents to allow sparse set contains checks.

Copy link
Contributor Author

@nicopap nicopap Jan 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the entities in World::removed_components are stored in a Vec. It's unlikely that it's possible to outperform sorting + binary search. I expect the Vec to be always relatively small and not spill out of cache.

The alternative is to create a method that exposes a Option<&[Entity]> in RemovedComponents, and iterate through it for each root entities.

Another option is to store orphaned in a Local to avoid allocation at each frame. Of course, since the iterator returned by RemovedComponents::iter is Cloned<slice::Iter<Entity>>, I don't expect it to allocate when it's empty. And storing the Vec in a Local smells memory leak.

Copy link
Contributor Author

@nicopap nicopap Jan 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is non-actionable. If performance is a concern, could you point to ways of benchmarking testing for regression?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to store orphaned in a Local to avoid allocation at each frame. Of course, since the iterator returned by RemovedComponents::iter is Cloned<slice::Iter<Entity>>, I don't expect it to allocate when it's empty. And storing the Vec in a Local smells memory leak.

This would be a nice (but not necessary) optimization. It would just allow the system to reuse a single allocation, it shouldn't cause a memory leak or anything bad.

Copy link
Contributor Author

@nicopap nicopap Apr 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My worry is that orphaning happens in bursts (think of unloading/loading scenes), so we end up with reserved memory for the worst case while the Vec is only fully used one frame every minutes/hours.

I think a cool thing bevy could provide is some sort of smart Local<Vec<T>> that clears intelligently. I think it would benefit the engine itself already.

I've added a Local<Vec<Entity>> to avoid the allocation, but see the two previous paragraphs.

crates/bevy_transform/src/systems.rs Outdated Show resolved Hide resolved
Copy link
Member

@james7132 james7132 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM other than this one nit.

crates/bevy_transform/src/systems.rs Show resolved Hide resolved
@nicopap nicopap force-pushed the fix-orphaned-transform branch 2 times, most recently from efa3c16 to b63cbc8 Compare February 7, 2023 09:55
@james7132 james7132 added this to the 0.11 milestone Mar 4, 2023
@nicopap nicopap modified the milestones: 0.11, 0.10.1 Mar 7, 2023
@nicopap nicopap added the M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide label Mar 7, 2023
@github-actions
Copy link
Contributor

github-actions bot commented Mar 7, 2023

It looks like your PR is a breaking change, but you didn't provide a migration guide.

Could you add some context on what users should update when this change get released in a new version of Bevy?
It will be used to help writing the migration guide for the version. Putting it after a ## Migration Guide will help it get automatically picked up by our tooling.

Copy link
Member

@JoJoJet JoJoJet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good fix, there's something I'm not sure about yet though.

root_query.par_for_each_mut(
// The differing depths and sizes of hierarchy trees causes the work for each root to be
// different. A batch size of 1 ensures that each tree gets it's own task and multiple
// large trees are not clumped together.
1,
|(entity, children, transform, mut global_transform)| {
let changed = transform.is_changed();
let changed = transform.is_changed() || orphaned.binary_search(&entity).is_ok();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to store orphaned in a Local to avoid allocation at each frame. Of course, since the iterator returned by RemovedComponents::iter is Cloned<slice::Iter<Entity>>, I don't expect it to allocate when it's empty. And storing the Vec in a Local smells memory leak.

This would be a nice (but not necessary) optimization. It would just allow the system to reuse a single allocation, it shouldn't cause a memory leak or anything bad.

crates/bevy_transform/src/systems.rs Outdated Show resolved Hide resolved
crates/bevy_transform/src/systems.rs Show resolved Hide resolved
crates/bevy_transform/src/systems.rs Outdated Show resolved Hide resolved
Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
@james7132 james7132 added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Apr 4, 2023
@james7132 james7132 added this pull request to the merge queue Apr 9, 2023
Merged via the queue into bevyengine:main with commit f32ee63 Apr 9, 2023
github-merge-queue bot pushed a commit that referenced this pull request Aug 28, 2023
…tities query (#9518)

# Objective

`sync_simple_transforms` only checks for removed parents and doesn't
filter for `Without<Parent>`, so it overwrites the `GlobalTransform` of
non-orphan entities that were orphaned and then reparented since the
last update.

Introduced by #7264

## Solution

Filter for `Without<Parent>`.

Fixes #9517, #9492

## Changelog

`sync_simple_transforms`:
* Added a `Without<Parent>` filter to the orphaned entities query.
@nicopap nicopap deleted the fix-orphaned-transform branch August 30, 2023 13:48
rdrpenguin04 pushed a commit to rdrpenguin04/bevy that referenced this pull request Jan 9, 2024
…tities query (bevyengine#9518)

# Objective

`sync_simple_transforms` only checks for removed parents and doesn't
filter for `Without<Parent>`, so it overwrites the `GlobalTransform` of
non-orphan entities that were orphaned and then reparented since the
last update.

Introduced by bevyengine#7264

## Solution

Filter for `Without<Parent>`.

Fixes bevyengine#9517, bevyengine#9492

## Changelog

`sync_simple_transforms`:
* Added a `Without<Parent>` filter to the orphaned entities query.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Hierarchy Parent-child entity hierarchies A-Transform Translations, rotations and scales C-Bug An unexpected or incorrect behavior M-Needs-Migration-Guide A breaking change to Bevy's public API that needs to be noted in a migration guide S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

The GlobalTransform did not get updated when Parent was removed
5 participants