-
-
Notifications
You must be signed in to change notification settings - Fork 218
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 missing integer vector methods #351
Conversation
API docs are being generated and will be shortly available at: https://godot-rust.github.io/docs/gdext/pr-351 |
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! Added some comments, mostly nitpicking wrt docs.
What I'm not really sure is the {min|max}_axis_index
API. I'm aware this is precisely what GDScript offers (and we already have for float vectors), but the semantics are quite weird:
max_axis_index
returnsX
on equalitymin_axis_index
returnsY
on equality
The implementations of 2D, 3D and 4D vectors also looks completely different, which combined with absence of bugs makes it quite easy to introduce bugs. If not now, then in future.
Is there a good use case for the GDScript semantics of returning an arbitrary axis on equality? If not, we may consider Option
as a return type, like Rust's max()
functions...
@@ -56,6 +56,31 @@ impl Vector2i { | |||
Self { x, y } | |||
} | |||
|
|||
///Returns the aspect ratio of this vector, the ratio of x to y. |
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 would keep docs as concise as possible and without repetition. x / y
is probably slightly easier to understand 🙂
///Returns the aspect ratio of this vector, the ratio of x to y. | |
/// Aspect ratio: x / y, as a `real` value. |
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.
Agreed, I just copied that from Godot docs😅
/// Returns the axis of the vector's highest value. See [`Vector2Axis`] enum. If all components | ||
/// are equal, this method returns [`Vector2Axis::X`]. |
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.
In addition to previous points, the See [`Vector2Axis`] enum
is not needed, as the user can click on any involved type (such as the return type).
@@ -75,10 +100,15 @@ impl Vector2i { | |||
} | |||
|
|||
/// Converts `self` to the corresponding `glam` type. | |||
fn to_glam(self) -> glam::IVec2 { | |||
fn to_glam(self) -> IVec2 { |
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.
Please keep glam types qualified, so the code is more expressive.
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've checked how other integer vector modules used glam types and they all omitted glam module. I'll include it for other integer modules too so it can be uniform.
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.
Makes sense, thanks!
/// Returns a new vector with each component set to 1 if it's positive, -1 if it's | ||
/// negative, and 0 if it's zero. | ||
#[inline] | ||
pub fn sign(self) -> Self { | ||
Self::from_glam(self.to_glam().signum()) | ||
} |
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.
The implementation is wrong; signum()
never returns 0.
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.
signum
does return 0
for integers. it's only for floats signum
is never 0. see https://doc.rust-lang.org/std/primitive.i64.html#method.signum
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.
Are we sure? Both IVec2::signum (i32::signum) and godot docs says it does return 0.
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 see, thanks! Now I'm even more confused why Rust chose two different behaviors here. Btw, a unit test wouldn't have raised this question in the first place 😛
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 never wrote an unit test before, only integration tests; so I don't know the workflow. I've checked multiple files but couldn't find any tests that I can figure out from. Would you mind suggesting a file with good written tests so I can do something similar? 😅
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 see, thanks! Now I'm even more confused why Rust chose two different behaviors here. Btw, a unit test wouldn't have raised this question in the first place 😛
well signum represents the sign of a value with numbers with: 1 being positive, -1 being negative, 0 being unsigned, and NAN for NAN.
so since 0 is unsigned for integers, signum returns 0. but since floats have signed 0s, i.e +0 and -0 are distinct values, signum should return 1 or -1 respectively.
floats are weird but it's consistent
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.
@kulkalkul it was a joke, I didn't mean to burden you with extra work 😉 as you said, most of your additions are simple enough to not need testing. In this case I was just surprised about the difference from floats, but with @lilizoey's explanation, it actually makes sense (from a IEEE but not mathematical perspective at least).
Maybe for the future, you could have a look at rect2.rs
, which contains both unit tests (#[test]
) and doctests (for Display
impl). Integration tests #[itest]
are in separate files, see here for explanation. For now it's really OK though 🙂
9a7d69c
to
8be1b68
Compare
Both ways are OK, imo. I think someone experienced with Godot would expect it would have the same implementation as Godot. On the other hand, Option gives extra possibilities without regressing the previous use case. And it is the rusty way of doing things. I don't think there is a clear reason for it except for C++ developers are not used to using algebraic data types. So, making it an Option makes sense. |
Yes, that's my thinking as well. Godot doesn't have var axis = vec.max_axis_index()
var value = vec[axis] which then "works" in terms of "just getting the largest component". In Rust, this may be a tiny bit more verbose, but explicit: let axis = vec.max_axis().unwrap_or(Vector2Axis::X);
let value = vec[axis]; We can probably call it Implementation could probably be done using |
Ah, looks like I forgot running ./check.sh before the second commit, my bad. I'll make clippy happy next time, no worries |
8be1b68
to
421fad5
Compare
Well, I thought a lot about how to solve it best, figured going straightforward would be the most mainteinable. I haven't used max_by_key for that reason. I also added some unit tests for axis functions (several lines are verbose, but kept them for the sake of completeness), which proved pretty useful to avert some pitfalls, especially with Vector4. And I just noticed that, files I was working with already had tests and I saw them previously... But my mind didn't register them as unit tests for some reason 😅 |
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 updating!
I like the use of cmp
. Still not 100% happy about 3 different impls for 2D/3D/4D and duplication for max/min, but since we have tests now, it's mostly a cosmetic issue that can be addressed later 🙂
As mentioned in the code review, I'd keep tests a bit shorter and assertions more local. At the moment a reader needs to jump to declaration of a variable quite far to see what's happening -- the idea of tests is however to make as obvious as possible what should be tested (the "spec").
@@ -56,6 +57,29 @@ impl Vector2i { | |||
Self { x, y } | |||
} | |||
|
|||
///Aspect ratio: x / y, as a `real` value. |
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.
Space after ///
. I even provided a verbatim suggestion 😉
fn axis_min_max() { | ||
let x_max = Vector2i::new(10, 5); | ||
let y_max = Vector2i::new(5, 10); | ||
|
||
let x_min = Vector2i::new(-5, 5); | ||
let y_min = Vector2i::new(5, -5); | ||
|
||
let max_eq = Vector2i::new(15, 15); | ||
let min_eq = Vector2i::new(15, 15); | ||
|
||
assert_eq!(x_max.max_axis(), Some(Vector2Axis::X)); | ||
assert_eq!(y_max.max_axis(), Some(Vector2Axis::Y)); | ||
|
||
assert_eq!(x_min.min_axis(), Some(Vector2Axis::X)); | ||
assert_eq!(y_min.min_axis(), Some(Vector2Axis::Y)); | ||
|
||
assert_eq!(max_eq.max_axis(), None); | ||
assert_eq!(min_eq.min_axis(), None); | ||
} |
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.
Since you only use each variable once, it would be easier to inline them, as the condition would be self-contained. I.e. this test would just have 6 assertions and no other statements.
Same for the others.
421fad5
to
974af2d
Compare
It bugs me too, but I refrained from abstracting it away to something similar like 4D to not overcomplicate it. About tests, I'm more on verbose side but replaced it as you asked. I also put a minor comment on 3D and 4D tests about "early equality traps" that I noticed while implementing so those two tests wouldn't be removed in the future (because they look like they are not testing anything important). |
Thanks a lot!
Great forethought, I appreciate it. |
Saw these unimplemented on #310. I haven't tested any as it is straightforward. Let me know if rest of this PR is verbose for the little amount of work I did. 😅
Coverage
Extra
I created two new macros for common implementations. I don't know if that's the preferable way of doing stuff, but I saw macros implemented some methods, so I did the same.
Vector4i::max_axis_index
andVector4i::min_axis_index
are rustified version of C++ implementation. I used an unwrap there while usingVector4Axis::try_from_ord
. But it has no chance of failing so I just added a comment without documenting it as "panics".Added a utility method similar to
to_glam
namedto_glam_real
. Using it for length implementation on RVec's.Due to trailing addition (+) causing issues with the vector component macro, I created a variable and increased it for each component. It results in a similar assembly with optimization level 0, and the same assembly with optimization level 1 and above.