feat(core): implemented and exported lru map
This commit is contained in:
parent
bdaa571777
commit
910361ccab
3 changed files with 268 additions and 0 deletions
|
@ -7,6 +7,7 @@ export * from './utils/crypto'
|
|||
export * from './utils/peer-utils'
|
||||
export * from './utils/tl-json'
|
||||
export * from './utils/async-lock'
|
||||
export * from './utils/lru-map'
|
||||
|
||||
export { BinaryReader } from './utils/binary/binary-reader'
|
||||
export { BinaryWriter } from './utils/binary/binary-writer'
|
||||
|
|
184
packages/core/src/utils/lru-map.ts
Normal file
184
packages/core/src/utils/lru-map.ts
Normal file
|
@ -0,0 +1,184 @@
|
|||
interface TwoWayLinkedList<K, T> {
|
||||
// k = key
|
||||
k: K
|
||||
// v = value
|
||||
v: T
|
||||
// p = previous
|
||||
p?: TwoWayLinkedList<K, T>
|
||||
// n = next
|
||||
n?: TwoWayLinkedList<K, T>
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple class implementing LRU-like behaviour for a map,
|
||||
* falling back to objects when `Map` is not available.
|
||||
*
|
||||
* Can be used to handle local cache of *something*
|
||||
*
|
||||
* Uses two-way linked list internally to keep track of insertion/access order
|
||||
*/
|
||||
export class LruMap<K extends keyof any, V> {
|
||||
private _capacity: number
|
||||
private _first?: TwoWayLinkedList<K, V>
|
||||
private _last?: TwoWayLinkedList<K, V>
|
||||
|
||||
private _size = 0
|
||||
|
||||
constructor(capacity: number, useObject = false) {
|
||||
this._capacity = capacity
|
||||
|
||||
if (typeof Map === 'undefined' || useObject) {
|
||||
const obj = {} as any
|
||||
this._set = (k, v) => obj[k] = v
|
||||
this._has = (k) => k in obj
|
||||
this._get = (k) => obj[k]
|
||||
this._del = (k) => delete obj[k]
|
||||
} else {
|
||||
const map = new Map()
|
||||
this._set = map.set.bind(map)
|
||||
this._has = map.has.bind(map)
|
||||
this._get = map.get.bind(map)
|
||||
this._del = map.delete.bind(map)
|
||||
}
|
||||
}
|
||||
|
||||
private readonly _set: (key: K, value: V) => void
|
||||
private readonly _has: (key: K) => boolean
|
||||
private readonly _get: (key: K) => TwoWayLinkedList<K, V> | undefined
|
||||
private readonly _del: (key: K) => void
|
||||
|
||||
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
|
||||
if (this._first) {
|
||||
this._first.p = item
|
||||
}
|
||||
this._first = item
|
||||
}
|
||||
|
||||
get(key: K): V | undefined {
|
||||
const item = this._get(key)
|
||||
if (!item) return undefined
|
||||
|
||||
this._markUsed(item)
|
||||
return item.v
|
||||
}
|
||||
|
||||
has(key: K): boolean {
|
||||
return this._has(key)
|
||||
}
|
||||
|
||||
set(key: K, value: V): void {
|
||||
let item = this._get(key)
|
||||
|
||||
if (item) {
|
||||
// already in cache, update
|
||||
item.v = value
|
||||
this._markUsed(item)
|
||||
return
|
||||
}
|
||||
|
||||
item = {
|
||||
k: key,
|
||||
v: value
|
||||
}
|
||||
this._set(key, item as any)
|
||||
|
||||
if (this._first) {
|
||||
this._first.p = item
|
||||
item.n = this._first
|
||||
} else {
|
||||
// first item ever
|
||||
this._last = item
|
||||
}
|
||||
|
||||
this._first = item
|
||||
this._size += 1
|
||||
if (this._size > this._capacity) {
|
||||
// remove the last item
|
||||
const oldest = this._last
|
||||
if (oldest) {
|
||||
if (oldest.p) {
|
||||
this._last = oldest.p
|
||||
this._last!.n = undefined
|
||||
} else {
|
||||
// exhausted
|
||||
this._last = undefined
|
||||
this._first = undefined
|
||||
}
|
||||
|
||||
// remove strong refs to and from the item
|
||||
oldest.p = oldest.n = undefined
|
||||
this._del(oldest.k)
|
||||
this._size -= 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// private _setForMap(key: K, value: V) {
|
||||
// const old = this._items[key]
|
||||
//
|
||||
// if (old) {
|
||||
// bring old one to the beginning
|
||||
//
|
||||
// }
|
||||
// if (!this._first) this._first = { v: str }
|
||||
// if (!this._last) this._last = this._first
|
||||
// else {
|
||||
// this._last.n = { v: str }
|
||||
// this._last = this._last.n
|
||||
// }
|
||||
//
|
||||
// this._set!.add(str)
|
||||
//
|
||||
// if (this._set!.size > this._capacity && this._first) {
|
||||
// // remove least recently used
|
||||
// this._set!.delete(this._first.v)
|
||||
// this._first = this._first.n
|
||||
// }
|
||||
// }
|
||||
|
||||
// private _hasForMap(str: string) {
|
||||
// return this._set!.has(str)
|
||||
// }
|
||||
|
||||
// private _setForObj(str: string) {
|
||||
// if (str in this._obj!) return
|
||||
//
|
||||
// if (!this._first) this._first = { v: str }
|
||||
// if (!this._last) this._last = this._first
|
||||
// else {
|
||||
// this._last.n = { v: str }
|
||||
// this._last = this._last.n
|
||||
// }
|
||||
//
|
||||
// this._obj![str] = true
|
||||
//
|
||||
// if (this._objSize === this._capacity) {
|
||||
// // remove least recently used
|
||||
// delete this._obj![this._first.v]
|
||||
// this._first = this._first.n
|
||||
// } else {
|
||||
// this._objSize! += 1
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private _hasForObj(str: string) {
|
||||
// return str in this._obj!
|
||||
// }
|
||||
}
|
83
packages/core/tests/lru-map.spec.ts
Normal file
83
packages/core/tests/lru-map.spec.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { describe, it } from 'mocha'
|
||||
import { expect } from 'chai'
|
||||
import { LruMap } from '../src'
|
||||
|
||||
describe('LruMap', () => {
|
||||
it('Map backend', () => {
|
||||
const lru = new LruMap<string, number>(2)
|
||||
|
||||
lru.set('first', 1)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).false
|
||||
expect(lru.get('first')).eq(1)
|
||||
|
||||
lru.set('first', 42)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).false
|
||||
expect(lru.get('first')).eq(42)
|
||||
|
||||
lru.set('second', 2)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.get('first')).eq(42)
|
||||
expect(lru.get('second')).eq(2)
|
||||
|
||||
lru.set('third', 3)
|
||||
expect(lru.has('first')).false
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.has('third')).true
|
||||
expect(lru.get('first')).eq(undefined)
|
||||
expect(lru.get('second')).eq(2)
|
||||
expect(lru.get('third')).eq(3)
|
||||
|
||||
lru.get('second') // update lru so that last = third
|
||||
lru.set('fourth', 4)
|
||||
expect(lru.has('first')).false
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.has('third')).false
|
||||
expect(lru.has('fourth')).true
|
||||
expect(lru.get('first')).eq(undefined)
|
||||
expect(lru.get('second')).eq(2)
|
||||
expect(lru.get('third')).eq(undefined)
|
||||
expect(lru.get('fourth')).eq(4)
|
||||
})
|
||||
|
||||
it('Object backend', () => {
|
||||
const lru = new LruMap<string, number>(2, true)
|
||||
|
||||
lru.set('first', 1)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).false
|
||||
expect(lru.get('first')).eq(1)
|
||||
|
||||
lru.set('first', 42)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).false
|
||||
expect(lru.get('first')).eq(42)
|
||||
|
||||
lru.set('second', 2)
|
||||
expect(lru.has('first')).true
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.get('first')).eq(42)
|
||||
expect(lru.get('second')).eq(2)
|
||||
|
||||
lru.set('third', 3)
|
||||
expect(lru.has('first')).false
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.has('third')).true
|
||||
expect(lru.get('first')).eq(undefined)
|
||||
expect(lru.get('second')).eq(2)
|
||||
expect(lru.get('third')).eq(3)
|
||||
|
||||
lru.get('second') // update lru so that last = third
|
||||
lru.set('fourth', 4)
|
||||
expect(lru.has('first')).false
|
||||
expect(lru.has('second')).true
|
||||
expect(lru.has('third')).false
|
||||
expect(lru.has('fourth')).true
|
||||
expect(lru.get('first')).eq(undefined)
|
||||
expect(lru.get('second')).eq(2)
|
||||
expect(lru.get('third')).eq(undefined)
|
||||
expect(lru.get('fourth')).eq(4)
|
||||
})
|
||||
})
|
Loading…
Reference in a new issue