chore!: use maps/sets from fuman
This commit is contained in:
parent
d50baeaaa3
commit
5e26949560
14 changed files with 21 additions and 459 deletions
|
@ -1,8 +1,8 @@
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
|
import { LruMap } from '@fuman/utils'
|
||||||
|
|
||||||
import type { RpcCallOptions } from '../../../network/network-manager.js'
|
import type { RpcCallOptions } from '../../../network/network-manager.js'
|
||||||
import type { MustEqual } from '../../../types/utils.js'
|
import type { MustEqual } from '../../../types/utils.js'
|
||||||
import { LruMap } from '../../../utils/lru-map.js'
|
|
||||||
import type { ITelegramClient } from '../../client.types.js'
|
import type { ITelegramClient } from '../../client.types.js'
|
||||||
import { getBusinessConnection } from '../premium/get-business-connection.js'
|
import { getBusinessConnection } from '../premium/get-business-connection.js'
|
||||||
|
|
||||||
|
|
|
@ -53,7 +53,7 @@ export async function getCustomEmojisFromMessages(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const arr = set.toArray()
|
const arr = [...set]
|
||||||
if (!arr.length) return []
|
if (!arr.length) return []
|
||||||
|
|
||||||
return getCustomEmojis(client, arr)
|
return getCustomEmojis(client, arr)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import type { tl } from '@mtcute/tl'
|
import type { tl } from '@mtcute/tl'
|
||||||
|
import { LruMap } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ServiceOptions } from '../../../storage/service/base.js'
|
import type { ServiceOptions } from '../../../storage/service/base.js'
|
||||||
import { BaseService } from '../../../storage/service/base.js'
|
import { BaseService } from '../../../storage/service/base.js'
|
||||||
import { longFromFastString, longToFastString } from '../../../utils/long-utils.js'
|
import { longFromFastString, longToFastString } from '../../../utils/long-utils.js'
|
||||||
import { LruMap } from '../../../utils/lru-map.js'
|
|
||||||
import { getAllPeersFrom, parseMarkedPeerId, toggleChannelIdMark } from '../../../utils/peer-utils.js'
|
import { getAllPeersFrom, parseMarkedPeerId, toggleChannelIdMark } from '../../../utils/peer-utils.js'
|
||||||
import { extractUsernames } from '../../utils/peer-utils.js'
|
import { extractUsernames } from '../../utils/peer-utils.js'
|
||||||
import type { IPeersRepository } from '../repository/peers.js'
|
import type { IPeersRepository } from '../repository/peers.js'
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
|
import { LruMap } from '@fuman/utils'
|
||||||
|
|
||||||
import type { ServiceOptions } from '../../../storage/service/base.js'
|
import type { ServiceOptions } from '../../../storage/service/base.js'
|
||||||
import { BaseService } from '../../../storage/service/base.js'
|
import { BaseService } from '../../../storage/service/base.js'
|
||||||
import { LruMap } from '../../../utils/lru-map.js'
|
|
||||||
import type { IReferenceMessagesRepository } from '../repository/ref-messages.js'
|
import type { IReferenceMessagesRepository } from '../repository/ref-messages.js'
|
||||||
|
|
||||||
export interface RefMessagesServiceOptions {
|
export interface RefMessagesServiceOptions {
|
||||||
|
|
|
@ -2,7 +2,7 @@ import Long from 'long'
|
||||||
import type { mtp, tl } from '@mtcute/tl'
|
import type { mtp, tl } from '@mtcute/tl'
|
||||||
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } from '@mtcute/tl-runtime'
|
||||||
import { TlSerializationCounter } from '@mtcute/tl-runtime'
|
import { TlSerializationCounter } from '@mtcute/tl-runtime'
|
||||||
import { type Deferred, Deque } from '@fuman/utils'
|
import { type Deferred, Deque, LruSet } from '@fuman/utils'
|
||||||
|
|
||||||
import { MtcuteError } from '../types/index.js'
|
import { MtcuteError } from '../types/index.js'
|
||||||
import type {
|
import type {
|
||||||
|
@ -11,7 +11,7 @@ import type {
|
||||||
} from '../utils/index.js'
|
} from '../utils/index.js'
|
||||||
import {
|
import {
|
||||||
LongMap,
|
LongMap,
|
||||||
LruSet,
|
LongSet,
|
||||||
SortedArray,
|
SortedArray,
|
||||||
compareLongs,
|
compareLongs,
|
||||||
getRandomInt,
|
getRandomInt,
|
||||||
|
@ -104,8 +104,8 @@ export class MtprotoSession {
|
||||||
|
|
||||||
/// state ///
|
/// state ///
|
||||||
// recent msg ids
|
// recent msg ids
|
||||||
recentOutgoingMsgIds: LruSet<Long> = new LruSet(1000, true)
|
recentOutgoingMsgIds: LruSet<Long> = new LruSet(1000, LongSet)
|
||||||
recentIncomingMsgIds: LruSet<Long> = new LruSet(1000, true)
|
recentIncomingMsgIds: LruSet<Long> = new LruSet(1000, LongSet)
|
||||||
|
|
||||||
// queues
|
// queues
|
||||||
queuedRpc: Deque<PendingRpc> = new Deque()
|
queuedRpc: Deque<PendingRpc> = new Deque()
|
||||||
|
|
|
@ -228,7 +228,7 @@ export class MultiSessionConnection extends EventEmitter {
|
||||||
|
|
||||||
for (let i = 0; i < this._connections.length; i++) {
|
for (let i = 0; i < this._connections.length; i++) {
|
||||||
const conn = this._connections[i]
|
const conn = this._connections[i]
|
||||||
const total = conn._session.queuedRpc.length + conn._session.pendingMessages.size()
|
const total = conn._session.queuedRpc.length + conn._session.pendingMessages.size
|
||||||
|
|
||||||
if (total < min) {
|
if (total < min) {
|
||||||
min = total
|
min = total
|
||||||
|
|
|
@ -17,8 +17,6 @@ export * from './linked-list.js'
|
||||||
export * from './links/index.js'
|
export * from './links/index.js'
|
||||||
export * from './logger.js'
|
export * from './logger.js'
|
||||||
export * from './long-utils.js'
|
export * from './long-utils.js'
|
||||||
export * from './lru-map.js'
|
|
||||||
export * from './lru-set.js'
|
|
||||||
export * from './misc-utils.js'
|
export * from './misc-utils.js'
|
||||||
export * from './peer-utils.js'
|
export * from './peer-utils.js'
|
||||||
export * from './sorted-array.js'
|
export * from './sorted-array.js'
|
||||||
|
|
|
@ -157,7 +157,7 @@ describe('LongMap', () => {
|
||||||
map.set(Long.fromInt(123), 'test')
|
map.set(Long.fromInt(123), 'test')
|
||||||
map.set(Long.fromInt(456), 'test2')
|
map.set(Long.fromInt(456), 'test2')
|
||||||
|
|
||||||
expect(map.size()).toEqual(2)
|
expect(map.size).toEqual(2)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -216,6 +216,6 @@ describe('LongSet', () => {
|
||||||
set.add(Long.fromInt(123))
|
set.add(Long.fromInt(123))
|
||||||
set.add(Long.fromInt(456))
|
set.add(Long.fromInt(456))
|
||||||
|
|
||||||
expect(set.toArray()).toEqual([Long.fromInt(123), Long.fromInt(456)])
|
expect([...set]).toEqual([Long.fromInt(123), Long.fromInt(456)])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import Long from 'long'
|
import Long from 'long'
|
||||||
import { typed } from '@fuman/utils'
|
import { CustomMap, CustomSet, typed } from '@fuman/utils'
|
||||||
|
|
||||||
import { getRandomInt } from './misc-utils.js'
|
import { getRandomInt } from './misc-utils.js'
|
||||||
|
|
||||||
|
@ -109,41 +109,9 @@ export function longFromFastString(val: string, unsigned = false): Long {
|
||||||
*
|
*
|
||||||
* Uses fast string representation internally.
|
* Uses fast string representation internally.
|
||||||
*/
|
*/
|
||||||
export class LongMap<V> {
|
export class LongMap<V> extends CustomMap<Long, string, V> {
|
||||||
private _map = new Map<string, V>()
|
constructor() {
|
||||||
|
super(longToFastString, longFromFastString)
|
||||||
set(key: Long, value: V): void {
|
|
||||||
this._map.set(longToFastString(key), value)
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: Long): boolean {
|
|
||||||
return this._map.has(longToFastString(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key: Long): V | undefined {
|
|
||||||
return this._map.get(longToFastString(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(key: Long): void {
|
|
||||||
this._map.delete(longToFastString(key))
|
|
||||||
}
|
|
||||||
|
|
||||||
*keys(unsigned?: boolean): IterableIterator<Long> {
|
|
||||||
for (const v of this._map.keys()) {
|
|
||||||
yield longFromFastString(v, unsigned)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
values(): IterableIterator<V> {
|
|
||||||
return this._map.values()
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this._map.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
size(): number {
|
|
||||||
return this._map.size
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,36 +120,8 @@ export class LongMap<V> {
|
||||||
*
|
*
|
||||||
* Uses fast string representation internally
|
* Uses fast string representation internally
|
||||||
*/
|
*/
|
||||||
export class LongSet {
|
export class LongSet extends CustomSet<Long, string> {
|
||||||
private _set = new Set<string>()
|
constructor() {
|
||||||
|
super(longToFastString, longFromFastString)
|
||||||
get size(): number {
|
|
||||||
return this._set.size
|
|
||||||
}
|
|
||||||
|
|
||||||
add(val: Long): void {
|
|
||||||
this._set.add(longToFastString(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(val: Long): void {
|
|
||||||
this._set.delete(longToFastString(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
has(val: Long): boolean {
|
|
||||||
return this._set.has(longToFastString(val))
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this._set.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
toArray(): Long[] {
|
|
||||||
const arr: Long[] = []
|
|
||||||
|
|
||||||
for (const v of this._set) {
|
|
||||||
arr.push(longFromFastString(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
return arr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { LruMap } from './lru-map.js'
|
|
||||||
|
|
||||||
describe('LruMap', () => {
|
|
||||||
it('should maintain maximum size by removing oldest added', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
lru.set('third', 3)
|
|
||||||
|
|
||||||
expect(lru.has('first')).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should update the last added item', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
lru.set('first', 42)
|
|
||||||
lru.set('third', 3)
|
|
||||||
|
|
||||||
expect(lru.get('first')).toEqual(42)
|
|
||||||
expect(lru.has('second')).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should update the last used item', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
lru.get('first')
|
|
||||||
lru.set('third', 3)
|
|
||||||
lru.get('third')
|
|
||||||
|
|
||||||
expect(lru.get('first')).toEqual(1)
|
|
||||||
expect(lru.has('second')).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should allow deleting items', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
lru.set('third', 3) // first is now deleted
|
|
||||||
lru.delete('second')
|
|
||||||
|
|
||||||
expect(lru.has('first')).toBeFalsy()
|
|
||||||
expect(lru.has('second')).toBeFalsy()
|
|
||||||
expect(lru.has('third')).toBeTruthy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle deleting all items', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
lru.delete('second')
|
|
||||||
lru.delete('first')
|
|
||||||
|
|
||||||
expect(lru.has('first')).toBeFalsy()
|
|
||||||
expect(lru.has('second')).toBeFalsy()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return undefined for non-existing items', () => {
|
|
||||||
const lru = new LruMap<string, number>(2)
|
|
||||||
|
|
||||||
lru.set('first', 1)
|
|
||||||
lru.set('second', 2)
|
|
||||||
|
|
||||||
expect(lru.get('third')).toEqual(undefined)
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,145 +0,0 @@
|
||||||
/* eslint-disable ts/no-unsafe-assignment */
|
|
||||||
/* eslint-disable ts/no-unsafe-argument */
|
|
||||||
// ^^ because of performance reasons
|
|
||||||
import { LongMap } from './long-utils.js'
|
|
||||||
|
|
||||||
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
|
|
||||||
*
|
|
||||||
* 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 string | number, V> {
|
|
||||||
private _capacity: number
|
|
||||||
private _first?: TwoWayLinkedList<K, V>
|
|
||||||
private _last?: TwoWayLinkedList<K, V>
|
|
||||||
|
|
||||||
private _map: Map<K, TwoWayLinkedList<K, V>>
|
|
||||||
|
|
||||||
private _size = 0
|
|
||||||
|
|
||||||
constructor(capacity: number, forLong = false) {
|
|
||||||
this._capacity = capacity
|
|
||||||
|
|
||||||
this._map = forLong ? (new LongMap() as any) : new Map()
|
|
||||||
}
|
|
||||||
|
|
||||||
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._map.get(key)
|
|
||||||
if (!item) return undefined
|
|
||||||
|
|
||||||
this._markUsed(item)
|
|
||||||
|
|
||||||
return item.v
|
|
||||||
}
|
|
||||||
|
|
||||||
has(key: K): boolean {
|
|
||||||
return this._map.has(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
this._map.delete(item.k)
|
|
||||||
this._size -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: K, value: V): void {
|
|
||||||
let item = this._map.get(key)
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
// already in cache, update
|
|
||||||
item.v = value
|
|
||||||
this._markUsed(item)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
item = {
|
|
||||||
k: key,
|
|
||||||
v: value,
|
|
||||||
// for jit to optimize stuff
|
|
||||||
n: undefined,
|
|
||||||
p: undefined,
|
|
||||||
}
|
|
||||||
this._map.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) {
|
|
||||||
this._remove(oldest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
delete(key: K): void {
|
|
||||||
const item = this._map.get(key)
|
|
||||||
if (item) this._remove(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this._map.clear()
|
|
||||||
this._first = undefined
|
|
||||||
this._last = undefined
|
|
||||||
this._size = 0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,94 +0,0 @@
|
||||||
import Long from 'long'
|
|
||||||
import { describe, expect, it } from 'vitest'
|
|
||||||
|
|
||||||
import { LruSet } from './lru-set.js'
|
|
||||||
|
|
||||||
describe('LruSet', () => {
|
|
||||||
describe('for strings', () => {
|
|
||||||
it('when 1 item is added, it is in the set', () => {
|
|
||||||
const set = new LruSet(2)
|
|
||||||
|
|
||||||
set.add('first')
|
|
||||||
expect(set.has('first')).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when =capacity items are added, they are all in the set', () => {
|
|
||||||
const set = new LruSet(2)
|
|
||||||
|
|
||||||
set.add('first')
|
|
||||||
set.add('second')
|
|
||||||
|
|
||||||
expect(set.has('first')).toEqual(true)
|
|
||||||
expect(set.has('second')).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when >capacity items are added, only the last <capacity> are in the set', () => {
|
|
||||||
const set = new LruSet(2)
|
|
||||||
|
|
||||||
set.add('first')
|
|
||||||
set.add('second')
|
|
||||||
set.add('third')
|
|
||||||
|
|
||||||
expect(set.has('first')).toEqual(false)
|
|
||||||
expect(set.has('second')).toEqual(true)
|
|
||||||
expect(set.has('third')).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when the same added is while not eliminated, it is ignored', () => {
|
|
||||||
const set = new LruSet(2)
|
|
||||||
|
|
||||||
set.add('first')
|
|
||||||
set.add('second')
|
|
||||||
set.add('first')
|
|
||||||
set.add('third')
|
|
||||||
|
|
||||||
expect(set.has('first')).toEqual(false)
|
|
||||||
expect(set.has('second')).toEqual(true)
|
|
||||||
expect(set.has('third')).toEqual(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('for Longs', () => {
|
|
||||||
it('when 1 item is added, it is in the set', () => {
|
|
||||||
const set = new LruSet(2, true)
|
|
||||||
|
|
||||||
set.add(Long.fromNumber(1))
|
|
||||||
expect(set.has(Long.fromNumber(1))).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when =capacity items are added, they are all in the set', () => {
|
|
||||||
const set = new LruSet(2, true)
|
|
||||||
|
|
||||||
set.add(Long.fromNumber(1))
|
|
||||||
set.add(Long.fromNumber(2))
|
|
||||||
|
|
||||||
expect(set.has(Long.fromNumber(1))).toEqual(true)
|
|
||||||
expect(set.has(Long.fromNumber(2))).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when >capacity items are added, only the last <capacity> are in the set', () => {
|
|
||||||
const set = new LruSet(2, true)
|
|
||||||
|
|
||||||
set.add(Long.fromNumber(1))
|
|
||||||
set.add(Long.fromNumber(2))
|
|
||||||
set.add(Long.fromNumber(3))
|
|
||||||
|
|
||||||
expect(set.has(Long.fromNumber(1))).toEqual(false)
|
|
||||||
expect(set.has(Long.fromNumber(2))).toEqual(true)
|
|
||||||
expect(set.has(Long.fromNumber(3))).toEqual(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when the same added is while not eliminated, it is ignored', () => {
|
|
||||||
const set = new LruSet(2, true)
|
|
||||||
|
|
||||||
set.add(Long.fromNumber(1))
|
|
||||||
set.add(Long.fromNumber(2))
|
|
||||||
set.add(Long.fromNumber(1))
|
|
||||||
set.add(Long.fromNumber(3))
|
|
||||||
|
|
||||||
expect(set.has(Long.fromNumber(1))).toEqual(false)
|
|
||||||
expect(set.has(Long.fromNumber(2))).toEqual(true)
|
|
||||||
expect(set.has(Long.fromNumber(3))).toEqual(true)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
|
@ -1,65 +0,0 @@
|
||||||
/* eslint-disable ts/no-unsafe-argument */
|
|
||||||
// ^^ because of performance reasons
|
|
||||||
import type Long from 'long'
|
|
||||||
|
|
||||||
import { LongSet } from './long-utils.js'
|
|
||||||
|
|
||||||
interface OneWayLinkedList<T> {
|
|
||||||
v: T
|
|
||||||
n?: OneWayLinkedList<T>
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple class implementing LRU-like behaviour for a Set.
|
|
||||||
*
|
|
||||||
* Note: this is not exactly LRU, but rather "least recently added"
|
|
||||||
* and doesn't mark items as recently added if they are already in the set.
|
|
||||||
* This is enough for our use case, so we don't bother with more complex implementation.
|
|
||||||
*
|
|
||||||
* Used to store recently received message IDs in {@link SessionConnection}
|
|
||||||
*
|
|
||||||
* Uses one-way linked list internally to keep track of insertion order
|
|
||||||
*/
|
|
||||||
export class LruSet<T extends string | number | Long> {
|
|
||||||
private _capacity: number
|
|
||||||
private _first?: OneWayLinkedList<T>
|
|
||||||
private _last?: OneWayLinkedList<T>
|
|
||||||
|
|
||||||
private _set: Set<T> | LongSet
|
|
||||||
|
|
||||||
constructor(capacity: number, forLong = false) {
|
|
||||||
this._capacity = capacity
|
|
||||||
|
|
||||||
this._set = forLong ? new LongSet() : new Set()
|
|
||||||
}
|
|
||||||
|
|
||||||
clear(): void {
|
|
||||||
this._first = this._last = undefined
|
|
||||||
this._set.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
add(val: T): void {
|
|
||||||
if (this._set.has(val as any)) return
|
|
||||||
|
|
||||||
if (!this._first) this._first = { v: val }
|
|
||||||
|
|
||||||
if (!this._last) {
|
|
||||||
this._last = this._first
|
|
||||||
} else {
|
|
||||||
this._last.n = { v: val }
|
|
||||||
this._last = this._last.n
|
|
||||||
}
|
|
||||||
|
|
||||||
this._set.add(val as any)
|
|
||||||
|
|
||||||
if (this._set.size > this._capacity && this._first) {
|
|
||||||
// remove least recently used
|
|
||||||
this._set.delete(this._first.v as any)
|
|
||||||
this._first = this._first.n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
has(val: T): boolean {
|
|
||||||
return this._set.has(val as any)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { LruMap, asyncResettable, timers } from '@mtcute/core/utils.js'
|
import { asyncResettable, timers } from '@mtcute/core/utils.js'
|
||||||
import type { MaybePromise } from '@mtcute/core'
|
import type { MaybePromise } from '@mtcute/core'
|
||||||
|
import { LruMap } from '@fuman/utils'
|
||||||
|
|
||||||
import type { IStateStorageProvider } from './provider.js'
|
import type { IStateStorageProvider } from './provider.js'
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue