-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
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
Add methods to bulk replace relationships on a entity #18058
Conversation
8fbbaba
to
a79617b
Compare
6f549b7
to
db43a02
Compare
let mut potential_relations = EntityHashSet::from_iter(related.iter().copied()); | ||
let mut relations_to_remove = EntityHashSet::with_capacity(related.len()); | ||
|
||
for related in existing_relations.iter() { | ||
if !potential_relations.remove(&related) { | ||
relations_to_remove.insert(related); | ||
} | ||
} | ||
|
||
let id = self.id(); | ||
self.world_scope(|world| { | ||
for related in relations_to_remove { | ||
world.entity_mut(related).remove::<R>(); | ||
} | ||
|
||
for related in potential_relations { | ||
world.entity_mut(related).insert(R::from(id)); | ||
} | ||
}); |
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.
A problem with this approach is that the resulting target collection may not result in the same order as the passed in related
.
As a user of replace_related
I would assume I can pass in a new order of the same set of entities to reorder them (for relationships requiring a stable ordering, e.g. Children
).
I'm also curious about the performance benefits of this approach (allocating hashsets) over just removing the target collection and calling add_children
(archetype moves?).
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.
In the original issue I posted (#18041), I proposed a method to do this without using hash sets, however it entails a more complex API in which the caller does the work of determining which entities are added and removed. This puts the responsibility on the caller to ensure that the arguments are consistent.
In my various reactive experiments, I need to do this computation anyway (for example foreach
computes the diff as part of its operation), so it's relatively easy for me to pass that information along to the replace_related
API. Having replace_related
do the calculation internally means that the set operations get done twice.
That being said, I may be overly/prematurely optimizing here. Maybe using hash sets is OK? I can't really say one way or the other.
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.
Maybe an optimized replace could benefit from the new RelationshipInsertHookMode
in #17858
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.
I'm not too fond of requiring the user to pass correct lists. Seems like this can easily be misunderstood/lead to bugs.
For more specialized scenarios (like tree rendering mechanisms) where the caller already knows the “diff”, there could be an alternative function taking the new_related
and deleted
with a name that communicates the “riskiness”.
Or those use cases can update the relationship components directly, and use the hook mode to avoid archetype moves.
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.
A problem with this approach is that the resulting target collection may not result in the same order as the passed in related.
We could directly modify the target collection to change the ordering but that would conflict with users overriding relationship on_insert
hooks. My opinion is that a user will break any assumptions we make if given the chance. Imagine for example a on_insert
hook that filters what entities can be inserted into a relationship. If we then manually insert entities into this collection we would be bypassing this filtering.
For more specialized scenarios (like tree rendering mechanisms) where the caller already knows the “diff”, there could be an alternative function taking the new_related and deleted with a name that communicates the “riskiness”.
I'd be willing to add this method too, but such a method requires #17858. Otherwise the method would be left with just calculating a difference between the insert
and remove
entity sets and inserting that (if we add any existing relationships the on_add hook will cause a double insert). Exactly what the current implementation does (and calculating a difference either requires a allocated set or the ability to sort the passed in slices. i.e. either allocating or breaking ordering).
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.
#17858 has been merged now :)
…18241) # Objective While redoing #18058 I needed `RelationshipSourceCollection` (henceforth referred to as the **Trait**) to implement `clear` so I added it. ## Solution Add the `clear` method to the **Trait**. Also make `add` and `remove` report if they succeeded. ## Testing Eyeballs --- ## Showcase The `RelationshipSourceCollection` trait now reports if adding or removing an entity from it was successful. It also not contains the `clear` method so you can easily clear the collection in generic contexts. ## Changes EDITED by Alice: We should get this into 0.16, so no migration guide needed. The `RelationshipSourceCollection` methods `add` and `remove` now need to return a boolean indicating if they were successful (adding a entity to a set that already contains it counts as failure). Additionally the `clear` method has been added to support clearing the collection in generic contexts.
09c7fd7
to
e9eeb9f
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.
Looks good. This will be a fundamental building block for entity templating systems.
This is mostly done. The documentation needs some editing and the naming could use some bikeshedding. Sadly I'm spent for today. See you tomorrow. |
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.
Nice job on the optimized version! 👍 Really liking the debug_assertions
too.
Still have some concerns about the ordering for replace_related
. And some minor perf comments etc.
This should be done now. There's still some performance left on the table (see #18300) but otherwise everything works and tests pass. One thing we should do is write down all the assumptions we make about relationship hooks. They're currently publicly accessible and a motivated user could override them and make them do some wild stuff. Alternatively we could add |
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 👍
I don't think hooks are overridable. And the derive macro stops you from adding hooks that are already used for relationship components. |
We allow users to impl |
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.
Sensible and well-tested. I've fixed a grammar issue, but now I'm comfortable merging this.
Head branch was pushed to by a user without write access
bevyengine#18378 renamed `RelationshipInsertHookMode`
# Objective Add a way to efficiently replace a set of specifically related entities with a new set. Closes #18041 ## Solution Add new `replace_(related/children)` to `EntityWorldMut` and friends. ## Testing Added a new test to `hierarchy.rs` that specifically check if `replace_children` actually correctly replaces the children on a entity while keeping the original one. --- ## Showcase `EntityWorldMut` and `EntityCommands` can now be used to efficiently replace the entities a entity is related to. ```rust /// `parent` has 2 children. `entity_a` and `entity_b`. assert_eq!([entity_a, entity_b], world.entity(parent).get::<Children>()); /// Replace `parent`s children with `entity_a` and `entity_c` world.entity_mut(parent).replace_related(&[entity_a, entity_c]); /// `parent` now has 2 children. `entity_a` and `entity_c`. /// /// `replace_children` has saved time by not removing and reading /// the relationship between `entity_a` and `parent` assert_eq!([entity_a, entity_c], world.entity(parent).get::<Children>()); --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective While working on #18058 I realized I could use `RelationshipTargetCollection::new`, so I added it. ## Solution - Add `RelationshipTargetCollection::new` - Add `RelationshipTargetCollection::reserve`. Could generally be useful when doing micro-optimizations. - Add `RelationshipTargetCollection::shrink_to_fit`. Rust collections generally don't shrink when removing elements. Might be a good idea to call this once in a while. ## Testing `cargo clippy` --- ## Showcase `RelationshipSourceCollection` now implements `new`, `reserve` and `shrink_to_fit` to give greater control over how much memory it consumes. ## Migration Guide Any type implementing `RelationshipSourceCollection` now needs to also implement `new`, `reserve` and `shrink_to_fit`. `reserve` and `shrink_to_fit` can be made no-ops if they conceptually mean nothing to a collection. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
# Objective While working on #18058 I realized I could use `RelationshipTargetCollection::new`, so I added it. ## Solution - Add `RelationshipTargetCollection::new` - Add `RelationshipTargetCollection::reserve`. Could generally be useful when doing micro-optimizations. - Add `RelationshipTargetCollection::shrink_to_fit`. Rust collections generally don't shrink when removing elements. Might be a good idea to call this once in a while. ## Testing `cargo clippy` --- ## Showcase `RelationshipSourceCollection` now implements `new`, `reserve` and `shrink_to_fit` to give greater control over how much memory it consumes. ## Migration Guide Any type implementing `RelationshipSourceCollection` now needs to also implement `new`, `reserve` and `shrink_to_fit`. `reserve` and `shrink_to_fit` can be made no-ops if they conceptually mean nothing to a collection. --------- Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com> Co-authored-by: Chris Russell <8494645+chescock@users.noreply.github.com>
Objective
Add a way to efficiently replace a set of specifically related entities with a new set.
Closes #18041
Solution
Add new
replace_(related/children)
toEntityWorldMut
and friends.Testing
Added a new test to
hierarchy.rs
that specifically check ifreplace_children
actually correctly replaces the children on a entity while keeping the original one.Showcase
EntityWorldMut
andEntityCommands
can now be used to efficiently replace the entities a entity is related to.