diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index fbd13d2c..c2f3ec4f 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -45,6 +45,7 @@ import { fastRaf } from "../../helpers/schedulers"; import { deferredPromise } from "../../helpers/cancellablePromise"; import RepliesElement from "./replies"; import DEBUG from "../../config/debug"; +import { SliceEnd } from "../../helpers/slicedArray"; const USE_MEDIA_TAILS = false; const IGNORE_ACTIONS = ['messageActionHistoryClear']; @@ -2696,7 +2697,7 @@ export default class ChatBubbles { additionMsgIds = [additionMsgId]; } else { const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); - if(historyStorage.history.length < loadCount) { + if(historyStorage.history.length < loadCount && !historyStorage.history.slice.isEnd(SliceEnd.Both)) { additionMsgIds = historyStorage.history.slice.slice(); // * filter last album, because we don't know is it the last item @@ -2718,7 +2719,7 @@ export default class ChatBubbles { let resultPromise: Promise; //const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id; - const isAdditionRender = additionMsgIds?.length; + const isAdditionRender = additionMsgIds?.length && result instanceof Promise; const isFirstMessageRender = (this.isFirstLoad && backLimit && result instanceof Promise) || isAdditionRender; if(isAdditionRender) { resultPromise = result as Promise; @@ -2729,18 +2730,17 @@ export default class ChatBubbles { this.isFirstLoad = false; const processResult = (historyResult: typeof result) => { - /* if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) { - const isTopEnd = historyResult.offsetIdOffset >= (historyResult.count - loadCount); + if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) { //this.log('discussion got history', loadCount, backLimit, historyResult, isTopEnd); // * inject discussion start - if(isTopEnd) { + if(historyResult.history.isEnd(SliceEnd.Top)) { const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId]; if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId); historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse()); this.scrollable.loadedAll.top = true; } - } */ + } }; const sup = (result: HistoryResult) => { diff --git a/src/helpers/slicedArray.ts b/src/helpers/slicedArray.ts index 9273e91f..99e6083d 100644 --- a/src/helpers/slicedArray.ts +++ b/src/helpers/slicedArray.ts @@ -4,25 +4,132 @@ type ItemType = number; -export class Slice extends Array { - constructor(protected slicedArray: SlicedArray, items: ItemType[] = []) { - super(...items); +export enum SliceEnd { + None = 0, + Top = 1, + Bottom = 2, + Both = 4 +}; + +export interface Slice extends Array { + slicedArray: SlicedArray; + end: SliceEnd; + + isEnd: (side: SliceEnd) => boolean; + setEnd: (side: SliceEnd) => void; +} - - } +export interface SliceConstructor { + new(...items: ItemType[]): Slice; } // TODO: Clear empty arrays after deleting items export default class SlicedArray { private slices: Slice[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */; - + private sliceConstructor: SliceConstructor; + constructor() { - this.slices = [new Slice(this)]; + const self = this; + this.sliceConstructor = class Slice extends Array implements Slice { + slicedArray: SlicedArray; + end: SliceEnd = SliceEnd.None; + + constructor(...items: ItemType[]) { + super(...items); + this.slicedArray = self; + } + + isEnd(side: SliceEnd) { + if(this.end & side) { + return true; + } + + if(side === SliceEnd.Top) { + const slice = self.last; + return slice.end & side ? this.includes(slice[slice.length - 1]) : false; + } else if(side === SliceEnd.Bottom) { + const slice = self.first; + return slice.end & side ? this.includes(slice[0]) : false; + }/* else if(side === SliceEnd.Both) { + + } */ + + return false; + } + + setEnd(side: SliceEnd) { + this.end |= side; + + if(side !== SliceEnd.Both && this.end & SliceEnd.Top && this.end & SliceEnd.Bottom) { + this.end |= SliceEnd.Both; + } + } + } + + const first = this.constructSlice(); + first.setEnd(SliceEnd.Bottom); + this.slices = [first]; + } + + public constructSlice(...items: ItemType[]) { + //const slice = new Slice(this, ...items); + const slice = new this.sliceConstructor(...items); + return slice; + + // ! code below will slow execution in 15 times + /* const self = this; + const p: Slice = new Proxy(slice, { + get: function(target, name: any) { + if(name === 'constructor') { + const p = new Proxy(Slice, { + construct: (target, args) => { + return self.constructSlice(...args); + } + }); + + return p; + } + + return target[name]; + } + }); + + return p; */ + + /* + var p = slicedArray.constructSlice(); + p.length = 100000; + p.fill(255); + + var a = new Array(100000); + a.fill(255); + + var b = 0; + var perf = performance.now(); + for(var i = 0; i < p.length; ++i) { + b += p[i]; + } + + console.log('perf 1', performance.now() - perf); + + b = 0; + perf = performance.now(); + for(var i = 0; i < a.length; ++i) { + b += a[i]; + } + + console.log('perf 2', performance.now() - perf); + */ } public insertSlice(slice: ItemType[]) { - if(!this.slices[0].length) { - this.slices[0].push(...slice); + if(!slice.length) { + return; + } + + const first = this.slices[0]; + if(!first.length) { + first.push(...slice); return; } @@ -59,7 +166,7 @@ export default class SlicedArray { } } - this.slices.splice(insertIndex, 0, new Slice(this, slice)); + this.slices.splice(insertIndex, 0, this.constructSlice(...slice)); } this.flatten(); @@ -76,6 +183,7 @@ export default class SlicedArray { const upperIndex = prevSlice.indexOf(nextSlice[0]); if(upperIndex !== -1) { + prevSlice.setEnd(nextSlice.end); this.slices.splice(i + 1, 1); length--; @@ -85,9 +193,17 @@ export default class SlicedArray { } // * + + get first() { + return this.slices[0]; + } + + get last() { + return this.slices[this.slices.length - 1]; + } get slice() { - return this.slices[0]; + return this.first; } get length() { @@ -107,9 +223,10 @@ export default class SlicedArray { } public findSliceOffset(maxId: number) { + let slice: Slice; for(let i = 0; i < this.slices.length; ++i) { let offset = 0; - const slice = this.slices[i]; + slice = this.slices[i]; if(slice.length < 2) { continue; } @@ -128,6 +245,13 @@ export default class SlicedArray { } } + if(slice && slice.isEnd(SliceEnd.Top)) { + return { + slice, + offset: slice.length + }; + } + return undefined; } @@ -135,6 +259,7 @@ export default class SlicedArray { public sliceMe(offsetId: number, add_offset: number, limit: number) { let slice = this.slice; let offset = 0; + let sliceOffset = 0; if(offsetId) { const pos = this.findSliceOffset(offsetId); @@ -143,30 +268,46 @@ export default class SlicedArray { } slice = pos.slice; - offset = pos.offset; + offset = sliceOffset = pos.offset; - if(slice.includes(offsetId) && add_offset < 0) { - add_offset += 1; + if(slice.includes(offsetId)) { + sliceOffset += 1; } + + /* if(slice.includes(offsetId) && add_offset < 0) { + add_offset += 1; + } */ } - let sliceEnd = offset + add_offset + limit; + let sliceStart = Math.max(sliceOffset + add_offset, 0); + let sliceEnd = sliceOffset + add_offset + limit; //const fixHalfBackLimit = add_offset && !(limit / add_offset % 2) && (sliceEnd % 2) ? 1 : 0; //sliceEnd += fixHalfBackLimit; + const sliced = slice.slice(sliceStart, sliceEnd) as Slice; + + const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit; + const bottomWasMeantToLoad = Math.abs(add_offset); + + const topFulfilled = (slice.length - sliceOffset) >= topWasMeantToLoad || slice.isEnd(SliceEnd.Top); + const bottomFulfilled = (sliceOffset - bottomWasMeantToLoad) >= 0 || slice.isEnd(SliceEnd.Bottom); + + //console.log('sliceMe', topFulfilled, bottomFulfilled); + return { - slice: slice.slice(Math.max(offset + add_offset, 0), sliceEnd), - offsetIdOffset: offset + slice: sliced, + offsetIdOffset: offset, + fulfilled: SliceEnd.None | (topFulfilled && bottomFulfilled ? SliceEnd.Both : ((topFulfilled ? SliceEnd.Top : SliceEnd.None) | (bottomFulfilled ? SliceEnd.Bottom : SliceEnd.None))) }; } - public unshift(item: ItemType) { - this.slice.unshift(item); + public unshift(...items: ItemType[]) { + this.first.unshift(...items); } - /* public push(item: ItemType) { - this.slice.push(item); - } */ + public push(...items: ItemType[]) { + this.last.push(...items); + } public delete(item: ItemType) { const found = this.findSlice(item); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 835391e2..351678d2 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -36,7 +36,7 @@ import pushHeavyTask from "../../helpers/heavyQueue"; import { getFileNameByLocation } from "../../helpers/fileName"; import appProfileManager from "./appProfileManager"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; -import SlicedArray from "../../helpers/slicedArray"; +import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray"; //console.trace('include'); // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет @@ -59,8 +59,8 @@ export type HistoryStorage = { export type HistoryResult = { count: number, - history: number[], - offsetIdOffset?: number + history: Slice, + offsetIdOffset?: number, }; export type Dialog = MTDialog.dialog; @@ -2124,7 +2124,7 @@ export class AppMessagesManager { */ public getServerMessageId(messageId: number) { const q = AppMessagesManager.MESSAGE_ID_OFFSET; - if(messageId <= q) { + if(messageId < q) { // id 0 -> mid 0xFFFFFFFF, so 0xFFFFFFFF must convert to 0 return messageId; } @@ -3307,12 +3307,13 @@ export class AppMessagesManager { const threadKey = message.peerId + '_' + message.mid; if(this.threadsServiceMessagesIdsStorage[threadKey]) return; + const maxMessageId = this.getServerMessageId(Math.max(...this.getMidsByMessage(message))); const serviceStartMessage: Message.messageService = { _: 'messageService', pFlags: { is_single: true } as any, - id: this.generateMessageId(message.id, true), + id: this.generateMessageId(maxMessageId, true), date: message.date, from_id: {_: 'peerUser', user_id: 0}/* message.from_id */, peer_id: message.peer_id, @@ -4629,7 +4630,7 @@ export class AppMessagesManager { } const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit); - if(haveSlice && (haveSlice.slice.length === limit || haveSlice.slice.includes(historyStorage.maxId))) { + if(haveSlice && (haveSlice.slice.length === limit || (haveSlice.fulfilled & SliceEnd.Both))) { return { count: historyStorage.count, history: haveSlice.slice, @@ -4641,16 +4642,19 @@ export class AppMessagesManager { const slice = historyStorage.history.sliceMe(maxId, offset, limit); return { count: historyStorage.count, - history: slice?.slice || [], + history: slice?.slice || historyStorage.history.constructSlice(), offsetIdOffset: slice?.offsetIdOffset || historyStorage.count }; }); } - public fillHistoryStorage(peerId: number, maxId: number, fullLimit: number, offset: number, historyStorage: HistoryStorage, threadId?: number): Promise { - return this.requestHistory(peerId, maxId, fullLimit, offset, undefined, threadId).then((historyResult) => { + public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise { + return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => { historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; + const offsetIdOffset = (historyResult as MessagesMessages.messagesMessagesSlice).offset_id_offset || 0; + const isTopEnd = offsetIdOffset >= (historyStorage.count - limit) || historyStorage.count < (limit + add_offset); + /* if(!maxId && historyResult.messages.length) { maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1); } @@ -4666,18 +4670,22 @@ export class AppMessagesManager { const mids = historyResult.messages.map((message) => (message as MyMessage).mid); // * add bound manually. // * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') - if(maxId && !mids.includes(maxId)) { + if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) { let i = 0; for(const length = mids.length; i < length; ++i) { - if(maxId > mids[i]) { + if(offset_id > mids[i]) { break; } } - mids.splice(i, 0, maxId); + mids.splice(i, 0, offset_id); } historyStorage.history.insertSlice(mids); + + if(isTopEnd) { + historyStorage.history.last.setEnd(SliceEnd.Top); + } /* const isBackLimit = offset < 0 && -offset !== fullLimit; if(isBackLimit) {