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

fix(web): proper linkage of sources to events 🪠 #10960

Merged
merged 7 commits into from
Apr 3, 2024

Conversation

jahorton
Copy link
Contributor

@jahorton jahorton commented Mar 7, 2024

Continues from #10843.

Despite my efforts to avoid it, it appears necessary to retool the event engines to ensure a proper linkage between original event identifier and the corresponding GestureSource that will be generated and updated. The previous "identifier" to "source" mapping style appears to be too "loose" due to asynchronicity - it's time to update to a strategy that can directly use closure-capture mechanics to ensure proper binding.

Extra details: By default, browsers will reuse touch identifiers after they are freed - identifiers aren't unique. If this replacement happens while related events are deferred, we get problems like what can be seen in #10843's user test reports. Thus, it's necessary to establish the link between identifier and source while the identifier is still current for the touchpath represented by the source, then "lock in" that link - something that closure-capturing is well-suited for.

As the actual construction is queued for later, the best way I can see to leverage closure-capture mechanisms to resolve the issue is to creating Promises for the GestureSource synchronously during the actual touchStart handler - not within its closures. Resolution of the Promises can occur within closures, but assignments leveraging the Promise need to be maintained synchronously, as it can then be accessed synchronously within the other events for closure-capture in their handlers. (After all, under extreme rapid-typing situations, it's quite possible the Promise will only be fulfilled long after the fact, but this Promise will always be available, unambiguously, in time with the original events.)

User Testing

TEST_RAPID_TYPING_STABILITY: Using the Keyman for Android app, attempt to reproduce #10592 and #10646.

  1. Set "EuroLatin (SIL)" as the active keyboard.
  2. Rapidly type 10 random keys. Verify that 10 characters are emitted, possibly after a brief wait.
    • Don't worry about accuracy - just try to type 10 characters as fast as humanly possible, whatever they are.
    • Keep in mind that a space ( ) does count as a character; it's easy to accidentally hit it when not worried about accuracy.
  3. Rapidly type while utilizing functions like the globe key, space bar and predictive texts intermittently. (Refer to fix(web): prevent invalid longpress shortcut triggers #10641 (comment) if needed.)
  4. Try to rapidly type something specific while being precise.
    • Perhaps typing rapidly should not skip keys; just make sure it's something you feel comfortable typing rapidly.
    • If any of the letters appear out-of-order or appear to be skipped entirely, FAIL this test.
    • Do not fail this test if you suspect you simply fat-fingered a key, hitting a close neighbor instead of the intended key.
  5. FAIL this test if any of the following happens:
    • Stuck key-highlighting
    • Stuck key-previews
    • Stuck subkey menu
    • Stuck modipress

TEST_GESTURE_REGRESSIONS: Run the full set of Web's gesture-related regression tests and report back any errors.

  • For the longterm, it would likely be best to add TEST_RAPID_TYPING_STABILITY to that set of tests.
  • I assume you have a copy of these tests done up in your preferred format; if not, copy and paste the ones from SUITE_TOUCH_GESTURES here.

@jahorton jahorton requested a review from mcdurdin as a code owner March 7, 2024 07:32
@keymanapp-test-bot keymanapp-test-bot bot added has-user-test user-test-required User tests have not been completed labels Mar 7, 2024
@keymanapp-test-bot
Copy link

keymanapp-test-bot bot commented Mar 7, 2024

User Test Results

Test specification and instructions

  • TEST_RAPID_TYPING_STABILITY (PASSED): Retested this issue with the attached PR build (Keyman 17.0.285-beta-test-10960) in an Android 13 Mobile device and here is my observation: 1. I was able to reproduce the keyboard error message after typing rapidly using the OSK for a long time. (approx. 4 minutes) . 2. However, I was not able to reproduce it If I rapidly typed 10 random keys. (as it mentioned in the test steps). Seems to be working fine. (notes)
  • 🟥 TEST_GESTURE_REGRESSIONS (FAILED): Retested using the 'Test uniminified KeymanWeb' test page on an Android (13) mobile device and here is my observation: 1. Followed the instructions as per in the Suite_Touch_Gestures page. 2. I did not see any issue up to Test_Basic_Modipress_Hold. 3. There is a sluggishness on the keyboard after longpressing % key (step 6) and then releasing it. Also, noticed that the keyboard changes back to the default layer. Seems to be an issue. I have attached the video file for reference. (notes)
Retesting Template
@keymanapp-test-bot retest TEST_GESTURE_REGRESSIONS

Test Artifacts

Comment on lines 235 to 243
/*
Using the Promise map built in touchStart, we can retrieve a Promise for the source linked
to this event and closure-capture it for the closure queued below.
*/
const capturedSourcePromises = new Map<number, Promise<GestureSource<ItemType, StateToken>>>();
for(let i = 0; i < event.touches.length; i++) {
const touchId = event.touches.item(i).identifier;
capturedSourcePromises.set(touchId, this.pendingSourcePromises.get(touchId).corePromise);
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The big difference from #10843 is that we can closure-capture the Promises at the original time of the event, unlike the GestureSource instances. #10843 tried to make the mapping work asynchronously, but there were cases that arose in which the former, more loosely-bound style simply couldn't work consistently.

That was likely because the mapping could see an entry erased by a different GestureSource (conceptually) that was reusing the same identifier.

@jahorton jahorton marked this pull request as draft March 7, 2024 07:54
@jahorton jahorton marked this pull request as ready for review March 7, 2024 08:22
@github-actions github-actions bot added web/ and removed web/ labels Mar 7, 2024
@bharanidharanj
Copy link

bharanidharanj commented Mar 7, 2024

Test Results

  • TEST_RAPID_TYPING_STABILITY (FAILED): Tested with the attached PR build (Keyman 17.0.271-beta-test-10960) on an Android 13 Mobile device and here is my observation: 1. Opened the Keyman In-app. 2. Rapidly typing on OSK leads to stuck-key-highlighting issues. 3. Tested the same on an Android 14 / API 34 emulator and I was able to reproduce it.

@keymanapp-test-bot keymanapp-test-bot bot added user-test-failed and removed user-test-required User tests have not been completed labels Mar 7, 2024
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 understand why you are making these changes and in principle they seem fine. This is a significant change to the structure of the gesture engine for beta, however, so we will definitely need to make sure we do extra testing for stability and unforeseen edge cases.

The only user-visible impact (apart from fixing instability, which is obviously good) seems to be the introduction of a delay? That seems less than ideal -- can you describe the impact?

Comment on lines +154 to +155
The easiest way to ensure the exact order involves programmatic delay of their
processing, essentially "sequentializing" the events into a deterministic order.
Copy link
Member

Choose a reason for hiding this comment

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

Is this delay visible to the end user?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should be imperceptible; it's a matter of queuing tasks against the microtask queue, the macrotask queue, and the custom queue from #10843. Each of those should take negligible amounts of time.

The only delay added in this PR (at present) is due to the need to await a few pre-resolved Promises... so a couple of microtask delays per queued event should be the only extra wait here. (They're not resolved when closure-captured, but they should already be resolved at every point they're being await-ed.)

Copy link
Contributor Author

@jahorton jahorton Mar 8, 2024

Choose a reason for hiding this comment

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

If desired, I probably could 'rig' a way to avoid using Promises and the microtask queue for this, which would remove even the microtask queue waits. That might actually make the code that little bit more difficult to parse for maintenance, though - I feel that Promise semantics work in our favor here for code clarity.

A simple object (providing a closure-capturable root reference) with a mutatable field would be enough. Said field would start off undefined but be assigned the source's reference when it's available (during the final onTouchStart closure). Of course, we'd have to explicitly document and maintain that connection, which is why I feel it a bit less "simple."

Copy link
Member

@mcdurdin mcdurdin Mar 8, 2024

Choose a reason for hiding this comment

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

My preference is almost always for synchronous code where possible -- re-entrant code makes code flow more complicated with potential for other interactions (from other promises and events) which all goes away without it. But I'd need to compare the two solutions here to be sure.

I think most of the wrinkles we've had in the new gestures have been async-related?

@keymanapp-test-bot keymanapp-test-bot bot added the user-test-required User tests have not been completed label Mar 8, 2024
@jahorton
Copy link
Contributor Author

jahorton commented Mar 8, 2024

I didn't see anything about an Android error this time, which is a plus. Though... that might be due to the more common error that surfaced. It'd surface quickly, so if the Android error never super-consistent to begin with, it may have just "not happened (yet)".

Turns out that I made a fairly simple mistake in one of the event handlers - I forgot that ending touches don't show up in their event's .touches property - only within the .changedTouches property. Our unit-test event simulation didn't meet that aspect of the spec and so failed to catch this detail.

tl;dr: The error I found when I went looking quite naturally made the last key touched act "sticky", among other things. I've fixed it and made it easier for unit tests to catch the same mistake in the future.

@keymanapp-test-bot retest all

@bharanidharanj
Copy link

Test Results

  • TEST_RAPID_TYPING_STABILITY (PASSED): Retested with the updated PR build (Keyman 17.0.285-beta-test-10960) in various Android OS versions and here is my observation: 1. Followed the steps as mentioned in the test description and I did not find any error message in Android emulators (Android 5.0, Android 7.1, Android 9.0 and Android 14.0). 2. I did not find any stuck-key-highlighting or any other issues (eg., stuck key-previews, stuck subkey menu, and stuck modipress) during the process. 3. FYI, I got a Keyboard error message while testing it in an Android 13 Mobile device. Apart from this, this build is working fine.

..Android 5.0 / API 21 emulator

..Android 7.1.1 / API 25 emulator

..Android 9.0 / API 28 emulator

..Android 14.0 / API 34 emulator

..Android 13.0 / Samsung Galaxy A23 Mobile device

Copy link

sentry-io bot commented Mar 8, 2024

Sentry Issue: KEYMAN-WEB-HY

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

jahorton commented Mar 13, 2024

I've been noticing a few gesture things along the lines of the failed test recently too on my personal iPhone... but hadn't realized it was happening that extensively. Interestingly, I'm not seeing some of the issues in iOS Simulator.

Did some digging via TestFlight and found that 17.0.261-beta seems to be comparatively fine, with a number of breaks happening in 17.0.262-beta.

... and after a bit of investigation, for iOS, the stuff I noticed is due to context issues that resolve with #10956. That doesn't exactly address the Android bits noted here, though. (17.0.262 did involve some change to iOS's context handling, so it does track somewhat.) Context resets generally involve layer resets, after all, so gestures involving layer manipulation could definitely be affected by context issues.

@darcywong00 darcywong00 modified the milestones: B17S3, B17S4 Mar 16, 2024
@jahorton
Copy link
Contributor Author

jahorton commented Mar 27, 2024

@keymanapp-test-bot retest TEST_GESTURE_REGRESSIONS

I've gone ahead and merged in recent beta changes to this PR. I've also tested a local build of the resulting artifact locally on a (slow) Android test device here - so far, the only issues I have seen appear to be due to lagginess. It appears that aspect is due to certain calculations needed during a modipress.

For any tests that fail, please describe the nature of the test failures for each. Please make it explicitly clear if the reason for failure is that things are happening correctly, but too slowly.


"Certain calculations" - we rely on key layout information from the DOM when noting the 'item' for each ongoing touchpath. To get this for layers currently not visible, we make them visible temporarily, which triggers layout reflows - and that appears to be a major contributing factor to lagginess on the local test device.


..Test_Alternating_Shift_and_Key

TestAlternate.mp4

I see the video, but I'm not clear on the reason this was labeled as a failure. Is it due to each tap taking a while to process, rather than executing quickly? The individual H outputs do have a notable delay between each, and that delay is longer than I'd expect you to be intentionally doing when following the test instructions. It'd help to have that "spelled out" explicitly, as right now, all I can do is make my best guess as to your reasoning.

@keymanapp-test-bot keymanapp-test-bot bot added user-test-required User tests have not been completed and removed user-test-failed labels Mar 27, 2024
@mcdurdin
Copy link
Member

"Certain calculations" - we rely on key layout information from the DOM when noting the 'item' for each ongoing touchpath. To get this for layers currently not visible, we make them visible temporarily, which triggers layout reflows - and that appears to be a major contributing factor to lagginess on the local test device.

Hmm, could we cache this information when the keyboard is first loaded? (Perhaps being smart enough to think about rotation as well?) This may not be a 17.0-beta change but I'm all for avoiding layout reflows during processing!

@jahorton
Copy link
Contributor Author

jahorton commented Mar 27, 2024

In regard to that last point...

image

Drilling down the other high-percentage contributors reveals similar figures - a lot of time was spent on layout-reflow. I may want to spend some time seeing if that can be optimized.

Interestingly, I don't see anything even close to this level of lagginess within the iOS app/keyboard; maybe Safari webviews are caching layout calculations in a manner that Chrome webviews aren't?

@mcdurdin
Copy link
Member

Drilling down the other high-percentage contributors reveals similar figures - a lot of time was spent on layout-reflow. I may want to spend some time seeing if that can be optimized.

Sounds good to me! Let's make sure we get the other beta PRs merged before you get too deep into this one though.

Interestingly, I don't see anything even close to this level of lagginess within the iOS app/keyboard; maybe Safari webviews are caching layout calculations in a manner that Chrome webviews aren't?

Potentially -- but also iPhones are much faster hardware so it may just be much less visible.

@jahorton
Copy link
Contributor Author

"Certain calculations" - we rely on key layout information from the DOM when noting the 'item' for each ongoing touchpath. To get this for layers currently not visible, we make them visible temporarily, which triggers layout reflows - and that appears to be a major contributing factor to lagginess on the local test device.

Hmm, could we cache this information when the keyboard is first loaded? (Perhaps being smart enough to think about rotation as well?) This may not be a 17.0-beta change but I'm all for avoiding layout reflows during processing!

I could see caching each layer's layout info as it's loaded. That would be well within reason. (Then dumping the cache on a rotation or keyboard size-shift.)

@jahorton
Copy link
Contributor Author

Drilling down the other high-percentage contributors reveals similar figures - a lot of time was spent on layout-reflow. I may want to spend some time seeing if that can be optimized.

Sounds good to me! Let's make sure we get the other beta PRs merged before you get too deep into this one though.

Agreed. If this is the only real source of problems at present, that can certainly be done separately, allowing this chain to merge.

@mcdurdin
Copy link
Member

Just wondering: can we not calculate the regions without doing layout at all? We have all the numbers in the touch layout data and we have the bounding box size?

@jahorton
Copy link
Contributor Author

Just wondering: can we not calculate the regions without doing layout at all? We have all the numbers in the touch layout data and we have the bounding box size?

At the very least, we can approximate it with that data - and we might even be able to perfectly match it. Sounds like a wise optimization path to investigate.

@jahorton
Copy link
Contributor Author

I'm not sure why KMW is 3KB larger -- there's not that much new code?

This is 6 PRs in on the 🪠 chain. This is the one that pushes things over on the filesize warning, I guess.

@bharanidharanj
Copy link

Test Results

  • TEST_GESTURE_REGRESSIONS (FAILED): Retested using the 'Test uniminified KeymanWeb' test page on an Android (13) mobile device and here is my observation: 1. Followed the instructions as per in the Suite_Touch_Gestures page. 2. I did not see any issue up to Test_Basic_Modipress_Hold. 3. There is a sluggishness on the keyboard after longpressing % key (step 6) and then releasing it. Also, noticed that the keyboard changes back to the default layer. Seems to be an issue. I have attached the video file for reference.
ModiPressHold.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

The remaining issues seem to mostly be performance-related. There is one notable, though ambiguous, functionality point of failure... but this does resolve a lot of far, far worse behaviors and will let us get beta to a better state. As such, @mcdurdin and I have made the decision to go ahead and merge this PR and its predecessors. (The next one in line is still pending review.)

Base automatically changed from change/web/input-sequentialization to beta April 3, 2024 02:22
@jahorton jahorton merged commit 769b611 into beta Apr 3, 2024
17 of 19 checks passed
@jahorton jahorton deleted the fix/web/queued-closure-source-linking branch April 3, 2024 02:22
@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