diff --git a/packages/core/src/highlevel/types/conversation.ts b/packages/core/src/highlevel/types/conversation.ts index d8cbb1d2..c99fe3b6 100644 --- a/packages/core/src/highlevel/types/conversation.ts +++ b/packages/core/src/highlevel/types/conversation.ts @@ -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> = new Map() - private _recentEdits = new Deque(10) + private _recentEdits = new Deque(undefined, { capacity: 10 }) private _pendingRead: Map> = 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) { diff --git a/packages/core/src/highlevel/updates/manager.ts b/packages/core/src/highlevel/updates/manager.ts index 90a41f79..02fc7e5e 100644 --- a/packages/core/src/highlevel/updates/manager.ts +++ b/packages/core/src/highlevel/updates/manager.ts @@ -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, diff --git a/packages/core/src/highlevel/updates/types.ts b/packages/core/src/highlevel/updates/types.ts index d53350fb..320265e4 100644 --- a/packages/core/src/highlevel/updates/types.ts +++ b/packages/core/src/highlevel/updates/types.ts @@ -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' diff --git a/packages/core/src/highlevel/utils/query-batcher.ts b/packages/core/src/highlevel/utils/query-batcher.ts index cdbcac84..8d32949f 100644 --- a/packages/core/src/highlevel/utils/query-batcher.ts +++ b/packages/core/src/highlevel/utils/query-batcher.ts @@ -1,4 +1,5 @@ -import { Deque } from '../../utils/deque.js' +import { Deque } from '@fuman/utils' + import type { ITelegramClient } from '../client.types.js' type Resolve = (value: T | PromiseLike) => void diff --git a/packages/core/src/highlevel/utils/rps-meter.ts b/packages/core/src/highlevel/utils/rps-meter.ts index 8fdf2a9c..18c563bf 100644 --- a/packages/core/src/highlevel/utils/rps-meter.ts +++ b/packages/core/src/highlevel/utils/rps-meter.ts @@ -1,4 +1,4 @@ -import { Deque } from '../../utils/deque.js' +import { Deque } from '@fuman/utils' export class RpsMeter { _hits: Deque @@ -12,7 +12,7 @@ export class RpsMeter { throw new Error('RPS meter is not supported on this platform') } - this._hits = new Deque(size) + this._hits = new Deque(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 diff --git a/packages/core/src/network/mtproto-session.ts b/packages/core/src/network/mtproto-session.ts index 99e02fb8..4ad50b6d 100644 --- a/packages/core/src/network/mtproto-session.ts +++ b/packages/core/src/network/mtproto-session.ts @@ -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, diff --git a/packages/core/src/network/session-connection.ts b/packages/core/src/network/session-connection.ts index 14b5fee1..c634625f 100644 --- a/packages/core/src/network/session-connection.ts +++ b/packages/core/src/network/session-connection.ts @@ -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) } } diff --git a/packages/core/src/utils/deque.test.ts b/packages/core/src/utils/deque.test.ts deleted file mode 100644 index da8df267..00000000 --- a/packages/core/src/utils/deque.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -import { describe, expect, it } from 'vitest' - -import { Deque } from './deque.js' - -describe('Deque', () => { - function setupWrapping() { - const d = new Deque(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() - - 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() - - 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() - - 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() - - 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() - - 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(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(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(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(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() - - d.pushBack(1) - d.pushBack(2) - - expect(d.peekFront()).eql(1) - expect(d.peekBack()).eql(2) - }) - - it('should correctly clear', () => { - const d = new Deque() - - d.pushBack(1) - d.pushBack(2) - - d.clear() - - expect(d.length).eql(0) - expect(d.toArray()).eql([]) - expect([...d.iter()]).eql([]) - }) -}) diff --git a/packages/core/src/utils/deque.ts b/packages/core/src/utils/deque.ts deleted file mode 100644 index c8016936..00000000 --- a/packages/core/src/utils/deque.ts +++ /dev/null @@ -1,252 +0,0 @@ -// ^^ because of performance reasons -const MIN_INITIAL_CAPACITY = 8 - -// System.arraycopy from java -function arraycopy(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 { - // 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(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(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 { - 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(this._capacity) - this._head = this._tail = 0 - } -} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index bf7d783b..0a71698e 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -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'