Browse Source

Sliced history

master
Eduard Kuzmenko 4 years ago
parent
commit
adc411824e
  1. 12
      src/components/chat/bubbles.ts
  2. 181
      src/helpers/slicedArray.ts
  3. 32
      src/lib/appManagers/appMessagesManager.ts

12
src/components/chat/bubbles.ts

@ -45,6 +45,7 @@ import { fastRaf } from "../../helpers/schedulers";
import { deferredPromise } from "../../helpers/cancellablePromise"; import { deferredPromise } from "../../helpers/cancellablePromise";
import RepliesElement from "./replies"; import RepliesElement from "./replies";
import DEBUG from "../../config/debug"; import DEBUG from "../../config/debug";
import { SliceEnd } from "../../helpers/slicedArray";
const USE_MEDIA_TAILS = false; const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS = ['messageActionHistoryClear']; const IGNORE_ACTIONS = ['messageActionHistoryClear'];
@ -2696,7 +2697,7 @@ export default class ChatBubbles {
additionMsgIds = [additionMsgId]; additionMsgIds = [additionMsgId];
} else { } else {
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); 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(); additionMsgIds = historyStorage.history.slice.slice();
// * filter last album, because we don't know is it the last item // * filter last album, because we don't know is it the last item
@ -2718,7 +2719,7 @@ export default class ChatBubbles {
let resultPromise: Promise<any>; let resultPromise: Promise<any>;
//const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id; //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; const isFirstMessageRender = (this.isFirstLoad && backLimit && result instanceof Promise) || isAdditionRender;
if(isAdditionRender) { if(isAdditionRender) {
resultPromise = result as Promise<any>; resultPromise = result as Promise<any>;
@ -2729,18 +2730,17 @@ export default class ChatBubbles {
this.isFirstLoad = false; this.isFirstLoad = false;
const processResult = (historyResult: typeof result) => { const processResult = (historyResult: typeof result) => {
/* if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) { if(this.chat.type === 'discussion' && 'offsetIdOffset' in historyResult) {
const isTopEnd = historyResult.offsetIdOffset >= (historyResult.count - loadCount);
//this.log('discussion got history', loadCount, backLimit, historyResult, isTopEnd); //this.log('discussion got history', loadCount, backLimit, historyResult, isTopEnd);
// * inject discussion start // * inject discussion start
if(isTopEnd) { if(historyResult.history.isEnd(SliceEnd.Top)) {
const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId]; const serviceStartMessageId = this.appMessagesManager.threadsServiceMessagesIdsStorage[this.peerId + '_' + this.chat.threadId];
if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId); if(serviceStartMessageId) historyResult.history.push(serviceStartMessageId);
historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse()); historyResult.history.push(...this.chat.getMidsByMid(this.chat.threadId).reverse());
this.scrollable.loadedAll.top = true; this.scrollable.loadedAll.top = true;
} }
} */ }
}; };
const sup = (result: HistoryResult) => { const sup = (result: HistoryResult) => {

181
src/helpers/slicedArray.ts

@ -4,25 +4,132 @@
type ItemType = number; type ItemType = number;
export class Slice extends Array<ItemType> { export enum SliceEnd {
constructor(protected slicedArray: SlicedArray, items: ItemType[] = []) { None = 0,
super(...items); Top = 1,
Bottom = 2,
Both = 4
};
export interface Slice extends Array<ItemType> {
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 // TODO: Clear empty arrays after deleting items
export default class SlicedArray { export default class SlicedArray {
private slices: Slice[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */; private slices: Slice[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */;
private sliceConstructor: SliceConstructor;
constructor() { constructor() {
this.slices = [new Slice(this)]; const self = this;
this.sliceConstructor = class Slice extends Array<ItemType> 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[]) { public insertSlice(slice: ItemType[]) {
if(!this.slices[0].length) { if(!slice.length) {
this.slices[0].push(...slice); return;
}
const first = this.slices[0];
if(!first.length) {
first.push(...slice);
return; 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(); this.flatten();
@ -76,6 +183,7 @@ export default class SlicedArray {
const upperIndex = prevSlice.indexOf(nextSlice[0]); const upperIndex = prevSlice.indexOf(nextSlice[0]);
if(upperIndex !== -1) { if(upperIndex !== -1) {
prevSlice.setEnd(nextSlice.end);
this.slices.splice(i + 1, 1); this.slices.splice(i + 1, 1);
length--; length--;
@ -86,10 +194,18 @@ export default class SlicedArray {
// * // *
get slice() { get first() {
return this.slices[0]; return this.slices[0];
} }
get last() {
return this.slices[this.slices.length - 1];
}
get slice() {
return this.first;
}
get length() { get length() {
return this.slice.length; return this.slice.length;
} }
@ -107,9 +223,10 @@ export default class SlicedArray {
} }
public findSliceOffset(maxId: number) { public findSliceOffset(maxId: number) {
let slice: Slice;
for(let i = 0; i < this.slices.length; ++i) { for(let i = 0; i < this.slices.length; ++i) {
let offset = 0; let offset = 0;
const slice = this.slices[i]; slice = this.slices[i];
if(slice.length < 2) { if(slice.length < 2) {
continue; continue;
} }
@ -128,6 +245,13 @@ export default class SlicedArray {
} }
} }
if(slice && slice.isEnd(SliceEnd.Top)) {
return {
slice,
offset: slice.length
};
}
return undefined; return undefined;
} }
@ -135,6 +259,7 @@ export default class SlicedArray {
public sliceMe(offsetId: number, add_offset: number, limit: number) { public sliceMe(offsetId: number, add_offset: number, limit: number) {
let slice = this.slice; let slice = this.slice;
let offset = 0; let offset = 0;
let sliceOffset = 0;
if(offsetId) { if(offsetId) {
const pos = this.findSliceOffset(offsetId); const pos = this.findSliceOffset(offsetId);
@ -143,30 +268,46 @@ export default class SlicedArray {
} }
slice = pos.slice; slice = pos.slice;
offset = pos.offset; offset = sliceOffset = pos.offset;
if(slice.includes(offsetId) && add_offset < 0) { if(slice.includes(offsetId)) {
add_offset += 1; 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; //const fixHalfBackLimit = add_offset && !(limit / add_offset % 2) && (sliceEnd % 2) ? 1 : 0;
//sliceEnd += fixHalfBackLimit; //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 { return {
slice: slice.slice(Math.max(offset + add_offset, 0), sliceEnd), slice: sliced,
offsetIdOffset: offset offsetIdOffset: offset,
fulfilled: SliceEnd.None | (topFulfilled && bottomFulfilled ? SliceEnd.Both : ((topFulfilled ? SliceEnd.Top : SliceEnd.None) | (bottomFulfilled ? SliceEnd.Bottom : SliceEnd.None)))
}; };
} }
public unshift(item: ItemType) { public unshift(...items: ItemType[]) {
this.slice.unshift(item); this.first.unshift(...items);
} }
/* public push(item: ItemType) { public push(...items: ItemType[]) {
this.slice.push(item); this.last.push(...items);
} */ }
public delete(item: ItemType) { public delete(item: ItemType) {
const found = this.findSlice(item); const found = this.findSlice(item);

32
src/lib/appManagers/appMessagesManager.ts

@ -36,7 +36,7 @@ import pushHeavyTask from "../../helpers/heavyQueue";
import { getFileNameByLocation } from "../../helpers/fileName"; import { getFileNameByLocation } from "../../helpers/fileName";
import appProfileManager from "./appProfileManager"; import appProfileManager from "./appProfileManager";
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug"; import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
import SlicedArray from "../../helpers/slicedArray"; import SlicedArray, { Slice, SliceEnd } from "../../helpers/slicedArray";
//console.trace('include'); //console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
@ -59,8 +59,8 @@ export type HistoryStorage = {
export type HistoryResult = { export type HistoryResult = {
count: number, count: number,
history: number[], history: Slice,
offsetIdOffset?: number offsetIdOffset?: number,
}; };
export type Dialog = MTDialog.dialog; export type Dialog = MTDialog.dialog;
@ -2124,7 +2124,7 @@ export class AppMessagesManager {
*/ */
public getServerMessageId(messageId: number) { public getServerMessageId(messageId: number) {
const q = AppMessagesManager.MESSAGE_ID_OFFSET; const q = AppMessagesManager.MESSAGE_ID_OFFSET;
if(messageId <= q) { if(messageId < q) { // id 0 -> mid 0xFFFFFFFF, so 0xFFFFFFFF must convert to 0
return messageId; return messageId;
} }
@ -3307,12 +3307,13 @@ export class AppMessagesManager {
const threadKey = message.peerId + '_' + message.mid; const threadKey = message.peerId + '_' + message.mid;
if(this.threadsServiceMessagesIdsStorage[threadKey]) return; if(this.threadsServiceMessagesIdsStorage[threadKey]) return;
const maxMessageId = this.getServerMessageId(Math.max(...this.getMidsByMessage(message)));
const serviceStartMessage: Message.messageService = { const serviceStartMessage: Message.messageService = {
_: 'messageService', _: 'messageService',
pFlags: { pFlags: {
is_single: true is_single: true
} as any, } as any,
id: this.generateMessageId(message.id, true), id: this.generateMessageId(maxMessageId, true),
date: message.date, date: message.date,
from_id: {_: 'peerUser', user_id: 0}/* message.from_id */, from_id: {_: 'peerUser', user_id: 0}/* message.from_id */,
peer_id: message.peer_id, peer_id: message.peer_id,
@ -4629,7 +4630,7 @@ export class AppMessagesManager {
} }
const haveSlice = historyStorage.history.sliceMe(maxId, offset, limit); 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 { return {
count: historyStorage.count, count: historyStorage.count,
history: haveSlice.slice, history: haveSlice.slice,
@ -4641,16 +4642,19 @@ export class AppMessagesManager {
const slice = historyStorage.history.sliceMe(maxId, offset, limit); const slice = historyStorage.history.sliceMe(maxId, offset, limit);
return { return {
count: historyStorage.count, count: historyStorage.count,
history: slice?.slice || [], history: slice?.slice || historyStorage.history.constructSlice(),
offsetIdOffset: slice?.offsetIdOffset || historyStorage.count offsetIdOffset: slice?.offsetIdOffset || historyStorage.count
}; };
}); });
} }
public fillHistoryStorage(peerId: number, maxId: number, fullLimit: number, offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> { public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise<void> {
return this.requestHistory(peerId, maxId, fullLimit, offset, undefined, threadId).then((historyResult) => { return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => {
historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; 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) { /* if(!maxId && historyResult.messages.length) {
maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1); maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1);
} }
@ -4666,19 +4670,23 @@ export class AppMessagesManager {
const mids = historyResult.messages.map((message) => (message as MyMessage).mid); const mids = historyResult.messages.map((message) => (message as MyMessage).mid);
// * add bound manually. // * add bound manually.
// * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') // * 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; let i = 0;
for(const length = mids.length; i < length; ++i) { for(const length = mids.length; i < length; ++i) {
if(maxId > mids[i]) { if(offset_id > mids[i]) {
break; break;
} }
} }
mids.splice(i, 0, maxId); mids.splice(i, 0, offset_id);
} }
historyStorage.history.insertSlice(mids); historyStorage.history.insertSlice(mids);
if(isTopEnd) {
historyStorage.history.last.setEnd(SliceEnd.Top);
}
/* const isBackLimit = offset < 0 && -offset !== fullLimit; /* const isBackLimit = offset < 0 && -offset !== fullLimit;
if(isBackLimit) { if(isBackLimit) {
return; return;

Loading…
Cancel
Save