2023-09-03 02:37:51 +03:00
|
|
|
/* eslint-disable @typescript-eslint/no-explicit-any,@typescript-eslint/no-unsafe-assignment */
|
2023-09-19 01:33:47 +03:00
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
2023-06-05 03:30:48 +03:00
|
|
|
// ^^ because of performance reasons
|
2021-11-23 00:03:59 +03:00
|
|
|
import { LongMap } from './long-utils'
|
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
interface TwoWayLinkedList<K, T> {
|
|
|
|
// k = key
|
|
|
|
k: K
|
|
|
|
// v = value
|
|
|
|
v: T
|
|
|
|
// p = previous
|
|
|
|
p?: TwoWayLinkedList<K, T>
|
|
|
|
// n = next
|
|
|
|
n?: TwoWayLinkedList<K, T>
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2023-09-19 01:33:47 +03:00
|
|
|
* Simple class implementing LRU-like behaviour for a Map
|
2021-05-19 20:30:23 +03:00
|
|
|
*
|
|
|
|
* Can be used to handle local cache of *something*
|
|
|
|
*
|
|
|
|
* Uses two-way linked list internally to keep track of insertion/access order
|
|
|
|
*/
|
2023-06-05 03:30:48 +03:00
|
|
|
export class LruMap<K extends string | number, V> {
|
2021-05-19 20:30:23 +03:00
|
|
|
private _capacity: number
|
|
|
|
private _first?: TwoWayLinkedList<K, V>
|
|
|
|
private _last?: TwoWayLinkedList<K, V>
|
|
|
|
|
2023-09-19 01:33:47 +03:00
|
|
|
private _map: Map<K, TwoWayLinkedList<K, V>>
|
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
private _size = 0
|
|
|
|
|
2023-09-19 01:33:47 +03:00
|
|
|
constructor(capacity: number, forLong = false) {
|
2021-05-19 20:30:23 +03:00
|
|
|
this._capacity = capacity
|
|
|
|
|
2023-09-19 01:33:47 +03:00
|
|
|
this._map = forLong ? (new LongMap() as any) : new Map()
|
2021-05-19 20:30:23 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
private _markUsed(item: TwoWayLinkedList<K, V>): void {
|
|
|
|
if (item === this._first) {
|
|
|
|
return // already the most recently used
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.p) {
|
|
|
|
if (item === this._last) {
|
|
|
|
this._last = item.p
|
|
|
|
}
|
|
|
|
item.p.n = item.n
|
|
|
|
}
|
|
|
|
|
|
|
|
if (item.n) {
|
|
|
|
item.n.p = item.p
|
|
|
|
}
|
|
|
|
|
|
|
|
item.p = undefined
|
|
|
|
item.n = this._first
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
if (this._first) {
|
|
|
|
this._first.p = item
|
|
|
|
}
|
|
|
|
this._first = item
|
|
|
|
}
|
|
|
|
|
|
|
|
get(key: K): V | undefined {
|
2023-09-19 01:33:47 +03:00
|
|
|
const item = this._map.get(key)
|
2021-05-19 20:30:23 +03:00
|
|
|
if (!item) return undefined
|
|
|
|
|
|
|
|
this._markUsed(item)
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
return item.v
|
|
|
|
}
|
|
|
|
|
|
|
|
has(key: K): boolean {
|
2023-09-19 01:33:47 +03:00
|
|
|
return this._map.has(key)
|
2021-05-19 20:30:23 +03:00
|
|
|
}
|
|
|
|
|
2023-09-18 19:40:09 +03:00
|
|
|
private _remove(item: TwoWayLinkedList<K, V>): void {
|
|
|
|
if (item.p) {
|
|
|
|
this._last = item.p
|
|
|
|
this._last.n = undefined
|
|
|
|
} else {
|
|
|
|
// exhausted
|
|
|
|
this._last = undefined
|
|
|
|
this._first = undefined
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove strong refs to and from the item
|
|
|
|
item.p = item.n = undefined
|
2023-09-19 01:33:47 +03:00
|
|
|
this._map.delete(item.k)
|
2023-09-18 19:40:09 +03:00
|
|
|
this._size -= 1
|
|
|
|
}
|
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
set(key: K, value: V): void {
|
2023-09-19 01:33:47 +03:00
|
|
|
let item = this._map.get(key)
|
2021-05-19 20:30:23 +03:00
|
|
|
|
|
|
|
if (item) {
|
|
|
|
// already in cache, update
|
|
|
|
item.v = value
|
|
|
|
this._markUsed(item)
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
item = {
|
|
|
|
k: key,
|
2021-06-06 15:20:41 +03:00
|
|
|
v: value,
|
2023-09-18 19:40:09 +03:00
|
|
|
// for jit to optimize stuff
|
|
|
|
n: undefined,
|
|
|
|
p: undefined,
|
2021-05-19 20:30:23 +03:00
|
|
|
}
|
2023-09-19 01:33:47 +03:00
|
|
|
this._map.set(key, item as any)
|
2021-05-19 20:30:23 +03:00
|
|
|
|
|
|
|
if (this._first) {
|
|
|
|
this._first.p = item
|
|
|
|
item.n = this._first
|
|
|
|
} else {
|
|
|
|
// first item ever
|
|
|
|
this._last = item
|
|
|
|
}
|
|
|
|
|
|
|
|
this._first = item
|
|
|
|
this._size += 1
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
if (this._size > this._capacity) {
|
|
|
|
// remove the last item
|
|
|
|
const oldest = this._last
|
2023-06-05 03:30:48 +03:00
|
|
|
|
2021-05-19 20:30:23 +03:00
|
|
|
if (oldest) {
|
2023-09-18 19:40:09 +03:00
|
|
|
this._remove(oldest)
|
2021-05-19 20:30:23 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-27 01:02:31 +03:00
|
|
|
delete(key: K): void {
|
2023-09-19 01:33:47 +03:00
|
|
|
const item = this._map.get(key)
|
2023-09-18 19:40:09 +03:00
|
|
|
if (item) this._remove(item)
|
2021-05-27 01:02:31 +03:00
|
|
|
}
|
2021-05-19 20:30:23 +03:00
|
|
|
}
|