chore!: use deque from fuman
This commit is contained in:
parent
8b4f4984a1
commit
19ab208be0
10 changed files with 13 additions and 442 deletions
|
@ -1,9 +1,8 @@
|
|||
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 type { MaybePromise } from '../../types/utils.js'
|
||||
import { Deque } from '../../utils/deque.js'
|
||||
import { getMarkedPeerId } from '../../utils/peer-utils.js'
|
||||
import type { ITelegramClient } from '../client.types.js'
|
||||
import { getPeerDialogs } from '../methods/dialogs/get-peer-dialogs.js'
|
||||
|
@ -55,7 +54,7 @@ export class Conversation {
|
|||
private _lock = new AsyncLock()
|
||||
|
||||
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()
|
||||
|
||||
|
@ -627,7 +626,7 @@ export class Conversation {
|
|||
private _processRecentEdits() {
|
||||
if (!this._recentEdits.length) return
|
||||
|
||||
const iter = this._recentEdits.iter()
|
||||
const iter = this._recentEdits[Symbol.iterator]()
|
||||
let it
|
||||
|
||||
while (!(it = iter.next()).done) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { tl } from '@mtcute/tl'
|
||||
import { AsyncLock, ConditionVariable, timers } from '@fuman/utils'
|
||||
import Long from 'long'
|
||||
import { AsyncLock, ConditionVariable, Deque, timers } from '@fuman/utils'
|
||||
|
||||
import { MtArgumentError } from '../../types/errors.js'
|
||||
import type { MaybePromise } from '../../types/utils.js'
|
||||
|
@ -9,7 +9,6 @@ import type {
|
|||
Logger,
|
||||
} from '../../utils/index.js'
|
||||
import {
|
||||
Deque,
|
||||
EarlyTimer,
|
||||
SortedLinkedList,
|
||||
getBarePeerId,
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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 { PeersIndex } from '../types/peers/peers-index.js'
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Deque } from '../../utils/deque.js'
|
||||
import { Deque } from '@fuman/utils'
|
||||
|
||||
import type { ITelegramClient } from '../client.types.js'
|
||||
|
||||
type Resolve<T> = (value: T | PromiseLike<T>) => void
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Deque } from '../../utils/deque.js'
|
||||
import { Deque } from '@fuman/utils'
|
||||
|
||||
export class RpsMeter {
|
||||
_hits: Deque<bigint>
|
||||
|
@ -12,7 +12,7 @@ export class RpsMeter {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ export class RpsMeter {
|
|||
const now = process.hrtime.bigint()
|
||||
const window = now - this.time
|
||||
// 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 idx = 0
|
||||
|
||||
|
|
|
@ -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 } from '@fuman/utils'
|
||||
import { type Deferred, Deque } from '@fuman/utils'
|
||||
|
||||
import { MtcuteError } from '../types/index.js'
|
||||
import type {
|
||||
|
@ -10,8 +10,6 @@ import type {
|
|||
Logger,
|
||||
} from '../utils/index.js'
|
||||
import {
|
||||
Deque,
|
||||
|
||||
LongMap,
|
||||
LruSet,
|
||||
SortedArray,
|
||||
|
|
|
@ -1489,7 +1489,7 @@ export class SessionConnection extends PersistentConnection {
|
|||
} else {
|
||||
// in case rpc wasn't sent yet (or had some error),
|
||||
// we can simply remove it from queue
|
||||
this._session.queuedRpc.remove(rpc)
|
||||
this._session.queuedRpc.removeBy(it => it === rpc)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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([])
|
||||
})
|
||||
})
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -11,7 +11,6 @@ export * from './bigint-utils.js'
|
|||
export * from './composer.js'
|
||||
export * from './crypto/index.js'
|
||||
export * from './dcs.js'
|
||||
export * from './deque.js'
|
||||
export * from './early-timer.js'
|
||||
export * from './function-utils.js'
|
||||
export * from './linked-list.js'
|
||||
|
|
Loading…
Reference in a new issue