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

Share implementation of sort methods. #16203

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

chescock
Copy link
Contributor

@chescock chescock commented Nov 1, 2024

Objective

The various Query::sort() methods have a lot of duplicated code between them, including some unsafe code. Reduce the duplication to make the code easier to read and maintain.

Solution

Extract the duplicated code to a private method, and pass in the sorting strategy as a closure.

Testing

I used cargo-show-asm to verify that the closures were inlined, but I didn't run anything through a profiler. The sort() method itself even had identical assembly before and after this change, although the others did not.

)
};
let mut keyed_query: Vec<_> = query_lens.collect();
keyed_query.sort_by(|(key_1, _), (key_2, _)| compare(key_1, key_2));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Query::sort_unstable_by was calling sort_by instead of sort_unstable_by. That seemed like a bug, so I changed it. If we don't merge this PR, we should probably make one that fixes this line.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good catch! This is a bug.

Comment on lines +824 to +833
fn sort_impl<L: ReadOnlyQueryData + 'w>(
self,
f: impl FnOnce(&mut Vec<(L::Item<'w>, NeutralOrd<Entity>)>),
) -> QuerySortedIter<
'w,
's,
D,
F,
impl ExactSizeIterator<Item = Entity> + DoubleEndedIterator + FusedIterator + 'w,
> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you see a way to preserve whether Entity needs to be wrapped in NeutralOrd?

Since this is no longer public, no need to restrict yourself to impl Trait, you can just use plain generics here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It seems simpler to use it everywhere. It's a simple newtype wrapper, so it won't affect performance. And the methods that weren't wrapping the Entity don't actually use it during sorting, so it won't affect behavior, either.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair, it should be okay then!

Copy link
Contributor

@Victoronz Victoronz left a comment

Choose a reason for hiding this comment

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

It'd be nice if this could be done without introducing an additonal level of closure, maybe with generics?

Another reason for punting on this was that while these do the same now, that may change when sort caching is introduced.
This might have to look different then.

@chescock
Copy link
Contributor Author

chescock commented Nov 2, 2024

It'd be nice if this could be done without introducing an additonal level of closure, maybe with generics?

What's wrong with using a closure here? Closures are just syntactic sugar for generics, and I don't see how to make things more clear here with named types. (There's no dynamic dispatch, if that's your worry; this is impl FnOnce, not dyn FnOnce. It all gets inlined.)

@chescock
Copy link
Contributor Author

chescock commented Nov 2, 2024

Another reason for punting on this was that while these do the same now, that may change when sort caching is introduced.
This might have to look different then.

Oh, I didn't know there were plans for sort caching! This PR certainly isn't urgent, so it can wait if that's happening soon.

Depending on your plans for caching, though, this change could make it easier. I wouldn't expect the caching to vary wildly based on the type of sort, so doing this first could mean that you have to add it to one place instead of six.

@Victoronz
Copy link
Contributor

It'd be nice if this could be done without introducing an additonal level of closure, maybe with generics?

What's wrong with using a closure here? Closures are just syntactic sugar for generics, and I don't see how to make things more clear here with named types. (There's no dynamic dispatch, if that's your worry; this is impl FnOnce, not dyn FnOnce. It all gets inlined.)

While this might generate mildly different code, the main concern here instead would be capability to preserve the information that might be useful for caching: the sort kind, and the sort function used, if there is one (maybe as a fn pointer).

However, caching is not yet in the process of being implemented and is still being discussed in #13464, so how it'll look like is up in the air.

@iiYese
Copy link
Contributor

iiYese commented Nov 3, 2024

Another reason for punting on this was that while these do the same now, that may change when sort caching is introduced.

It shouldn't. The public user facing APIs for the cached variants will only allow fn pointers because closures can be stateful & alter how they sort. The cache would have no way of detecting if this state is changed hence they're not allowed. All impl Fns can accept function pointers.

@alice-i-cecile alice-i-cecile added A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Nov 3, 2024
@Victoronz
Copy link
Contributor

It'd be nice if this could be done without introducing an additonal level of closure, maybe with generics?

What's wrong with using a closure here? Closures are just syntactic sugar for generics, and I don't see how to make things more clear here with named types. (There's no dynamic dispatch, if that's your worry; this is impl FnOnce, not dyn FnOnce. It all gets inlined.)

While this might generate mildly different code, the main concern here instead would be capability to preserve the information that might be useful for caching: the sort kind, and the sort function used, if there is one (maybe as a fn pointer).

Actually, the information I am talking about can and would better be passed as an additional parameter to sort_impl if need be, so no longer a concern.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Code-Quality A section of code that is hard to understand or change S-Needs-Review Needs reviewer attention (from anyone!) to move forward
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants