Skip to content

Commit

Permalink
refactor(web): transcription cache needed for multitaps, no longer pr…
Browse files Browse the repository at this point in the history
…ediction-only
  • Loading branch information
jahorton committed Oct 24, 2023
1 parent 2836a16 commit ad07855
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 15 deletions.
9 changes: 8 additions & 1 deletion common/web/input-processor/src/text/inputProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand All @@ -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');
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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);
}

/**
Expand All @@ -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() {
Expand Down
37 changes: 37 additions & 0 deletions common/web/input-processor/src/transcriptionCache.ts
Original file line number Diff line number Diff line change
@@ -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);
}
}
}

0 comments on commit ad07855

Please sign in to comment.