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

Add snapped to integer vectors #768

Merged
merged 1 commit into from
Jul 9, 2024

Conversation

joriskleiber
Copy link
Contributor

This adds snapped for integer vectors based on this code.

@Bromeon Bromeon added feature Adds functionality to the library c: core Core components labels Jun 16, 2024
@joriskleiber joriskleiber marked this pull request as draft June 16, 2024 22:35

// manual implement `a.div_floor(step)` since Rust's native method is still unstable, as of 1.79.0
let mut d = a / step;
let r = a % step;
Copy link
Contributor Author

@joriskleiber joriskleiber Jun 16, 2024

Choose a reason for hiding this comment

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

a % step won't panic because if a is ever equal to i32::MIN(-2147483648) and step is -1, value needs to be -2147483647.5 which is imposible.

Copy link
Member

Choose a reason for hiding this comment

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

if value is i32::MIN and step is -1 then a is i32::MIN since -1 / 2 = 0 for i32.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Thank you!

Could you also add tests for zero steps? (Either one component 0, or the whole step vector).

/// A new vector with each component snapped to the closest multiple of the corresponding
/// component in `step`.
pub fn snapped(self, step: Self) -> Self {
#[inline]
Copy link
Member

@Bromeon Bromeon Jun 17, 2024

Choose a reason for hiding this comment

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

#[inline] is mostly needed for cross-crate inlining (because Rust simply can't do it without that attribute), but I wouldn't use it for private/crate-local functions. The compiler (or LLVM) should be able to judge whether to inline such functions itself.

Copy link
Member

Choose a reason for hiding this comment

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

Additionally, the snap_one method is now duplicated for all vectors. I think it would be better to declare it once manually outside the macro.

Copy link
Member

Choose a reason for hiding this comment

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

#[inline] is mostly needed for cross-crate inlining (because Rust simply can't do it without that attribute)

Not entirely true anymore, rust will automatically inline small functions as of 1.75 rust-lang/rust#116505

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, this is however hardly useful in practice -- even if a function is "simple", there's a chance that refactoring makes it non-simple and causes pessimizations. As such, always writing #[inline] when inlining is allowed is still the better practice...

But it's a start, I hope this extended in the not too distant future 🙂

@joriskleiber joriskleiber marked this pull request as ready for review June 23, 2024 01:29
@joriskleiber
Copy link
Contributor Author

I don't know if there is a better way to handle the possible division overflow, but I thinks its better to panic with a message then just letting it crash.

godot-core/src/builtin/vectors/vector_macros.rs Outdated Show resolved Hide resolved
Comment on lines +461 to +464
assert!(
value != i32::MIN || step != -1,
"snapped() called on vector component i32::MIN with step component -1"
);
Copy link
Member

Choose a reason for hiding this comment

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

Is only this a problem? step == -1 would work?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, I just realized how many overflow errors this function has... I have no idea how to handle all these, maybe you could give some guidance what the best way forward would be?

Copy link
Member

Choose a reason for hiding this comment

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

Limit the input range to the common usage (and panic for others) would be a start. Upon user request, we can then expand in the future.

@GodotRust
Copy link

API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-768

@joriskleiber
Copy link
Contributor Author

So I did the math (hopefully correct) and came up with these, in my opinion reasonable, panic conditions. I hope I didn't miss any other panics or overflows.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Thanks a lot for approaching this so carefully! Turns out the seemingly simple snapped is very involved 😮

PR looks good, just some small doc nitpicks and it should be ready!

godot-core/src/builtin/vectors/vector_macros.rs Outdated Show resolved Hide resolved
Comment on lines 461 to 464
assert!(
value != i32::MIN || step >= 0,
"snapped() called on vector component i32::MIN with step component smaller then 0"
);
assert!(
value != i32::MAX || step <= 1,
"snapped() called on vector component i32::MAX with step component greater then 1"
);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
assert!(
value != i32::MIN || step >= 0,
"snapped() called on vector component i32::MIN with step component smaller then 0"
);
assert!(
value != i32::MAX || step <= 1,
"snapped() called on vector component i32::MAX with step component greater then 1"
);
assert!(
value != i32::MIN || step >= 0,
"snapped() called on vector component i32::MIN with step component less than 0"
);
assert!(
value != i32::MAX || step <= 1,
"snapped() called on vector component i32::MAX with step component greater than 1"
);

Comment on lines 500 to 502
/// # Panics
/// If any component of `self` is `i32::MIN` while the same component on `step` smaller then `0` or
/// if any component of `self` is `i32::MAX` while the same component on `step` greater then `1`.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this could be worded in a way makes clear that panics only happen in under/overflow scenarios, not general usage and especially not 0 steps:

Suggested change
/// # Panics
/// If any component of `self` is `i32::MIN` while the same component on `step` smaller then `0` or
/// if any component of `self` is `i32::MAX` while the same component on `step` greater then `1`.
/// # Panics
/// On under- or overflow:
/// - If any component of `self` is `i32::MIN` while the same component on `step` less than `0`.
/// - If any component of `self` is `i32::MAX` while the same component on `step` greater than `1`.

@joriskleiber
Copy link
Contributor Author

As expected there where more possible overflow scenarios. For whatever reason i thought addition could only overflow for i32::MIN - 1 or i32::MAX + 1.

The description for the second panic condition is horrific but I don't know how to word it otherwise.

Copy link
Member

@Bromeon Bromeon left a comment

Choose a reason for hiding this comment

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

Thanks for working out the conditions so carefully!

I think we might need to keep an eye on the performance; with all the overflow checks, it could happen that the happy path is now unnecessarily slow. But if this becomes a problem, we can probably use debug_assert! or similar.

@Bromeon Bromeon added this pull request to the merge queue Jul 8, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 8, 2024
@Ughuuu
Copy link
Contributor

Ughuuu commented Jul 8, 2024

I second them becoming debug_asserts, but probably would need to be a bigger change over more things.

@Bromeon Bromeon added this pull request to the merge queue Jul 8, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jul 8, 2024
@Bromeon Bromeon added this pull request to the merge queue Jul 9, 2024
Merged via the queue into godot-rust:master with commit 7641696 Jul 9, 2024
15 checks passed
@Bromeon
Copy link
Member

Bromeon commented Jul 9, 2024

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: core Core components feature Adds functionality to the library
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants