import { Dictionary } from './types';
|
|
// Simple LRU cache use doubly linked list
|
// @module zrender/core/LRU
|
|
export class Entry<T> {
|
|
value: T
|
|
key: string | number
|
|
next: Entry<T>
|
|
prev: Entry<T>
|
|
constructor(val: T) {
|
this.value = val;
|
}
|
}
|
/**
|
* Simple double linked list. Compared with array, it has O(1) remove operation.
|
* @constructor
|
*/
|
export class LinkedList<T> {
|
|
head: Entry<T>
|
tail: Entry<T>
|
|
private _len = 0
|
|
/**
|
* Insert a new value at the tail
|
*/
|
insert(val: T): Entry<T> {
|
const entry = new Entry(val);
|
this.insertEntry(entry);
|
return entry;
|
}
|
|
/**
|
* Insert an entry at the tail
|
*/
|
insertEntry(entry: Entry<T>) {
|
if (!this.head) {
|
this.head = this.tail = entry;
|
}
|
else {
|
this.tail.next = entry;
|
entry.prev = this.tail;
|
entry.next = null;
|
this.tail = entry;
|
}
|
this._len++;
|
}
|
|
/**
|
* Remove entry.
|
*/
|
remove(entry: Entry<T>) {
|
const prev = entry.prev;
|
const next = entry.next;
|
if (prev) {
|
prev.next = next;
|
}
|
else {
|
// Is head
|
this.head = next;
|
}
|
if (next) {
|
next.prev = prev;
|
}
|
else {
|
// Is tail
|
this.tail = prev;
|
}
|
entry.next = entry.prev = null;
|
this._len--;
|
}
|
|
/**
|
* Get length
|
*/
|
len(): number {
|
return this._len;
|
}
|
|
/**
|
* Clear list
|
*/
|
clear() {
|
this.head = this.tail = null;
|
this._len = 0;
|
}
|
|
}
|
|
/**
|
* LRU Cache
|
*/
|
export default class LRU<T> {
|
|
private _list = new LinkedList<T>()
|
|
private _maxSize = 10
|
|
private _lastRemovedEntry: Entry<T>
|
|
private _map: Dictionary<Entry<T>> = {}
|
|
constructor(maxSize: number) {
|
this._maxSize = maxSize;
|
}
|
|
/**
|
* @return Removed value
|
*/
|
put(key: string | number, value: T): T {
|
const list = this._list;
|
const map = this._map;
|
let removed = null;
|
if (map[key] == null) {
|
const len = list.len();
|
// Reuse last removed entry
|
let entry = this._lastRemovedEntry;
|
|
if (len >= this._maxSize && len > 0) {
|
// Remove the least recently used
|
const leastUsedEntry = list.head;
|
list.remove(leastUsedEntry);
|
delete map[leastUsedEntry.key];
|
|
removed = leastUsedEntry.value;
|
this._lastRemovedEntry = leastUsedEntry;
|
}
|
|
if (entry) {
|
entry.value = value;
|
}
|
else {
|
entry = new Entry(value);
|
}
|
entry.key = key;
|
list.insertEntry(entry);
|
map[key] = entry;
|
}
|
|
return removed;
|
}
|
|
get(key: string | number): T {
|
const entry = this._map[key];
|
const list = this._list;
|
if (entry != null) {
|
// Put the latest used entry in the tail
|
if (entry !== list.tail) {
|
list.remove(entry);
|
list.insertEntry(entry);
|
}
|
|
return entry.value;
|
}
|
}
|
|
/**
|
* Clear the cache
|
*/
|
clear() {
|
this._list.clear();
|
this._map = {};
|
}
|
|
len() {
|
return this._list.len();
|
}
|
}
|