-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
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
Conversation
480ab13
to
0436c5f
Compare
crates/bevy_transform/src/systems.rs
Outdated
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(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 aLocal
to avoid allocation at each frame. Of course, since the iterator returned byRemovedComponents::iter
isCloned<slice::Iter<Entity>>
, I don't expect it to allocate when it's empty. And storing theVec
in aLocal
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.
There was a problem hiding this comment.
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.
0436c5f
to
e59d44e
Compare
There was a problem hiding this 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.
efa3c16
to
b63cbc8
Compare
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? |
There was a problem hiding this 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.
crates/bevy_transform/src/systems.rs
Outdated
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(); |
There was a problem hiding this comment.
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 aLocal
to avoid allocation at each frame. Of course, since the iterator returned byRemovedComponents::iter
isCloned<slice::Iter<Entity>>
, I don't expect it to allocate when it's empty. And storing theVec
in aLocal
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.
Co-authored-by: vyb <vyb@users.noreply.github.com>
b63cbc8
to
d1af08a
Compare
Co-authored-by: JoJoJet <21144246+JoJoJet@users.noreply.github.com>
…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.
…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.
Objective
GlobalTransform
did not get updated whenParent
was removed #7263This 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
RemovedComponent<Parent>
topropagate_transforms
RemovedComponent<Parent>
andLocal<Vec<Entity>>
tosync_simple_transforms
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 removedwith a
Parent
component in a single frame, and a binary search ona
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
GlobalTransform
not getting updated whenParent
is removedMigration Guide
bevy_transform::systems::sync_simple_transforms
andbevy_transform::systems::propagate_transforms
(which is not re-exported by bevy) you need to account for the additionalRemovedComponents<Parent>
parameter.