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

change(web): reworks nearest-key detection to avoid layout reflow 🪠 #11129

Merged
merged 3 commits into from
Apr 3, 2024

Conversation

jahorton
Copy link
Contributor

@jahorton jahorton commented Apr 1, 2024

Addresses the performance concerns noted at #10960 (comment).

This changes the Web engine's 'nearest key' detection method to run entirely based upon precomputed layout values, completely sidestepping any need to trigger any (costly) layout reflow operations. This significantly improves keyboard-responsiveness in rapid-typing / "key-mash" scenarios.

User Testing

TEST_GESTURE_REGRESSIONS: Run the full set of Web's gesture-related regression tests and report back on any errors. Please give details for any error encountered.

@keymanapp-test-bot keymanapp-test-bot bot added has-user-test user-test-required User tests have not been completed labels Apr 1, 2024
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Apr 1, 2024

User Test Results

Test specification and instructions

  • 🟥 TEST_GESTURE_REGRESSIONS (FAILED): Tested with the attached PR build (Keyman 17.0.296-beta-test-11129) on an Android (13) Mobile device and here is my observation: 1. Tested the baseline tests on the "Test unminified KeymanWeb" test page on the Chrome browser and noticed that some tests like Test_Numeric_From_Shift, Test_Modipress_Multitap_Flick_Preview and Test_Flick_during_Modipress are failed due to a lack of motion in animation while dragging the specified key by up-and-right motion or sometimes with straight up or down motion. (notes)
Retesting Template
@keymanapp-test-bot retest TEST_GESTURE_REGRESSIONS

Test Artifacts

@keymanapp-test-bot keymanapp-test-bot bot added this to the B17S5 milestone Apr 1, 2024
@jahorton
Copy link
Contributor Author

jahorton commented Apr 1, 2024

A couple of profiles with these changes in place, on the same device (SM-T350 / Galaxy Tab A, released May 2015):

image

This run did include some actual layer changes, and it's those which seemed to have the largest run-time impact. (With this test device, even triggering a layer change in isolation has perceptible lag.)

image

The second-highest run-time contribution came from gesture processing itself. In order of their listing:

  1. The handler that triggers key-preview display and updates - thus, triggering (limited) layout and style operations.
  2. touch-events: 'touchmove' handling & related overhead
  3. touch-event queue ordering / overhead + the main bulk of 'touchstart' handling.
  4. gesture-selection + related handling. Occasionally includes reflows if layer shifts are triggered (start-of-sentence, modipress, modifier keys)
  5. gesture-model updates + evaluation
  6. Seems to be a subset of number 4, also relating to gesture-selection.
  7. The rest seem to be event-handling & event-ordering overhead.

There certainly is more overhead from ensuring proper event-handling order than we can consider negligible, but even then, it's only a contributing factor.

For comparison, here's a row with only a single layer-shift (due to start-of-sentence) and a similar number of keystrokes (~20). Same device.

image

Any layer-shifting operations will have a greater performance hit than the remainder of the code.

@github-actions github-actions bot added the web/ label Apr 1, 2024
@jahorton jahorton marked this pull request as ready for review April 1, 2024 07:00
@jahorton jahorton requested a review from ermshiperete as a code owner April 1, 2024 07:00
@jahorton
Copy link
Contributor Author

jahorton commented Apr 1, 2024

Diving even deeper in, it's worth note that every keystroke does manipulate the DOM a bit, especially when predictive text is active. Each suggestion has corresponding DOM elements that get maintained.

Also, here's an extreme close-up of a single layer-change operation:

image

7120 ms - 6940 ms = 180ms - almost 0.2 seconds. This is definitely enough to be perceptible.

image

Waaaait a second. The keyboard's size dimensions shouldn't be affected here, so maybe we could

Other notable layout aspects:

image

image

image

@bharanidharanj
Copy link

Test Results

  • TEST_GESTURE_REGRESSIONS (FAILED): Tested with the attached PR build (Keyman 17.0.296-beta-test-11129) on an Android (13) Mobile device and here is my observation: 1. Tested the baseline tests on the "Test unminified KeymanWeb" test page on the Chrome browser and noticed that some tests like Test_Numeric_From_Shift, Test_Modipress_Multitap_Flick_Preview and Test_Flick_during_Modipress are failed due to a lack of motion in animation while dragging the specified key by up-and-right motion or sometimes with straight up or down motion.

I have attached a video file (test_flick_during_modipress) represents there is a lack of motion in the animation and it does not show the expected output Úq on the text input screen.

modipressmultitap.mp4

@keymanapp-test-bot keymanapp-test-bot bot added user-test-failed and removed user-test-required User tests have not been completed labels Apr 1, 2024
@jahorton
Copy link
Contributor Author

jahorton commented Apr 2, 2024

From that test report... what I glean is that the flick-locking mechanism appears to be interfering with the test. There's a chance we may need some improvements to flick-resetting. The animations look smooth to me until they stop for a while, then a "jump" occurs to a different flick - which is indicative of this.

@jahorton
Copy link
Contributor Author

jahorton commented Apr 2, 2024

Per discussion with @mcdurdin, that user-test failure seems acceptable, noting issue #11139 for further tracking for potential improvement. The aspects being addressed by this PR are working as intended, with the noted issue being largely unrelated to these changes.

That said, I can't actually merge this PR yet; it hasn't been reviewed.

Base automatically changed from fix/web/queued-closure-source-linking to beta April 3, 2024 02:22
Copy link
Member

@mcdurdin mcdurdin left a comment

Choose a reason for hiding this comment

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

I think conceptually this is a significant improvement and simplification to the nearest-key algorithm. I have a few questions on code choices I don't really understand and one potential bug.

web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts Outdated Show resolved Hide resolved
web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts Outdated Show resolved Hide resolved
Comment on lines 249 to 256
if (minDistance < Number.MAX_VALUE) {
let fudgeFactor = closestKey.spec.proportionalWidth * NEAREST_KEY_HORIZ_FUDGE_FACTOR;
fudgeFactor = fudgeFactor > minHorizFudge ? fudgeFactor : minHorizFudge;

if (((x1 - x) >= 0 && (x1 - x) < dxMax) || ((x - x2) >= 0 && (x - x2) < dxMax)) {
return <KeyElement>t.firstChild;
if(minDistance <= fudgeFactor) {
return closestKey.btn;
}
}
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't the acceptable area be a fixed value, not relative to the size of the key? That is, pressing on the edge of spacebar shouldn't have a different outcome to pressing the edge of the Z key in terms of how close the finger has to be.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm just working with "what was" here. What you're saying certainly makes sense, though.

Given most of our keyboards tend to have 10 keys in their top row, using its full width... that implies a default total key width + padding of 10% computedWidth. (Thus, 0.1 "proportional width + padding"). Would a 6% * computedWidth fudge factor be reasonable?

Copy link
Member

Choose a reason for hiding this comment

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

yep, sounds like we came to the same conclusion 🤣 in my previous comment #11129 (comment)

So that's a good sign!

Comment on lines +236 to +240
const distance = distanceFromCenter - keyRadius;
if(distance < minDistance) {
minDistance = distance;
closestKey = key;
}
Copy link
Member

Choose a reason for hiding this comment

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

Once the distance starts to increase again, we can break out of the loop, right?

@jahorton jahorton requested a review from mcdurdin April 3, 2024 06:56
@jahorton jahorton merged commit 333d465 into beta Apr 3, 2024
13 of 15 checks passed
@jahorton jahorton deleted the change/web/no-nearest-key-reflow branch April 3, 2024 15:05
@keyman-server
Copy link
Collaborator

Changes in this pull request will be available for download in Keyman version 17.0.301-beta

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants