UNPKG

xterm

Version:

Full xterm terminal, in your browser

137 lines (126 loc) 3.55 kB
/** * Copyright (c) 2017 The xterm.js authors. All rights reserved. * @license MIT */ interface ILinkedListNode<T> { prev: ILinkedListNode<T> | null; next: ILinkedListNode<T> | null; key: number | null; value: T | null; } export class LRUMap<T> { private _map: { [key: number]: ILinkedListNode<T> } = {}; private _head: ILinkedListNode<T> | null = null; private _tail: ILinkedListNode<T> | null = null; private _nodePool: ILinkedListNode<T>[] = []; public size: number = 0; constructor(public capacity: number) { } private _unlinkNode(node: ILinkedListNode<T>): void { const prev = node.prev; const next = node.next; if (node === this._head) { this._head = next; } if (node === this._tail) { this._tail = prev; } if (prev !== null) { prev.next = next; } if (next !== null) { next.prev = prev; } } private _appendNode(node: ILinkedListNode<T>): void { const tail = this._tail; if (tail !== null) { tail.next = node; } node.prev = tail; node.next = null; this._tail = node; if (this._head === null) { this._head = node; } } /** * Preallocate a bunch of linked-list nodes. Allocating these nodes ahead of time means that * they're more likely to live next to each other in memory, which seems to improve performance. * * Each empty object only consumes about 60 bytes of memory, so this is pretty cheap, even for * large maps. */ public prealloc(count: number): void { const nodePool = this._nodePool; for (let i = 0; i < count; i++) { nodePool.push({ prev: null, next: null, key: null, value: 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]; if (node !== undefined) { this._unlinkNode(node); this._appendNode(node); return node.value; } return null; } /** * Gets a value from a key without marking it as the most recently used item. */ public peekValue(key: number): T | null { const node = this._map[key]; if (node !== undefined) { return node.value; } return null; } public peek(): T | null { const head = this._head; return head === null ? null : head.value; } public set(key: number, value: T): void { // This is unsafe: See note above. let node = this._map[key]; if (node !== undefined) { // already exists, we just need to mutate it and move it to the end of the list node = this._map[key]; this._unlinkNode(node); node.value = value; } else if (this.size >= this.capacity) { // we're out of space: recycle the head node, move it to the tail node = this._head!; this._unlinkNode(node); delete this._map[node.key!]; node.key = key; node.value = value; this._map[key] = node; } else { // make a new element const nodePool = this._nodePool; if (nodePool.length > 0) { // use a preallocated node if we can node = nodePool.pop()!; node.key = key; node.value = value; } else { node = { prev: null, next: null, key, value }; } this._map[key] = node; this.size++; } this._appendNode(node); } }