chore!: use deque from fuman

This commit is contained in:
alina 🌸 2024-09-19 21:35:41 +03:00
parent 8b4f4984a1
commit 19ab208be0
Signed by: teidesu
SSH key fingerprint: SHA256:uNeCpw6aTSU4aIObXLvHfLkDa82HWH9EiOj9AXOIRpI
10 changed files with 13 additions and 442 deletions

View file

@ -1,9 +1,8 @@
import type { tl } from '@mtcute/tl' import type { tl } from '@mtcute/tl'
import { AsyncLock, Deferred, timers } from '@fuman/utils' import { AsyncLock, Deferred, Deque, timers } from '@fuman/utils'
import { MtArgumentError, MtTimeoutError } from '../../types/errors.js' import { MtArgumentError, MtTimeoutError } from '../../types/errors.js'
import type { MaybePromise } from '../../types/utils.js' import type { MaybePromise } from '../../types/utils.js'
import { Deque } from '../../utils/deque.js'
import { getMarkedPeerId } from '../../utils/peer-utils.js' import { getMarkedPeerId } from '../../utils/peer-utils.js'
import type { ITelegramClient } from '../client.types.js' import type { ITelegramClient } from '../client.types.js'
import { getPeerDialogs } from '../methods/dialogs/get-peer-dialogs.js' import { getPeerDialogs } from '../methods/dialogs/get-peer-dialogs.js'
@ -55,7 +54,7 @@ export class Conversation {
private _lock = new AsyncLock() private _lock = new AsyncLock()
private _pendingEditMessage: Map<number, QueuedHandler<Message>> = new Map() private _pendingEditMessage: Map<number, QueuedHandler<Message>> = new Map()
private _recentEdits = new Deque<Message>(10) private _recentEdits = new Deque<Message>(undefined, { capacity: 10 })
private _pendingRead: Map<number, QueuedHandler<void>> = new Map() private _pendingRead: Map<number, QueuedHandler<void>> = new Map()
@ -627,7 +626,7 @@ export class Conversation {
private _processRecentEdits() { private _processRecentEdits() {
if (!this._recentEdits.length) return if (!this._recentEdits.length) return
const iter = this._recentEdits.iter() const iter = this._recentEdits[Symbol.iterator]()
let it let it
while (!(it = iter.next()).done) { while (!(it = iter.next()).done) {

View file

@ -1,6 +1,6 @@
import { tl } from '@mtcute/tl' import { tl } from '@mtcute/tl'
import { AsyncLock, ConditionVariable, timers } from '@fuman/utils'
import Long from 'long' import Long from 'long'
import { AsyncLock, ConditionVariable, Deque, timers } from '@fuman/utils'
import { MtArgumentError } from '../../types/errors.js' import { MtArgumentError } from '../../types/errors.js'
import type { MaybePromise } from '../../types/utils.js' import type { MaybePromise } from '../../types/utils.js'
@ -9,7 +9,6 @@ import type {
Logger, Logger,
} from '../../utils/index.js' } from '../../utils/index.js'
import { import {
Deque,
EarlyTimer, EarlyTimer,
SortedLinkedList, SortedLinkedList,
getBarePeerId, getBarePeerId,

View file

@ -1,7 +1,7 @@
import type { tl } from '@mtcute/tl' import type { tl } from '@mtcute/tl'
import type { AsyncLock, ConditionVariable, timers } from '@fuman/utils' import type { AsyncLock, ConditionVariable, Deque, timers } from '@fuman/utils'
import type { Deque, EarlyTimer, Logger, SortedLinkedList } from '../../utils/index.js' import type { EarlyTimer, Logger, SortedLinkedList } from '../../utils/index.js'
import type { CurrentUserInfo } from '../storage/service/current-user.js' import type { CurrentUserInfo } from '../storage/service/current-user.js'
import type { PeersIndex } from '../types/peers/peers-index.js' import type { PeersIndex } from '../types/peers/peers-index.js'

View file

@ -1,4 +1,5 @@
import { Deque } from '../../utils/deque.js' import { Deque } from '@fuman/utils'
import type { ITelegramClient } from '../client.types.js' import type { ITelegramClient } from '../client.types.js'
type Resolve<T> = (value: T | PromiseLike<T>) => void type Resolve<T> = (value: T | PromiseLike<T>) => void

View file

@ -1,4 +1,4 @@
import { Deque } from '../../utils/deque.js' import { Deque } from '@fuman/utils'
export class RpsMeter { export class RpsMeter {
_hits: Deque<bigint> _hits: Deque<bigint>
@ -12,7 +12,7 @@ export class RpsMeter {
throw new Error('RPS meter is not supported on this platform') throw new Error('RPS meter is not supported on this platform')
} }
this._hits = new Deque<bigint>(size) this._hits = new Deque<bigint>(undefined, { capacity: size })
this.time = BigInt(time) * BigInt(1e6) this.time = BigInt(time) * BigInt(1e6)
} }
@ -27,7 +27,7 @@ export class RpsMeter {
const now = process.hrtime.bigint() const now = process.hrtime.bigint()
const window = now - this.time const window = now - this.time
// find the first hit within the last `time` ms // find the first hit within the last `time` ms
const iter = this._hits.iter() const iter = this._hits[Symbol.iterator]()
let first = iter.next() let first = iter.next()
let idx = 0 let idx = 0

View file

@ -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 } from '@fuman/utils' import { type Deferred, Deque } from '@fuman/utils'
import { MtcuteError } from '../types/index.js' import { MtcuteError } from '../types/index.js'
import type { import type {
@ -10,8 +10,6 @@ import type {
Logger, Logger,
} from '../utils/index.js' } from '../utils/index.js'
import { import {
Deque,
LongMap, LongMap,
LruSet, LruSet,
SortedArray, SortedArray,

View file

@ -1489,7 +1489,7 @@ export class SessionConnection extends PersistentConnection {
} else { } else {
// in case rpc wasn't sent yet (or had some error), // in case rpc wasn't sent yet (or had some error),
// we can simply remove it from queue // we can simply remove it from queue
this._session.queuedRpc.remove(rpc) this._session.queuedRpc.removeBy(it => it === rpc)
} }
} }

View file

@ -1,173 +0,0 @@
import { describe, expect, it } from 'vitest'
import { Deque } from './deque.js'
describe('Deque', () => {
function setupWrapping() {
const d = new Deque<number>(Infinity, /* capacity: */ 8)
for (let i = 1; i <= 7; i++) {
d.pushBack(i)
}
// [1, 2, 3, 4, 5, 6, 7, _]
d.popFront()
d.popFront()
d.popFront()
// [_, _, _, 4, 5, 6, 7, _]
d.pushBack(8)
d.pushBack(9)
d.pushBack(10)
// [9, 10, _, 4, 5, 6, 7, 8]
return d
}
it('should push items to the end correctly', () => {
const d = new Deque<number>()
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
expect(d.toArray()).eql([1, 2, 3])
expect([...d.iter()]).eql([1, 2, 3])
})
it('should push items to the start correctly', () => {
const d = new Deque<number>()
d.pushFront(1)
d.pushFront(2)
d.pushFront(3)
expect(d.toArray()).eql([3, 2, 1])
})
it('should pop items from the end correctly', () => {
const d = new Deque<number>()
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
expect(d.popBack()).eql(3)
expect(d.popBack()).eql(2)
expect(d.popBack()).eql(1)
})
it('should pop items from the start correctly', () => {
const d = new Deque<number>()
d.pushFront(1)
d.pushFront(2)
d.pushFront(3)
expect(d.popFront()).eql(3)
expect(d.popFront()).eql(2)
expect(d.popFront()).eql(1)
})
it('should return the correct length', () => {
const d = new Deque<number>()
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
expect(d.length).eql(3)
})
it('accessors should correctly wrap around', () => {
const d = setupWrapping()
expect(d.toArray()).eql([4, 5, 6, 7, 8, 9, 10])
expect([...d.iter()]).eql([4, 5, 6, 7, 8, 9, 10])
expect(d.at(6)).eql(10)
})
it('should handle maxLength by removing items from the other end', () => {
const d = new Deque<number>(4)
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
d.pushBack(4)
d.pushBack(5)
expect(d.toArray()).eql([2, 3, 4, 5])
d.pushFront(6)
expect(d.toArray()).eql([6, 2, 3, 4])
})
it('should correctly resize', () => {
const d = new Deque<number>(Infinity, /* capacity: */ 8)
for (let i = 0; i <= 16; i++) {
d.pushBack(i)
}
const expected = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
expect(d.length).eql(17)
expect(d.toArray()).eql(expected)
expect([...d.iter()]).eql(expected)
expect(d.at(16)).eql(16)
})
it('should correctly remove items', () => {
const d = new Deque<number>(Infinity, /* capacity: */ 16)
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
d.pushBack(4)
d.pushBack(5)
d.remove(1)
d.remove(4)
expect(d.toArray()).eql([2, 3, 5])
})
it('should correctly remove items by predicate (the first matched)', () => {
const d = new Deque<number>(Infinity, /* capacity: */ 4)
d.pushBack(1)
d.pushBack(2)
d.pushBack(3)
d.pushBack(4)
d.pushBack(5)
d.removeBy(it => it >= 2)
expect(d.toArray()).eql([1, 3, 4, 5])
})
it('should correctly peek at edges', () => {
const d = new Deque<number>()
d.pushBack(1)
d.pushBack(2)
expect(d.peekFront()).eql(1)
expect(d.peekBack()).eql(2)
})
it('should correctly clear', () => {
const d = new Deque<number>()
d.pushBack(1)
d.pushBack(2)
d.clear()
expect(d.length).eql(0)
expect(d.toArray()).eql([])
expect([...d.iter()]).eql([])
})
})

View file

@ -1,252 +0,0 @@
// ^^ because of performance reasons
const MIN_INITIAL_CAPACITY = 8
// System.arraycopy from java
function arraycopy<T>(src: T[], srcPos: number, dest: T[], destPos: number, length: number) {
for (let i = 0; i < length; i++) {
dest[destPos + i] = src[srcPos + i]
}
}
/**
* Deque implementation.
* `undefined` values are not allowed
*
* Based on Java implementation
*/
export class Deque<T> {
// another implementation variant would be to use
// blocks of fixed size instead of a single array
// to avoid copying stuff around
protected _elements: (T | undefined)[]
protected _head = 0
protected _tail = 0
protected _capacity: number
constructor(
readonly maxLength: number = Infinity,
minCapacity: number = maxLength === Infinity ? MIN_INITIAL_CAPACITY : maxLength,
) {
let capacity = minCapacity
if (capacity < MIN_INITIAL_CAPACITY) {
capacity = MIN_INITIAL_CAPACITY
}
if (capacity !== MIN_INITIAL_CAPACITY) {
// Find the best power of two to hold elements.
capacity |= capacity >>> 1
capacity |= capacity >>> 2
capacity |= capacity >>> 4
capacity |= capacity >>> 8
capacity |= capacity >>> 16
capacity += 1
if (capacity < 0) {
// too many
capacity >>>= 1
}
}
this._elements = new Array<T | undefined>(capacity)
this._capacity = capacity
}
protected _resize(): void {
const p = this._head
const n = this._capacity
const r = n - p // number of elements to the right of the head
const newCapacity = n << 1
if (newCapacity < 0) throw new Error('Deque is too big')
const arr = new Array<T | undefined>(newCapacity)
// copy items to the new array
// copy head till the end of arr
arraycopy(this._elements, p, arr, 0, r)
// copy from start to tail
arraycopy(this._elements, 0, arr, r, p)
this._elements = arr
this._head = 0
this._tail = n
this._capacity = newCapacity
}
pushBack(item: T): void {
if (item === undefined) throw new Error('item can not be undefined')
this._elements[this._tail] = item
if ((this._tail = (this._tail + 1) & (this._capacity - 1)) === this._head) {
this._resize()
}
if (this.length > this.maxLength) {
this.popFront()
}
}
pushFront(item: T): void {
if (item === undefined) throw new Error('item can not be undefined')
this._elements[(this._head = (this._head - 1) & (this._capacity - 1))] = item
if (this._head === this._tail) {
this._resize()
}
if (this.length > this.maxLength) {
this.popBack()
}
}
popFront(): T | undefined {
const h = this._head
const res = this._elements[h]
if (res === undefined) return undefined
this._elements[h] = undefined
this._head = (h + 1) & (this._capacity - 1)
return res
}
popBack(): T | undefined {
const t = (this._tail - 1) & (this._capacity - 1)
const res = this._elements[t]
if (res === undefined) return undefined
this._elements[t] = undefined
this._tail = t
return res
}
peekFront(): T | undefined {
return this._elements[this._head]
}
peekBack(): T | undefined {
return this._elements[(this._tail - 1) & (this._capacity - 1)]
}
get length(): number {
return (this._tail - this._head) & (this._capacity - 1)
}
toArray(): T[] {
const sz = this.length
if (sz === 0) return []
const arr = new Array(sz)
if (this._head < this._tail) {
// copy as-is, head to tail
arraycopy(this._elements, this._head, arr, 0, sz)
} else {
const headPortion = this._capacity - this._head
// copy from head to end
arraycopy(this._elements, this._head, arr, 0, headPortion)
// copy from start to tail
arraycopy(this._elements, 0, arr, headPortion, this._tail)
}
return arr as T[]
}
protected _delete(i: number): void {
const els = this._elements
const mask = this._capacity - 1
const h = this._head
const t = this._tail
const front = (i - h) & mask
const back = (t - i) & mask
if (front < back) {
if (h <= i) {
arraycopy(els, h, els, h + 1, front)
} else {
// wrap
arraycopy(els, 0, els, 1, i)
els[0] = els[mask]
arraycopy(els, h, els, h + 1, mask - h)
}
els[h] = undefined
this._head = (h + 1) & mask
} else if (i < t) {
// copy null tail as well
arraycopy(els, i + 1, els, i, back)
this._tail = t - 1
} else {
// wrap
arraycopy(els, i + 1, els, i, mask - i)
els[mask] = els[0]
arraycopy(els, 1, els, 0, t)
this._tail = (t - 1) & mask
}
}
remove(item: T): void {
const mask = this._capacity - 1
let i = this._head
let val: T | undefined
while ((val = this._elements[i]) !== undefined) {
if (item === val) {
this._delete(i)
return
}
i = (i + 1) & mask
}
}
removeBy(pred: (it: T) => boolean): void {
const mask = this._capacity - 1
let i = this._head
let val: T | undefined
while ((val = this._elements[i]) !== undefined) {
if (pred(val)) {
this._delete(i)
return
}
i = (i + 1) & mask
}
}
at(idx: number): T | undefined {
return this._elements[(this._head + idx) & (this._capacity - 1)]
}
*iter(): IterableIterator<T> {
const sz = this.length
if (sz === 0) return
if (this._head < this._tail) {
// head to tail
for (let i = 0; i < sz; i++) {
yield this._elements[this._head + i]!
}
} else {
const headPortion = this._capacity - this._head
// head to end
for (let i = 0; i < headPortion; i++) {
yield this._elements[this._head + i]!
}
// start to tail
for (let i = 0; i < this._tail; i++) {
yield this._elements[i]!
}
}
}
clear(): void {
this._elements = new Array<T | undefined>(this._capacity)
this._head = this._tail = 0
}
}

View file

@ -11,7 +11,6 @@ export * from './bigint-utils.js'
export * from './composer.js' export * from './composer.js'
export * from './crypto/index.js' export * from './crypto/index.js'
export * from './dcs.js' export * from './dcs.js'
export * from './deque.js'
export * from './early-timer.js' export * from './early-timer.js'
export * from './function-utils.js' export * from './function-utils.js'
export * from './linked-list.js' export * from './linked-list.js'