-
-
Notifications
You must be signed in to change notification settings - Fork 205
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
Conversation
|
||
// 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; |
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 % 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.
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.
if value
is i32::MIN
and step is -1
then a
is i32::MIN
since -1 / 2 = 0
for i32
.
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.
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] |
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.
#[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.
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.
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.
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.
#[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
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.
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 🙂
7da3b4d
to
d444540
Compare
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. |
d444540
to
8e5e481
Compare
assert!( | ||
value != i32::MIN || step != -1, | ||
"snapped() called on vector component i32::MIN with step component -1" | ||
); |
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.
Is only this a problem? step == -1
would work?
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.
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?
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.
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.
8e5e481
to
c0449ff
Compare
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-768 |
c0449ff
to
56b9e74
Compare
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. |
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.
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!
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" | ||
); |
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.
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" | |
); |
/// # 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`. |
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 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:
/// # 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`. |
56b9e74
to
12ffa74
Compare
12ffa74
to
26c5e10
Compare
As expected there where more possible overflow scenarios. For whatever reason i thought addition could only overflow for The description for the second panic condition is horrific but I don't know how to word it otherwise. |
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.
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.
I second them becoming debug_asserts, but probably would need to be a bigger change over more things. |
Thanks! |
This adds
snapped
for integer vectors based on this code.