From b414455c23d839bec6c3f1e6e4f88ad290d101f8 Mon Sep 17 00:00:00 2001 From: jahorton Date: Tue, 20 Jul 2021 09:02:50 +0700 Subject: [PATCH 1/6] fix(common/core/web): adds empty-array check for alternates --- .../src/text/prediction/languageProcessor.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/common/core/web/input-processor/src/text/prediction/languageProcessor.ts b/common/core/web/input-processor/src/text/prediction/languageProcessor.ts index 17faf9e15de..30f0d070106 100644 --- a/common/core/web/input-processor/src/text/prediction/languageProcessor.ts +++ b/common/core/web/input-processor/src/text/prediction/languageProcessor.ts @@ -323,8 +323,16 @@ namespace com.keyman.text.prediction { this.lmEngine.resetContext(context); } + let alternates = transcription.alternates; + if(!alternates || alternates.length == 0) { + alternates = [{ + sample: transcription.transform, + p: 1.0 + }]; + } + let transform = transcription.transform; - var promise = this.currentPromise = this.lmEngine.predict(transcription.alternates || transcription.transform, context); + var promise = this.currentPromise = this.lmEngine.predict(alternates || transcription.transform, context); let lp = this; return promise.then(function(suggestions: Suggestion[]) { From 52c3356c7b8d925eda91854f160b8dd95ac552bb Mon Sep 17 00:00:00 2001 From: jahorton Date: Wed, 21 Jul 2021 11:40:21 +0700 Subject: [PATCH 2/6] fix(common/models): adds worker null guard for predict --- common/predictive-text/worker/model-compositor.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/common/predictive-text/worker/model-compositor.ts b/common/predictive-text/worker/model-compositor.ts index 5c41e919576..7004388f78f 100644 --- a/common/predictive-text/worker/model-compositor.ts +++ b/common/predictive-text/worker/model-compositor.ts @@ -73,6 +73,14 @@ class ModelCompositor { if(!(transformDistribution instanceof Array)) { transformDistribution = [ {sample: transformDistribution, p: 1.0} ]; + } else if(transformDistribution.length == 0) { + transformDistribution.push({ + sample: { + insert: '', + deleteLeft: 0 + }, + p: 1.0 + }) } let inputTransform = transformDistribution.sort(function(a, b) { From 1c8689b404fe8344093cab23ab779cc8228e725c Mon Sep 17 00:00:00 2001 From: jahorton Date: Thu, 22 Jul 2021 08:22:28 +0700 Subject: [PATCH 3/6] fix(common/core/web): fixes likely root of problem --- common/core/web/input-processor/src/text/inputProcessor.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/core/web/input-processor/src/text/inputProcessor.ts b/common/core/web/input-processor/src/text/inputProcessor.ts index 65780232033..6928fe542e2 100644 --- a/common/core/web/input-processor/src/text/inputProcessor.ts +++ b/common/core/web/input-processor/src/text/inputProcessor.ts @@ -171,6 +171,11 @@ namespace com.keyman.text { if(pair.p < KEYSTROKE_EPSILON) { break; } else if(timer && timer() >= TIMEOUT_THRESHOLD) { + // It's always possible that the thread _executing_ our JS got paused, + // even if JS itself is single-threaded. + if(alternates.length == 0) { + alternates = undefined; + } break; } From fd940cb6188754b7a35983bd7d14df9938cd2f6c Mon Sep 17 00:00:00 2001 From: jahorton Date: Thu, 22 Jul 2021 10:36:36 +0700 Subject: [PATCH 4/6] change(common/core/web): adjustments per review --- .../input-processor/src/text/inputProcessor.ts | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/common/core/web/input-processor/src/text/inputProcessor.ts b/common/core/web/input-processor/src/text/inputProcessor.ts index 6928fe542e2..97c428d7d1b 100644 --- a/common/core/web/input-processor/src/text/inputProcessor.ts +++ b/common/core/web/input-processor/src/text/inputProcessor.ts @@ -171,11 +171,11 @@ namespace com.keyman.text { if(pair.p < KEYSTROKE_EPSILON) { break; } else if(timer && timer() >= TIMEOUT_THRESHOLD) { - // It's always possible that the thread _executing_ our JS got paused, - // even if JS itself is single-threaded. - if(alternates.length == 0) { - alternates = undefined; - } + // Note: it's always possible that the thread _executing_ our JS + // got paused by the OS, even if JS itself is single-threaded. + // + // The case where `alternates` is initialized (line 167) but empty + // (because of net-zero loop iterations) MUST be handled. break; } @@ -192,6 +192,8 @@ namespace com.keyman.text { // If alternateBehavior.beep == true, ignore it. It's a disallowed key sequence, // so we expect users to never intend their use. + // + // Also possible that this set of conditions fail for all evaluated alternates. if(alternateBehavior && !alternateBehavior.beep && pair.p > 0) { let transform: Transform = alternateBehavior.transcription.transform; @@ -220,7 +222,9 @@ namespace com.keyman.text { // -- All keystroke (and 'alternate') processing is now complete. Time to finalize everything! -- // Notify the ModelManager of new input - it's predictive text time! - ruleBehavior.transcription.alternates = alternates; + if(alternates && alternates.length > 0) { + ruleBehavior.transcription.alternates = alternates; + } // Yes, even for ruleBehavior.triggersDefaultCommand. Those tend to change the context. ruleBehavior.predictionPromise = this.languageProcessor.predict(ruleBehavior.transcription); From 1125b368f8c8e92205864e9a3a0b9527b0700732 Mon Sep 17 00:00:00 2001 From: jahorton Date: Thu, 22 Jul 2021 10:39:32 +0700 Subject: [PATCH 5/6] change(common/core/web): removes conditional replacement of a now always-truthy --- .../input-processor/src/text/prediction/languageProcessor.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/core/web/input-processor/src/text/prediction/languageProcessor.ts b/common/core/web/input-processor/src/text/prediction/languageProcessor.ts index 30f0d070106..d9a74b3a877 100644 --- a/common/core/web/input-processor/src/text/prediction/languageProcessor.ts +++ b/common/core/web/input-processor/src/text/prediction/languageProcessor.ts @@ -332,7 +332,7 @@ namespace com.keyman.text.prediction { } let transform = transcription.transform; - var promise = this.currentPromise = this.lmEngine.predict(alternates || transcription.transform, context); + var promise = this.currentPromise = this.lmEngine.predict(alternates, context); let lp = this; return promise.then(function(suggestions: Suggestion[]) { From 9d35b26f1be02bbda8a574b973fe3d2c5f2f2981 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 22 Jul 2021 10:56:38 +0700 Subject: [PATCH 6/6] docs(common/models): applies PR suggestion Co-authored-by: Marc Durdin --- common/predictive-text/worker/model-compositor.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/common/predictive-text/worker/model-compositor.ts b/common/predictive-text/worker/model-compositor.ts index 7004388f78f..1cd69d9616a 100644 --- a/common/predictive-text/worker/model-compositor.ts +++ b/common/predictive-text/worker/model-compositor.ts @@ -74,6 +74,17 @@ class ModelCompositor { if(!(transformDistribution instanceof Array)) { transformDistribution = [ {sample: transformDistribution, p: 1.0} ]; } else if(transformDistribution.length == 0) { + /* + Robust stop-gap: if our other filters somehow fail, this fixes the + zero-length array by making it match the form of the array that + would result if it were instead called with the other legal + parameter type - a single Transform. + + Unfortunately, the method will lack all data about even + the original keystroke that resulted in the call... but this way, + we can at least get some predictions rather than shortcutting + and producing none whatsoever. + */ transformDistribution.push({ sample: { insert: '',