Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/git/src/cache.ts
3314 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
interface Item<K, V> {
7
previous: Item<K, V> | undefined;
8
next: Item<K, V> | undefined;
9
key: K;
10
value: V;
11
}
12
13
const enum Touch {
14
None = 0,
15
AsOld = 1,
16
AsNew = 2
17
}
18
19
class LinkedMap<K, V> implements Map<K, V> {
20
21
readonly [Symbol.toStringTag] = 'LinkedMap';
22
23
private _map: Map<K, Item<K, V>>;
24
private _head: Item<K, V> | undefined;
25
private _tail: Item<K, V> | undefined;
26
private _size: number;
27
28
private _state: number;
29
30
constructor() {
31
this._map = new Map<K, Item<K, V>>();
32
this._head = undefined;
33
this._tail = undefined;
34
this._size = 0;
35
this._state = 0;
36
}
37
38
clear(): void {
39
this._map.clear();
40
this._head = undefined;
41
this._tail = undefined;
42
this._size = 0;
43
this._state++;
44
}
45
46
isEmpty(): boolean {
47
return !this._head && !this._tail;
48
}
49
50
get size(): number {
51
return this._size;
52
}
53
54
get first(): V | undefined {
55
return this._head?.value;
56
}
57
58
get last(): V | undefined {
59
return this._tail?.value;
60
}
61
62
has(key: K): boolean {
63
return this._map.has(key);
64
}
65
66
get(key: K, touch: Touch = Touch.None): V | undefined {
67
const item = this._map.get(key);
68
if (!item) {
69
return undefined;
70
}
71
if (touch !== Touch.None) {
72
this.touch(item, touch);
73
}
74
return item.value;
75
}
76
77
set(key: K, value: V, touch: Touch = Touch.None): this {
78
let item = this._map.get(key);
79
if (item) {
80
item.value = value;
81
if (touch !== Touch.None) {
82
this.touch(item, touch);
83
}
84
} else {
85
item = { key, value, next: undefined, previous: undefined };
86
switch (touch) {
87
case Touch.None:
88
this.addItemLast(item);
89
break;
90
case Touch.AsOld:
91
this.addItemFirst(item);
92
break;
93
case Touch.AsNew:
94
this.addItemLast(item);
95
break;
96
default:
97
this.addItemLast(item);
98
break;
99
}
100
this._map.set(key, item);
101
this._size++;
102
}
103
return this;
104
}
105
106
delete(key: K): boolean {
107
return !!this.remove(key);
108
}
109
110
remove(key: K): V | undefined {
111
const item = this._map.get(key);
112
if (!item) {
113
return undefined;
114
}
115
this._map.delete(key);
116
this.removeItem(item);
117
this._size--;
118
return item.value;
119
}
120
121
shift(): V | undefined {
122
if (!this._head && !this._tail) {
123
return undefined;
124
}
125
if (!this._head || !this._tail) {
126
throw new Error('Invalid list');
127
}
128
const item = this._head;
129
this._map.delete(item.key);
130
this.removeItem(item);
131
this._size--;
132
return item.value;
133
}
134
135
forEach(callbackfn: (value: V, key: K, map: LinkedMap<K, V>) => void, thisArg?: any): void {
136
const state = this._state;
137
let current = this._head;
138
while (current) {
139
if (thisArg) {
140
callbackfn.bind(thisArg)(current.value, current.key, this);
141
} else {
142
callbackfn(current.value, current.key, this);
143
}
144
if (this._state !== state) {
145
throw new Error(`LinkedMap got modified during iteration.`);
146
}
147
current = current.next;
148
}
149
}
150
151
keys(): IterableIterator<K> {
152
const map = this;
153
const state = this._state;
154
let current = this._head;
155
const iterator: IterableIterator<K> = {
156
[Symbol.iterator]() {
157
return iterator;
158
},
159
next(): IteratorResult<K> {
160
if (map._state !== state) {
161
throw new Error(`LinkedMap got modified during iteration.`);
162
}
163
if (current) {
164
const result = { value: current.key, done: false };
165
current = current.next;
166
return result;
167
} else {
168
return { value: undefined, done: true };
169
}
170
}
171
};
172
return iterator;
173
}
174
175
values(): IterableIterator<V> {
176
const map = this;
177
const state = this._state;
178
let current = this._head;
179
const iterator: IterableIterator<V> = {
180
[Symbol.iterator]() {
181
return iterator;
182
},
183
next(): IteratorResult<V> {
184
if (map._state !== state) {
185
throw new Error(`LinkedMap got modified during iteration.`);
186
}
187
if (current) {
188
const result = { value: current.value, done: false };
189
current = current.next;
190
return result;
191
} else {
192
return { value: undefined, done: true };
193
}
194
}
195
};
196
return iterator;
197
}
198
199
entries(): IterableIterator<[K, V]> {
200
const map = this;
201
const state = this._state;
202
let current = this._head;
203
const iterator: IterableIterator<[K, V]> = {
204
[Symbol.iterator]() {
205
return iterator;
206
},
207
next(): IteratorResult<[K, V]> {
208
if (map._state !== state) {
209
throw new Error(`LinkedMap got modified during iteration.`);
210
}
211
if (current) {
212
const result: IteratorResult<[K, V]> = { value: [current.key, current.value], done: false };
213
current = current.next;
214
return result;
215
} else {
216
return { value: undefined, done: true };
217
}
218
}
219
};
220
return iterator;
221
}
222
223
[Symbol.iterator](): IterableIterator<[K, V]> {
224
return this.entries();
225
}
226
227
protected trimOld(newSize: number) {
228
if (newSize >= this.size) {
229
return;
230
}
231
if (newSize === 0) {
232
this.clear();
233
return;
234
}
235
let current = this._head;
236
let currentSize = this.size;
237
while (current && currentSize > newSize) {
238
this._map.delete(current.key);
239
current = current.next;
240
currentSize--;
241
}
242
this._head = current;
243
this._size = currentSize;
244
if (current) {
245
current.previous = undefined;
246
}
247
this._state++;
248
}
249
250
protected trimNew(newSize: number) {
251
if (newSize >= this.size) {
252
return;
253
}
254
if (newSize === 0) {
255
this.clear();
256
return;
257
}
258
let current = this._tail;
259
let currentSize = this.size;
260
while (current && currentSize > newSize) {
261
this._map.delete(current.key);
262
current = current.previous;
263
currentSize--;
264
}
265
this._tail = current;
266
this._size = currentSize;
267
if (current) {
268
current.next = undefined;
269
}
270
this._state++;
271
}
272
273
private addItemFirst(item: Item<K, V>): void {
274
// First time Insert
275
if (!this._head && !this._tail) {
276
this._tail = item;
277
} else if (!this._head) {
278
throw new Error('Invalid list');
279
} else {
280
item.next = this._head;
281
this._head.previous = item;
282
}
283
this._head = item;
284
this._state++;
285
}
286
287
private addItemLast(item: Item<K, V>): void {
288
// First time Insert
289
if (!this._head && !this._tail) {
290
this._head = item;
291
} else if (!this._tail) {
292
throw new Error('Invalid list');
293
} else {
294
item.previous = this._tail;
295
this._tail.next = item;
296
}
297
this._tail = item;
298
this._state++;
299
}
300
301
private removeItem(item: Item<K, V>): void {
302
if (item === this._head && item === this._tail) {
303
this._head = undefined;
304
this._tail = undefined;
305
}
306
else if (item === this._head) {
307
// This can only happen if size === 1 which is handled
308
// by the case above.
309
if (!item.next) {
310
throw new Error('Invalid list');
311
}
312
item.next.previous = undefined;
313
this._head = item.next;
314
}
315
else if (item === this._tail) {
316
// This can only happen if size === 1 which is handled
317
// by the case above.
318
if (!item.previous) {
319
throw new Error('Invalid list');
320
}
321
item.previous.next = undefined;
322
this._tail = item.previous;
323
}
324
else {
325
const next = item.next;
326
const previous = item.previous;
327
if (!next || !previous) {
328
throw new Error('Invalid list');
329
}
330
next.previous = previous;
331
previous.next = next;
332
}
333
item.next = undefined;
334
item.previous = undefined;
335
this._state++;
336
}
337
338
private touch(item: Item<K, V>, touch: Touch): void {
339
if (!this._head || !this._tail) {
340
throw new Error('Invalid list');
341
}
342
if ((touch !== Touch.AsOld && touch !== Touch.AsNew)) {
343
return;
344
}
345
346
if (touch === Touch.AsOld) {
347
if (item === this._head) {
348
return;
349
}
350
351
const next = item.next;
352
const previous = item.previous;
353
354
// Unlink the item
355
if (item === this._tail) {
356
// previous must be defined since item was not head but is tail
357
// So there are more than on item in the map
358
previous!.next = undefined;
359
this._tail = previous;
360
}
361
else {
362
// Both next and previous are not undefined since item was neither head nor tail.
363
next!.previous = previous;
364
previous!.next = next;
365
}
366
367
// Insert the node at head
368
item.previous = undefined;
369
item.next = this._head;
370
this._head.previous = item;
371
this._head = item;
372
this._state++;
373
} else if (touch === Touch.AsNew) {
374
if (item === this._tail) {
375
return;
376
}
377
378
const next = item.next;
379
const previous = item.previous;
380
381
// Unlink the item.
382
if (item === this._head) {
383
// next must be defined since item was not tail but is head
384
// So there are more than on item in the map
385
next!.previous = undefined;
386
this._head = next;
387
} else {
388
// Both next and previous are not undefined since item was neither head nor tail.
389
next!.previous = previous;
390
previous!.next = next;
391
}
392
item.next = undefined;
393
item.previous = this._tail;
394
this._tail.next = item;
395
this._tail = item;
396
this._state++;
397
}
398
}
399
400
toJSON(): [K, V][] {
401
const data: [K, V][] = [];
402
403
this.forEach((value, key) => {
404
data.push([key, value]);
405
});
406
407
return data;
408
}
409
410
fromJSON(data: [K, V][]): void {
411
this.clear();
412
413
for (const [key, value] of data) {
414
this.set(key, value);
415
}
416
}
417
}
418
419
abstract class Cache<K, V> extends LinkedMap<K, V> {
420
421
protected _limit: number;
422
protected _ratio: number;
423
424
constructor(limit: number, ratio: number = 1) {
425
super();
426
this._limit = limit;
427
this._ratio = Math.min(Math.max(0, ratio), 1);
428
}
429
430
get limit(): number {
431
return this._limit;
432
}
433
434
set limit(limit: number) {
435
this._limit = limit;
436
this.checkTrim();
437
}
438
439
get ratio(): number {
440
return this._ratio;
441
}
442
443
set ratio(ratio: number) {
444
this._ratio = Math.min(Math.max(0, ratio), 1);
445
this.checkTrim();
446
}
447
448
override get(key: K, touch: Touch = Touch.AsNew): V | undefined {
449
return super.get(key, touch);
450
}
451
452
peek(key: K): V | undefined {
453
return super.get(key, Touch.None);
454
}
455
456
override set(key: K, value: V): this {
457
super.set(key, value, Touch.AsNew);
458
return this;
459
}
460
461
protected checkTrim() {
462
if (this.size > this._limit) {
463
this.trim(Math.round(this._limit * this._ratio));
464
}
465
}
466
467
protected abstract trim(newSize: number): void;
468
}
469
470
export class LRUCache<K, V> extends Cache<K, V> {
471
472
constructor(limit: number, ratio: number = 1) {
473
super(limit, ratio);
474
}
475
476
protected override trim(newSize: number) {
477
this.trimOld(newSize);
478
}
479
480
override set(key: K, value: V): this {
481
super.set(key, value);
482
this.checkTrim();
483
return this;
484
}
485
}
486
487