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

JDK-8298060: Fix precision bug in gesture recognizer classes #966

Conversation

hjohn
Copy link
Collaborator

@hjohn hjohn commented Dec 3, 2022

This includes a fix for the precision problem we've found as part of the graphics warnings clean ups.

I've included two commits, one with just the minimal fix, and one with the clean ups. I can drop off the 2nd commit if it is deemed to be of no added value.


Progress

  • Change must be properly reviewed (1 review required, with at least 1 Reviewer)
  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue

Issue

  • JDK-8298060: Fix precision bug in gesture recognizer classes

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/966/head:pull/966
$ git checkout pull/966

Update a local copy of the PR:
$ git checkout pull/966
$ git pull https://git.openjdk.org/jfx.git pull/966/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 966

View PR using the GUI difftool:
$ git pr show -t 966

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/966.diff

Webrev

Link to Webrev Comment

- Made constants written in capitals final
- Made fields final that can be final
- Made fields and methods private that can be private
- Renamed constants that aren't constant to camel case
- Fixed spelling errors
- Re-ordered fields (statics at the top)
- Removed redundant initial values
- Removed unused fields and parameters
- Optimized imports
- Removed commented out code
@bridgekeeper
Copy link

bridgekeeper bot commented Dec 3, 2022

👋 Welcome back jhendrikx! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@hjohn hjohn marked this pull request as ready for review December 5, 2022 12:42
@openjdk openjdk bot added the rfr Ready for review label Dec 5, 2022
@mlbridge
Copy link

mlbridge bot commented Dec 5, 2022

Webrevs

@arapte
Copy link
Member

arapte commented Dec 12, 2022

The commit#1 changes look good.
I would recommend to revert commit#2 for few reasons:

  1. Cleanup changes are a lot more than actual fix change.
  2. It will be difficult to revisit in future.
  3. If required the cleanup changes could go in as a separate fix.

Could you please provide link to the discussion that led to this change?

@hjohn
Copy link
Collaborator Author

hjohn commented Dec 12, 2022

The commit#1 changes look good. I would recommend to revert commit#2 for few reasons:

  1. Cleanup changes are a lot more than actual fix change.
  2. It will be difficult to revisit in future.
  3. If required the cleanup changes could go in as a separate fix.

Could you please provide link to the discussion that led to this change?

@arapte the discussion was part of fixing warnings in javafx.graphics. You can find it here (open first resolved comment): #960 (review)

interface GestureRecognizer extends GlassTouchEventListener {
static final long INITIAL_VELOCITY_THRESHOLD_NANOS = 100L * 1000;
Copy link
Contributor

Choose a reason for hiding this comment

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

100_000L ?

static {
@SuppressWarnings("removal")
var dummy = AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
String s = System.getProperty("com.sun.javafx.gestures.rotate.threshold");
if (s != null) {
ROTATATION_THRESHOLD = Double.valueOf(s);
rotationThresholdDegrees = Double.valueOf(s);
Copy link
Contributor

Choose a reason for hiding this comment

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

should we catch a NumberFormatException and re-throw it with a meaningful text message? Or the current behavior is ok?

ditto on line 64

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I suppose that would be out of scope for this PR. I agree though that it would be good to mention that the double that couldn't be parsed is the one we got from System.getProperty("com.sun.javafx.gestures.rotate.threshold") as otherwise the error would mean nothing to anyone.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, that sort of change seems out of scope.

}
s = System.getProperty("com.sun.javafx.gestures.scroll.inertia");
if (s != null) {
SCROLL_INERTIA_ENABLED = Boolean.valueOf(s);
scrollInertiaEnabled = Boolean.valueOf(s);
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment about re-throwing an exception (x2)

//capture radius (pytaguras) or init to variables x,y ???
double scrollMagnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
factorX = deltaX / scrollMagnitude;
factorY = deltaY / scrollMagnitude;
initialInertiaScrollVelocity = scrollMagnitude / timePassed;
initialInertiaScrollVelocity = scrollMagnitude / nanosPassed * NANOS_TO_SECONDS;
Copy link
Contributor

Choose a reason for hiding this comment

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

is this correct? shouldn't it be scrollMagnitude / (nanosPassed * NANOS_TO_SECONDS)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it's correct, unless you have reason to believe why it wouldn't be. scrollMagnitude is a double, and the division and multiplication that follow it will be all done as doubles.

Copy link
Contributor

Choose a reason for hiding this comment

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

your change is equivalent to

(scrollMagnitude * NANOS_TO_SECONDS) / nanosPassed

is that correct?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

That would be equivalent yes.

Perhaps it is because timePassed was a double before measured in seconds while I'm now doing the calculation with nano seconds?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Okay I see what you're getting at, your first suggestion would not be correct, it's supposed to be what you suggest in the 2nd one. So what I want is:

 (scrollMagnitude / nanosPassed) * NANOS_TO_SECONDS

or:

 (scrollMagnitude * NANOS_TO_SECONDS) / nanosPassed

...but that's the same as what I wrote.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just needed you to confirm that this is indeed correct.

Perhaps a default method double nanosToSeconds(long) in GestureRecognizer would be clearer?

Copy link
Member

Choose a reason for hiding this comment

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

But is it correct? The previous code computed a deltaTime in seconds as ((currentNanos - startNanos) / 1e9), and then divided the scrollMagnitude by that. I think that the equivalent to the old code is:

    scrollMagnitude / (nanosPassed * NANOS_TO_SECONDS)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It turns out it is not correct, the parenthesis are indeed required, not sure how I could have missed it. I'm going to see if I can add a test to this as they're missing to ensure it does the same before/after (aside from precision).

Copy link
Contributor

Choose a reason for hiding this comment

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

could we create a new method, like 'nanosToSeconds'?

long nanosPassed = nanos - rotationStartNanos;

if (nanosPassed > INITIAL_VELOCITY_THRESHOLD_NANOS) {
initialInertiaRotationVelocity = currentRotation / nanosPassed * NANOS_TO_SECONDS;
Copy link
Contributor

Choose a reason for hiding this comment

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

is this correct? shouldn't it be currentRotation / (nanosPassed * NANOS_TO_SECONDS) ?

}
s = System.getProperty("com.sun.javafx.gestures.zoom.inertia");
if (s != null) {
ZOOM_INERTIA_ENABLED = Boolean.valueOf(s);
zoomInertiaEnabled = Boolean.valueOf(s);
Copy link
Contributor

Choose a reason for hiding this comment

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

... and here

long nanosPassed = nanos - zoomStartNanos;

if (nanosPassed > INITIAL_VELOCITY_THRESHOLD_NANOS) {
initialInertiaZoomVelocity = (totalZoomFactor - prevTotalZoomFactor) / nanosPassed * NANOS_TO_SECONDS;
Copy link
Contributor

Choose a reason for hiding this comment

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

(totalZoomFactor - prevTotalZoomFactor) / (nanosPassed * NANOS_TO_SECONDS); ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The order is irrelevant, it could as well be ((totalZoomFactor - prevTotalZoomFactor) / nanosPassed) * NANOS_TO_SECONDS -- I'll let the compiler decide what is more optimal.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See other conversation, your suggestion would not be correct as it changes the order of the operations.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

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

inline

static {
@SuppressWarnings("removal")
var dummy = AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
String s = System.getProperty("com.sun.javafx.gestures.rotate.threshold");
if (s != null) {
ROTATATION_THRESHOLD = Double.valueOf(s);
rotationThresholdDegrees = Double.valueOf(s);
Copy link
Member

Choose a reason for hiding this comment

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

Yes, that sort of change seems out of scope.

//capture radius (pytaguras) or init to variables x,y ???
double scrollMagnitude = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
factorX = deltaX / scrollMagnitude;
factorY = deltaY / scrollMagnitude;
initialInertiaScrollVelocity = scrollMagnitude / timePassed;
initialInertiaScrollVelocity = scrollMagnitude / nanosPassed * NANOS_TO_SECONDS;
Copy link
Member

Choose a reason for hiding this comment

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

But is it correct? The previous code computed a deltaTime in seconds as ((currentNanos - startNanos) / 1e9), and then divided the scrollMagnitude by that. I think that the equivalent to the old code is:

    scrollMagnitude / (nanosPassed * NANOS_TO_SECONDS)

@openjdk
Copy link

openjdk bot commented Dec 15, 2022

@hjohn this pull request can not be integrated into master due to one or more merge conflicts. To resolve these merge conflicts and update this pull request you can run the following commands in the local repository for your personal fork:

git checkout feature/precision-problem-in-gesture-recognizers
git fetch https://git.openjdk.org/jfx master
git merge FETCH_HEAD
# resolve conflicts and follow the instructions given by git merge
git commit -m "Merge master"
git push

@openjdk openjdk bot added the merge-conflict Pull request has merge conflict with target branch label Dec 15, 2022
@openjdk openjdk bot removed the merge-conflict Pull request has merge conflict with target branch label Dec 15, 2022
@hjohn
Copy link
Collaborator Author

hjohn commented Dec 15, 2022

@kevinrushforth

Please advise if I should continue on this road:

I've reverted RotateGestureRecognizer to add a test case first.

To make RotateGestureRecognizer testable I had to make it public and abstract away the need for ViewScene as constructor parameter, see the Make RotateGestureRecognizer easier to test commit. I also added a test case (90% coverage).

If you are okay with these changes (I needed to change GlassViewEventHandler and add a small interface), I can do the same for the Zoom & ScrollGestureRecognizers.

The test I added reveals the precision problem when you uncomment the line: passTime(Long.MAX_VALUE / 1000 / 1000 - 10000000); -- the accuracy problem of converting longs to doubles then breaks the test. The newer version of the code works (after I fixed the calculation by adding parenthesis).

@kevinrushforth
Copy link
Member

To make RotateGestureRecognizer testable I had to make it public and abstract away the need for ViewScene as constructor parameter, see the Make RotateGestureRecognizer easier to test commit. I also added a test case (90% coverage).

This seems quite an intrusive change just to support low level testing of this. I can take a closer look next week, but I'm not really sold on the idea.

@bridgekeeper
Copy link

bridgekeeper bot commented Mar 31, 2023

@hjohn This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@hjohn
Copy link
Collaborator Author

hjohn commented Apr 1, 2023

I think this change still has merit. I also think the change to make the code more testable is a good thing. A lot of code in JavaFX is hard to test, and when things are hard to test, the testing is often done poorly or not at all -- these classes did not have any tests.

@bridgekeeper
Copy link

bridgekeeper bot commented Apr 29, 2023

@hjohn This pull request has been inactive for more than 4 weeks and will be automatically closed if another 4 weeks passes without any activity. To avoid this, simply add a new comment to the pull request. Feel free to ask for assistance if you need help with progressing this pull request towards integration!

@bridgekeeper
Copy link

bridgekeeper bot commented May 27, 2023

@hjohn This pull request has been inactive for more than 8 weeks and will now be automatically closed. If you would like to continue working on this pull request in the future, feel free to reopen it! This can be done using the /open pull request command.

@bridgekeeper bridgekeeper bot closed this May 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rfr Ready for review
4 participants