chore!: use maps/sets from fuman

This commit is contained in:
alina 🌸 2024-09-20 02:43:43 +03:00
parent d50baeaaa3
commit 5e26949560
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
14 changed files with 21 additions and 459 deletions

View file

@ -1,8 +1,8 @@
import type { tl } from '@mtcute/tl'
import { LruMap } from '@fuman/utils'
import type { RpcCallOptions } from '../../../network/network-manager.js'
import type { MustEqual } from '../../../types/utils.js'
import { LruMap } from '../../../utils/lru-map.js'
import type { ITelegramClient } from '../../client.types.js'
import { getBusinessConnection } from '../premium/get-business-connection.js'

View file

@ -53,7 +53,7 @@ export async function getCustomEmojisFromMessages(
}
}
const arr = set.toArray()
const arr = [...set]
if (!arr.length) return []
return getCustomEmojis(client, arr)

View file

@ -1,10 +1,10 @@
import Long from 'long'
import type { tl } from '@mtcute/tl'
import { LruMap } from '@fuman/utils'
import type { ServiceOptions } from '../../../storage/service/base.js'
import { BaseService } from '../../../storage/service/base.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 { extractUsernames } from '../../utils/peer-utils.js'
import type { IPeersRepository } from '../repository/peers.js'

View file

@ -1,6 +1,7 @@
import { LruMap } from '@fuman/utils'
import type { ServiceOptions } 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'
export interface RefMessagesServiceOptions {

View file

@ -2,7 +2,7 @@ import Long from 'long'
import type { mtp, tl } from '@mtcute/tl'
import type { TlBinaryWriter, TlReaderMap, TlWriterMap } 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 type {
@ -11,7 +11,7 @@ import type {
} from '../utils/index.js'
import {
LongMap,
LruSet,
LongSet,
SortedArray,
compareLongs,
getRandomInt,
@ -104,8 +104,8 @@ export class MtprotoSession {
/// state ///
// recent msg ids
recentOutgoingMsgIds: LruSet<Long> = new LruSet(1000, true)
recentIncomingMsgIds: LruSet<Long> = new LruSet(1000, true)
recentOutgoingMsgIds: LruSet<Long> = new LruSet(1000, LongSet)
recentIncomingMsgIds: LruSet<Long> = new LruSet(1000, LongSet)
// queues
queuedRpc: Deque<PendingRpc> = new Deque()

View file

@ -228,7 +228,7 @@ export class MultiSessionConnection extends EventEmitter {
for (let i = 0; i < this._connections.length; 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) {
min = total

View file

@ -17,8 +17,6 @@ export * from './linked-list.js'
export * from './links/index.js'
export * from './logger.js'
export * from './long-utils.js'
export * from './lru-map.js'
export * from './lru-set.js'
export * from './misc-utils.js'
export * from './peer-utils.js'
export * from './sorted-array.js'

View file

@ -157,7 +157,7 @@ describe('LongMap', () => {
map.set(Long.fromInt(123), 'test')
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(456))
expect(set.toArray()).toEqual([Long.fromInt(123), Long.fromInt(456)])
expect([...set]).toEqual([Long.fromInt(123), Long.fromInt(456)])
})
})

View file

@ -1,5 +1,5 @@
import Long from 'long'
import { typed } from '@fuman/utils'
import { CustomMap, CustomSet, typed } from '@fuman/utils'
import { getRandomInt } from './misc-utils.js'
@ -109,41 +109,9 @@ export function longFromFastString(val: string, unsigned = false): Long {
*
* Uses fast string representation internally.
*/
export class LongMap<V> {
private _map = new Map<string, V>()
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
export class LongMap<V> extends CustomMap<Long, string, V> {
constructor() {
super(longToFastString, longFromFastString)
}
}
@ -152,36 +120,8 @@ export class LongMap<V> {
*
* Uses fast string representation internally
*/
export class LongSet {
private _set = new Set<string>()
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
export class LongSet extends CustomSet<Long, string> {
constructor() {
super(longToFastString, longFromFastString)
}
}

View file

@ -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)
})
})

View file

@ -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
}
}

View file

@ -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)
})
})
})

View file

@ -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)
}
}

View file

@ -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 { LruMap } from '@fuman/utils'
import type { IStateStorageProvider } from './provider.js'