refactor(web): transcription cache needed for multitaps, no longer prediction-only

This commit is contained in:
Joshua A. Horton 2023-10-24 11:45:57 +07:00
parent 2836a16650
commit ad07855239
3 changed files with 52 additions and 15 deletions

View file

@ -22,6 +22,7 @@ import {
SystemStoreIDs,
type TextTransform
} from "@keymanapp/keyboard-processor";
import { TranscriptionCache } from "../transcriptionCache.js";
export default class InputProcessor {
public static readonly DEFAULT_OPTIONS: ProcessorInitOptions = {
@ -37,6 +38,8 @@ export default class InputProcessor {
private kbdProcessor: KeyboardProcessor;
private lngProcessor: LanguageProcessor;
private readonly contextCache = new TranscriptionCache();
constructor(device: DeviceSpec, predictiveTextWorker: Worker, options?: ProcessorInitOptions) {
if(!device) {
throw new Error('device must be defined');
@ -48,7 +51,7 @@ export default class InputProcessor {
this.contextDevice = device;
this.kbdProcessor = new KeyboardProcessor(device, options);
this.lngProcessor = new LanguageProcessor(predictiveTextWorker);
this.lngProcessor = new LanguageProcessor(predictiveTextWorker, this.contextCache);
}
public get languageProcessor(): LanguageProcessor {
@ -215,6 +218,10 @@ export default class InputProcessor {
ruleBehavior.triggersDefaultCommand = true;
}
// Multitaps operate in part by referencing 'committed' Transcriptions to rewind
// the context as necessary.
this.contextCache.save(ruleBehavior.transcription);
// The keyboard may want to take an action after all other keystroke processing is
// finished, for example to switch layers. This action may not have any output
// but may change system store or variable store values. Given this, we don't need to

View file

@ -3,6 +3,7 @@ import { LMLayer } from "@keymanapp/lexical-model-layer/web";
import { OutputTarget, Transcription, Mock } from "@keymanapp/keyboard-processor";
import ContextWindow from "../contextWindow.js";
import ModelSpec from "./modelSpec.js"
import { TranscriptionCache } from "../../transcriptionCache.js";
/**
* Corresponds to the 'suggestionsready' LanguageProcessor event.
@ -60,18 +61,18 @@ export default class LanguageProcessor extends EventEmitter<LanguageProcessorEve
private configuration?: Configuration;
private currentPromise?: Promise<Suggestion[]>;
private recentTranscriptions: Transcription[] = [];
private readonly recentTranscriptions: TranscriptionCache;
private _mayPredict: boolean = true;
private _mayCorrect: boolean = true;
private _state: StateChangeEnum = 'inactive';
private static readonly TRANSCRIPTION_BUFFER: 10 = 10;
public constructor(predictiveTextWorker: Worker, supportsRightDeletions: boolean = false) {
public constructor(predictiveTextWorker: Worker, transcriptionCache: TranscriptionCache, supportsRightDeletions: boolean = false) {
super();
this.recentTranscriptions = transcriptionCache;
// Establishes KMW's platform 'capabilities', which limit the range of context a LMLayer
// model may expect.
let capabilities: Capabilities = {
@ -357,11 +358,7 @@ export default class LanguageProcessor extends EventEmitter<LanguageProcessorEve
}
private recordTranscription(transcription: Transcription) {
this.recentTranscriptions.push(transcription);
if(this.recentTranscriptions.length > LanguageProcessor.TRANSCRIPTION_BUFFER) {
this.recentTranscriptions.splice(0, 1);
}
this.recentTranscriptions.save(transcription);
}
/**
@ -372,11 +369,7 @@ export default class LanguageProcessor extends EventEmitter<LanguageProcessorEve
* @returns The matching `Transcription`, or `null` none is found.
*/
public getPredictionState(id: number): Transcription {
let match = this.recentTranscriptions.filter((t: Transcription) => {
return t.token == id;
})
return match.length == 0 ? null : match[0];
return this.recentTranscriptions.get(id);
}
public shutdown() {

View file

@ -0,0 +1,37 @@
import { Transcription } from "@keymanapp/keyboard-processor";
const TRANSCRIPTION_BUFFER_SIZE = 10;
export class TranscriptionCache {
private readonly map = new Map<number, Transcription>();
public get(key: number) {
const value = this.map.get(key);
// Update the entry's 'age' / position in the keys() ordering.
if(value) {
this.save(value);
}
return value;
}
public save(value: Transcription) {
const key = value.token >= 0 ? value.token : -value.token;
// Resets the key's ordering in Map.keys.
this.map.delete(key);
this.map.set(key, value);
if(this.map.size > TRANSCRIPTION_BUFFER_SIZE) {
/* Deletes the oldest entry. As per the specification of `Map.keys()`, the keys are in
* insertion order. The earlier `map.delete` call resets a key's position in the list,
* ensuring index 0 corresponds to the entry least-recently referenced.
*
* See also:
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/keys
*/
this.map.delete(this.map.keys().next().value);
}
}
}