-
Notifications
You must be signed in to change notification settings - Fork 3
/
SearchCache.ts
120 lines (108 loc) · 3.97 KB
/
SearchCache.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import {Queue} from "../Queue";
import {IQueueNode} from "./_types/IQueueNode";
import {TCacheKeysList} from "./_types/TCacheKeysList";
import {TCacheMap} from "./_types/TCacheMap";
type IMapVal<K, V> = {value: V; node: IQueueNode<K>};
/**
* A class to cache items that are created for a search, such that items aren't constantly replaced
*/
export class SearchCache<K extends [any, ...any], V> {
protected create: (...key: K) => V;
protected map = new Map() as TCacheMap<K, IMapVal<K, V>>;
protected queue: Queue<K> = new Queue();
protected maxSize: number = 0;
/**
* Creates a new search cache
* @param create The function to create new values
* @param maxSize The maximum number of items to keep in the cache
*/
public constructor(create: (...key: K) => V, maxSize: number = 50) {
this.create = create;
this.maxSize = maxSize;
}
/**
* Retrieves multiple items, either from the cache or newly created
* @param items The keys of the items to get
* @returns The items
*/
public getAll(items: TCacheKeysList<K>): V[] {
return items.map(key =>
key instanceof Array
? this.get(...(key as any))
: /** @ts-ignore */
this.get(key)
);
}
/**
* Retrieves an item, either from the cache or newly created
* @param keys The keys to get the item for
* @returns The item
*/
public get(...keys: K): V {
// If the map contains the value, return it
let map: Map<any, any> = this.map;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
if (!(map instanceof Map)) break;
map = map.get(key);
}
const lastKey = keys[keys.length - 1];
const data: IMapVal<K, V> = map?.get(lastKey);
if (data) {
const {node, value} = data;
// Add the keys to the back of the queue
this.queue.removeNode(node);
const newNode = this.queue.push(keys);
map.set(lastKey, {value, node: newNode});
return value;
}
// Otherwise, create the value and store it
const node = this.queue.push(keys);
const value = this.create(...keys);
this.add(this.map, keys, value, node);
if (this.queue.getSize() > this.maxSize) {
const removeKey = this.queue.pop();
if (removeKey) this.remove(this.map, removeKey);
}
return value;
}
/**
* Adds an item to the cache map
* @param map The map to add the value to
* @param keys The keys to add the value under
* @param value The value to add
* @param node The queue node to store in the map
*/
protected add<MKeys extends [MK, ...MKrest], MK, MKrest extends any[]>(
map: Map<MK, TCacheMap<MKrest, IMapVal<K, V>>>,
[key, ...keys]: MKeys,
value: V,
node: IQueueNode<K>
): void {
if (keys.length > 0) {
let subMap = map.get(key);
if (!subMap) {
subMap = new Map() as TCacheMap<MKrest, IMapVal<K, V>>;
map.set(key, subMap);
}
this.add(subMap as Map<any, any>, keys as [any, ...any[]], value, node);
} else (map as Map<MK, IMapVal<K, V>>).set(key, {value, node});
}
/**
* Removes an item from the cache
* @param map The map to add the value to
* @param keys The keys to remove the value from
*/
protected remove<MKeys extends [MK, ...MKrest], MK, MKrest extends any[]>(
map: Map<MK, TCacheMap<MKrest, IMapVal<K, V>>>,
[key, ...keys]: MKeys
): void {
if (keys.length > 0) {
let subMap = map.get(key) as Map<any, any>;
if (subMap) {
this.remove(subMap as Map<any, any>, keys as [any, ...any[]]);
if (subMap.size == 0) map.delete(key);
}
} else (map as Map<MK, IMapVal<K, V>>).delete(key);
}
}