diff --git a/web/src/engine/common/web-utils/src/rewindable-cache.ts b/web/src/engine/common/web-utils/src/rewindable-cache.ts index 9e930d20ae..edba06256c 100644 --- a/web/src/engine/common/web-utils/src/rewindable-cache.ts +++ b/web/src/engine/common/web-utils/src/rewindable-cache.ts @@ -14,10 +14,10 @@ * reaching a specified capacity and that provides the ability to * forget _all_ entries more recent than a specified entry. */ -export class RewindableCache { +export class RewindableCache { static readonly DEFAULT_MAX_SIZE = 5; - private map: Map = new Map(); + private map: Map = new Map(); private maxSize: number; /** @@ -41,37 +41,18 @@ export class RewindableCache { * @param key * @returns */ - peek(key: Key): Value { + get(key: number): Value { const entry = this.map.get(key); return entry; } - /** - * Fetches the cache entry with the specified key and also updates its - * most recent access time. - * @param key - * @returns - */ - get(key: Key): Value { - const map = this.map; - const entry = map.get(key); - if(!map.has(key)) { - return undefined; - } - - map.delete(key); - map.set(key, entry); - - return this.peek(key); - } - /** * Adds the specified key-value pair as a cache entry, overwriting any * previous value for the specified key. * @param key * @returns */ - add(key: Key, value: Value) { + add(key: number, value: Value) { const map = this.map; if(map.has(key)) { @@ -81,7 +62,7 @@ export class RewindableCache { map.set(key, value); if(map.size > this.maxSize) { - const oldest = map.keys().next().value as Key; + const oldest = map.keys().next().value as number; map.delete(oldest); map.delete(oldest); } @@ -95,7 +76,7 @@ export class RewindableCache { * Note: returns an Array and is accordingly O(N). * @returns */ - keys(): Key[] { + keys(): number[] { return [...this.map.keys()].reverse(); } @@ -107,30 +88,26 @@ export class RewindableCache { } /** - * Deletes all entries more recent than the specified key, leaving + * Deletes all entries with a key less than the specified key, leaving * the specified key as the most recent entry. * @param key */ - rewindTo(key: Key) { + rewindTo(key: number) { const map = this.map; if(!map.has(key)) { throw new Error("Key not found; cannot clear entries appearing afterward."); } - let keyFound: boolean = false; // Iteration: by insertion order // The most recent entries will appear after the specified key. for(const k of map.keys()) { - if(!keyFound) { - keyFound = k == key; - continue; + if(k > key) { + // It is safe to delete a key from a Set or Map while iterating over it in ES6+. + // https://stackoverflow.com/a/28306768 + map.delete(k); + map.delete(k); } - - // It is safe to delete a key from a Set while iterating over it in ES6+. - // https://stackoverflow.com/a/28306768 - map.delete(k); - map.delete(k); } } } \ No newline at end of file diff --git a/web/src/engine/common/web-utils/src/tests/rewindableCache.tests.js b/web/src/engine/common/web-utils/src/tests/rewindableCache.tests.js index 4524149ea5..f0093347ef 100644 --- a/web/src/engine/common/web-utils/src/tests/rewindableCache.tests.js +++ b/web/src/engine/common/web-utils/src/tests/rewindableCache.tests.js @@ -63,7 +63,7 @@ describe("RewindableCache", () => { cache.add(5, 'e'); assert.sameOrderedMembers([...cache.keys()], [5, 4]); - assert.sameOrderedMembers([...cache.keys().map((k) => cache.peek(k))], ['e', 'd']); + assert.sameOrderedMembers([...cache.keys().map((k) => cache.get(k))], ['e', 'd']); assert.equal(cache.size, 2); }); @@ -75,12 +75,13 @@ describe("RewindableCache", () => { cache.add(4, 'd'); cache.add(5, 'e'); - cache.get(2); + // make it more recently-accessed, but don't remove it. cache.rewindTo(3); - assert.sameOrderedMembers([...cache.keys()], [3, 1]); - assert.sameOrderedMembers([...cache.keys().map((k) => cache.peek(k))], ['c', 'a']); - assert.equal(cache.size, 2); + cache.add(2, 'b'); // 2 is still ORIGINALLY older than 3, even if accessed more recently. + assert.sameOrderedMembers([...cache.keys()], [2, 3, 1]); + assert.sameOrderedMembers([...cache.keys().map((k) => cache.get(k))], ['b', 'c', 'a']); + assert.equal(cache.size, 3); }); it("forgets no entries if invalid key is provided", () => { @@ -102,7 +103,6 @@ describe("RewindableCache", () => { cache.add(4, 'd'); // 2 cache.add(5, 'e'); // 3 - assert.isUndefined(cache.peek(2), 'outdated cache entry should be forgotten'); assert.isUndefined(cache.get(2), 'outdated cache entry should be forgotten'); }); }); \ No newline at end of file diff --git a/web/src/engine/main/src/headless/inputProcessor.ts b/web/src/engine/main/src/headless/inputProcessor.ts index 891eec0b3c..e3c204ab17 100644 --- a/web/src/engine/main/src/headless/inputProcessor.ts +++ b/web/src/engine/main/src/headless/inputProcessor.ts @@ -103,7 +103,7 @@ export class InputProcessor { // Support for multitap context reversion; multitap keys should act as if they were // the first thing typed since `preInput`, the state before the original base key. if(keyEvent.baseTranscriptionToken) { - const transcription = this.contextCache.peek(keyEvent.baseTranscriptionToken); + const transcription = this.contextCache.get(keyEvent.baseTranscriptionToken); if(transcription) { this.contextCache.rewindTo(keyEvent.baseTranscriptionToken); // Has there been a context change at any point during the multitap? If so, we need diff --git a/web/src/engine/main/src/headless/languageProcessor.ts b/web/src/engine/main/src/headless/languageProcessor.ts index e81e523284..f4486a5145 100644 --- a/web/src/engine/main/src/headless/languageProcessor.ts +++ b/web/src/engine/main/src/headless/languageProcessor.ts @@ -377,7 +377,7 @@ export class LanguageProcessor extends EventEmitter { * @returns The matching `Transcription`, or `null` none is found. */ public getPredictionState(id: number): Transcription { - return this.recentTranscriptions.peek(id); + return this.recentTranscriptions.get(id); } public shutdown() { diff --git a/web/src/engine/main/src/headless/transcriptionCache.ts b/web/src/engine/main/src/headless/transcriptionCache.ts index 3ac7810a11..f1cc20a73f 100644 --- a/web/src/engine/main/src/headless/transcriptionCache.ts +++ b/web/src/engine/main/src/headless/transcriptionCache.ts @@ -3,7 +3,7 @@ import { RewindableCache } from "@keymanapp/web-utils"; const TRANSCRIPTION_BUFFER_SIZE = 10; -export class TranscriptionCache extends RewindableCache { +export class TranscriptionCache extends RewindableCache { constructor() { super(TRANSCRIPTION_BUFFER_SIZE); } @@ -17,7 +17,7 @@ export class TranscriptionCache extends RewindableCache { const entries = [...this.keys()].map((key) => { return { key: key, - entry: this.peek(key) + entry: this.get(key) } }); diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts index e4d3f4f319..457ef0a570 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/context-tracker.ts @@ -15,7 +15,7 @@ import { ContextState } from './context-state.js'; import { ContextTransition } from './context-transition.js'; export class ContextTracker { - readonly cache = new RewindableCache(5); + readonly cache = new RewindableCache(5); // Aim: relocate to ContextTransition in some form? // Or can we split it up in some manner across the different types? @@ -275,7 +275,7 @@ export class ContextTracker { if(tokenizedPostContext.left.length > 0) { for(const id of this.cache.keys()) { - const priorMatchState = this.cache.peek(id); + const priorMatchState = this.cache.get(id); // Skip intermediate multitap-produced contexts. // When multitapping, we skip all contexts from prior taps within the same interaction, @@ -303,7 +303,7 @@ export class ContextTracker { if(transitionId !== undefined) { if(priorMatchState.transitionId != transitionId) { // Already has a taggedContext. - this.cache.get(transformDistribution.find((entry) => entry.sample.id !== undefined).sample.id); + this.cache.add(priorMatchState.transitionId, priorMatchState); } this.cache.add(transitionId, result); } @@ -332,7 +332,7 @@ export class ContextTracker { if(key === undefined) { return undefined; } else { - return this.cache.peek(key); + return this.cache.get(key); } } diff --git a/web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts b/web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts index 048d839371..108fa08396 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/model-compositor.ts @@ -293,7 +293,7 @@ export class ModelCompositor { } // When the context is tracked, we prefer the tracked information. - let contextMatchFound = this.contextTracker.cache.peek(-reversion.transformId); + let contextMatchFound = this.contextTracker.cache.get(-reversion.transformId); if(!contextMatchFound) { return fallbackSuggestions();