From cf9cb80cb4b82e5dbf11fce59d274175d629b0b8 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 14 Sep 2018 10:08:26 -0700 Subject: [PATCH] Use a number as the dynamic cache key This prevents string creation on every draw --- src/renderer/atlas/DynamicCharAtlas.ts | 16 ++++++-- src/renderer/atlas/LRUMap.test.ts | 56 +++++++++++++------------- src/renderer/atlas/LRUMap.ts | 8 ++-- 3 files changed, 44 insertions(+), 36 deletions(-) diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/renderer/atlas/DynamicCharAtlas.ts index 66baa822..c3927646 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/renderer/atlas/DynamicCharAtlas.ts @@ -34,9 +34,17 @@ interface IGlyphCacheValue { isEmpty: boolean; } -function getGlyphCacheKey(chars: string, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): string { - const styleFlags = (bold ? 0 : 4) + (dim ? 0 : 2) + (italic ? 0 : 1); - return `${bg}_${fg}_${styleFlags}${chars}`; +function getGlyphCacheKey(code: number, fg: number, bg: number, bold: boolean, dim: boolean, italic: boolean): number { + // Note that this only returns a valid key when code < 256 + // Layout: + // 0b00000000000000000000000000000001: italic (1) + // 0b00000000000000000000000000000010: dim (1) + // 0b00000000000000000000000000000100: bold (1) + // 0b00000000000000000000111111111000: fg (9) + // 0b00000000000111111111000000000000: bg (9) + // 0b00011111111000000000000000000000: code (8) + // 0b11100000000000000000000000000000: unused (3) + return code << 21 | bg << 12 | fg << 3 | (bold ? 0 : 4) + (dim ? 0 : 2) + (italic ? 0 : 1); } export default class DynamicCharAtlas extends BaseCharAtlas { @@ -98,7 +106,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas { x: number, y: number ): boolean { - const glyphKey = getGlyphCacheKey(chars, fg, bg, bold, dim, italic); + const glyphKey = getGlyphCacheKey(code, fg, bg, bold, dim, italic); const cacheValue = this._cacheMap.get(glyphKey); if (cacheValue !== null && cacheValue !== undefined) { this._drawFromCache(ctx, cacheValue, x, y); diff --git a/src/renderer/atlas/LRUMap.test.ts b/src/renderer/atlas/LRUMap.test.ts index ba01e410..197d1159 100644 --- a/src/renderer/atlas/LRUMap.test.ts +++ b/src/renderer/atlas/LRUMap.test.ts @@ -9,57 +9,57 @@ import LRUMap from './LRUMap'; describe('LRUMap', () => { it('can be used to store and retrieve values', () => { const map = new LRUMap(10); - map.set('keya', 'valuea'); - map.set('keyb', 'valueb'); - map.set('keyc', 'valuec'); - assert.strictEqual(map.get('keya'), 'valuea'); - assert.strictEqual(map.get('keyb'), 'valueb'); - assert.strictEqual(map.get('keyc'), 'valuec'); + map.set(1, 'valuea'); + map.set(2, 'valueb'); + map.set(3, 'valuec'); + assert.strictEqual(map.get(1), 'valuea'); + assert.strictEqual(map.get(2), 'valueb'); + assert.strictEqual(map.get(3), 'valuec'); }); it('maintains a size from insertions', () => { const map = new LRUMap(10); assert.strictEqual(map.size, 0); - map.set('a', 'value'); + map.set(1, 'value'); assert.strictEqual(map.size, 1); - map.set('b', 'value'); + map.set(2, 'value'); assert.strictEqual(map.size, 2); }); it('deletes the oldest entry when the capacity is exceeded', () => { const map = new LRUMap(4); - map.set('a', 'value'); - map.set('b', 'value'); - map.set('c', 'value'); - map.set('d', 'value'); - map.set('e', 'value'); - assert.isNull(map.get('a')); - assert.isNotNull(map.get('b')); - assert.isNotNull(map.get('c')); - assert.isNotNull(map.get('d')); - assert.isNotNull(map.get('e')); + map.set(1, 'value'); + map.set(2, 'value'); + map.set(3, 'value'); + map.set(4, 'value'); + map.set(5, 'value'); + assert.isNull(map.get(1)); + assert.isNotNull(map.get(2)); + assert.isNotNull(map.get(3)); + assert.isNotNull(map.get(4)); + assert.isNotNull(map.get(5)); assert.strictEqual(map.size, 4); }); it('prevents a recently accessed entry from getting deleted', () => { const map = new LRUMap(2); - map.set('a', 'value'); - map.set('b', 'value'); - map.get('a'); + map.set(1, 'value'); + map.set(2, 'value'); + map.get(1); // a would normally get deleted here, except that we called get() - map.set('c', 'value'); - assert.isNotNull(map.get('a')); + map.set(3, 'value'); + assert.isNotNull(map.get(1)); // b got deleted instead of a - assert.isNull(map.get('b')); - assert.isNotNull(map.get('c')); + assert.isNull(map.get(2)); + assert.isNotNull(map.get(3)); }); it('supports mutation', () => { const map = new LRUMap(10); - map.set('keya', 'oldvalue'); - map.set('keya', 'newvalue'); + map.set(1, 'oldvalue'); + map.set(1, 'newvalue'); // mutation doesn't change the size assert.strictEqual(map.size, 1); - assert.strictEqual(map.get('keya'), 'newvalue'); + assert.strictEqual(map.get(1), 'newvalue'); }); }); diff --git a/src/renderer/atlas/LRUMap.ts b/src/renderer/atlas/LRUMap.ts index eccfbfea..984dbb72 100644 --- a/src/renderer/atlas/LRUMap.ts +++ b/src/renderer/atlas/LRUMap.ts @@ -6,12 +6,12 @@ interface ILinkedListNode { prev: ILinkedListNode; next: ILinkedListNode; - key: string; + key: number; value: T; } export default class LRUMap { - private _map: { [key: string]: ILinkedListNode } = {}; + private _map: { [key: number]: ILinkedListNode } = {}; private _head: ILinkedListNode = null; private _tail: ILinkedListNode = null; private _nodePool: ILinkedListNode[] = []; @@ -68,7 +68,7 @@ export default class LRUMap { } } - public get(key: string): T | null { + public get(key: number): T | null { // This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However, // it's faster than calling hasOwnProperty, and in our case, it would never overlap. const node = this._map[key]; @@ -85,7 +85,7 @@ export default class LRUMap { return head === null ? null : head.value; } - public set(key: string, value: T): void { + public set(key: number, value: T): void { // This is unsafe: See note above. let node = this._map[key]; if (node !== undefined) {