-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Fix child_builder and Parent / Children management #1076
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
Conversation
I like the idea of having some tests here. I'm in the middle of a change to make Children unique by hashing the children. I'm curious how you intend to approach that problem. |
let mut childset = HashSet::default(); | ||
|
||
let mut new_children = if let Ok(children) = world.get::<Children>(self.parent) { | ||
childset.extend(children.iter().copied()); |
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 think the cost of this might become significant if the number of children is large and the game is making frequent hierarchy changes. That's why in #1079 I chose to make the set persistent. It makes Children more complex internally but avoids this full iteration of the list on every operation.
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.
removed that by now.
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.
Have a look again. I now "only" check for contain in the smallvec itself, though for each added child.
I refrain from maintaining a hash map or hash set in the children component itself, since I still need to maintain order with a vec, which defeats the purpose on insert and removal and imposes more allocations for many components with few children.
I refrain from creating an ad-hoc hash set to "speed up" contains checks per operation, since its a rather delicate perf optimization which needs to be benchmarked to do it properly. I could imagine for use cases with many children and many insert/removal operations, an order-independent approach is more suitable. Like going pure HashSet in that case.
44b18b1
to
b3ce4a0
Compare
.insert_one(self.parent, Children(self.children)) | ||
.unwrap(); | ||
for child in self.children.iter().rev() { | ||
if !new_children.0.contains(child) { |
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.
Here and on line 75 you're scanning through the existing children once for every new child. I think that's worse than the previous version where you built a HashSet. That required a scan through the existing children once per command application. Though this version might actually be faster in the case where there's only a single child being added but I'd expect it to be much slower if there are multiple new children.
running 3 tests test push_children_1_parent_to_many_children ... bench: 2,182,562 ns/iter (+/- 84,295) test push_children_many_parents_with_1_child ... bench: 356,531 ns/iter (+/- 34,554) test push_children_many_parents_with_8_children ... bench: 2,430,602 ns/iter (+/- 79,929)
I added a work in progress commit for some benchmarks. I removed an unmaintained bench and made bencher work again. Commit contains first numbers. I add an ad-hoc HashSet for more reasonable contains() checks to see what is going on. With each change I add the benchmark results to the corresponding work in progess commit to keep track. Is there a better way to keep track and collaborate? I checked your branch and cherry-picked the benchmark: running 3 tests Compared to latest commit on my branch: running 3 tests There are still some correctness issues I believe but your approach is faster and it seams to be even for the single child usecase |
running 3 tests test push_children_1_parent_to_many_children ... bench: 1,885,600 ns/iter (+/- 408,324) test push_children_many_parents_with_1_child ... bench: 458,489 ns/iter (+/- 43,306) test push_children_many_parents_with_8_children ... bench: 3,043,359 ns/iter (+/- 574,231)
running 3 tests test push_children_1_parent_to_many_children ... bench: 1,866,480 ns/iter (+/- 87,740) test push_children_many_parents_with_1_child ... bench: 368,759 ns/iter (+/- 14,810) test push_children_many_parents_with_8_children ... bench: 2,505,811 ns/iter (+/- 98,279)
closed in favor of #1079 |
I was looking into the tests for Parent / Children management and noted it was difficult to figure out the intended properties of push_children / insert_children, since the two existing tests were a collection of multiple assertions over some assemblages of commands.
I intend to split the tests up into a granularity to check roughly one property at a time.
The first commit contains skipped test cases for the things which do not work as I would assume. Look for
#[cfg(skip)]
. The other test cases should at least cover what the previous test was covering.I intend to fix the implementation of PushChildren / InsertChildren to not panic, keep Children unique, handle PreviousParent like it is probably intended.
The hierarchy_maintenance_system is interesting too in that regard to uphold the intended properties of Parent / Children relationship, but more like fixing up things. Functions like push_children / insert_children should not depend on that system to fix up things for them.