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

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

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.

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.

None yet

4 participants