Browse Source

Fix freeze on fast chat switch

master
Eduard Kuzmenko 4 years ago
parent
commit
080fb56d82
  1. 9
      src/components/chat/bubbles.ts
  2. 32
      src/helpers/blur.ts
  3. 4
      src/helpers/heavyQueue.ts
  4. 42
      src/hooks/useHeavyAnimationCheck.ts

9
src/components/chat/bubbles.ts

@ -45,7 +45,7 @@ import AudioElement from "../audio";
import { Message, MessageEntity, MessageReplyHeader } from "../../layer"; import { Message, MessageEntity, MessageReplyHeader } from "../../layer";
import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
import { FocusDirection } from "../../helpers/fastSmoothScroll"; import { FocusDirection } from "../../helpers/fastSmoothScroll";
import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent } from "../../hooks/useHeavyAnimationCheck"; import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck";
import { fastRaf } from "../../helpers/schedulers"; import { fastRaf } from "../../helpers/schedulers";
import { deferredPromise } from "../../helpers/cancellablePromise"; import { deferredPromise } from "../../helpers/cancellablePromise";
import RepliesElement from "./replies"; import RepliesElement from "./replies";
@ -67,6 +67,7 @@ import reflowScrollableElement from "../../helpers/dom/reflowScrollableElement";
import replaceContent from "../../helpers/dom/replaceContent"; import replaceContent from "../../helpers/dom/replaceContent";
import setInnerHTML from "../../helpers/dom/setInnerHTML"; import setInnerHTML from "../../helpers/dom/setInnerHTML";
import whichChild from "../../helpers/dom/whichChild"; import whichChild from "../../helpers/dom/whichChild";
import { cancelAnimationByKey } from "../../helpers/animation";
const USE_MEDIA_TAILS = false; const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS: Message.messageService['action']['_'][] = [/* 'messageActionHistoryClear' */]; const IGNORE_ACTIONS: Message.messageService['action']['_'][] = [/* 'messageActionHistoryClear' */];
@ -1403,6 +1404,12 @@ export default class ChatBubbles {
this.scrollable.loadedAll.top = false; this.scrollable.loadedAll.top = false;
this.scrollable.loadedAll.bottom = false; this.scrollable.loadedAll.bottom = false;
// cancel scroll
cancelAnimationByKey(this.scrollable.container);
// do not wait ending of previous scale animation
interruptHeavyAnimation();
if(TEST_SCROLL !== undefined) { if(TEST_SCROLL !== undefined) {
TEST_SCROLL = TEST_SCROLL_TIMES; TEST_SCROLL = TEST_SCROLL_TIMES;
} }

32
src/helpers/blur.ts

@ -6,7 +6,7 @@
import _DEBUG from '../config/debug'; import _DEBUG from '../config/debug';
import fastBlur from '../vendor/fastBlur'; import fastBlur from '../vendor/fastBlur';
import pushHeavyTask from './heavyQueue'; import addHeavyTask from './heavyQueue';
const RADIUS = 2; const RADIUS = 2;
const ITERATIONS = 2; const ITERATIONS = 2;
@ -29,24 +29,30 @@ function processBlur(dataUri: string, radius: number, iterations: number) {
const ctx = canvas.getContext('2d')!; const ctx = canvas.getContext('2d')!;
//ctx.filter = 'blur(2px)';
ctx.drawImage(img, 0, 0); ctx.drawImage(img, 0, 0);
fastBlur(ctx, 0, 0, canvas.width, canvas.height, radius, iterations); fastBlur(ctx, 0, 0, canvas.width, canvas.height, radius, iterations);
//resolve(canvas.toDataURL()); resolve(canvas.toDataURL());
canvas.toBlob(blob => { if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
/* canvas.toBlob(blob => {
resolve(URL.createObjectURL(blob)); resolve(URL.createObjectURL(blob));
if(DEBUG) { if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`); console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
} }
}); }); */
}; };
img.src = dataUri; img.src = dataUri;
}); });
} }
const blurPromises: {[dataUri: string]: Promise<string>} = {}; const blurPromises: Map<string, Promise<string>> = new Map();
const CACHE_SIZE = 1000;
export default function blur(dataUri: string, radius: number = RADIUS, iterations: number = ITERATIONS) { export default function blur(dataUri: string, radius: number = RADIUS, iterations: number = ITERATIONS) {
if(!dataUri) { if(!dataUri) {
@ -54,15 +60,23 @@ export default function blur(dataUri: string, radius: number = RADIUS, iteration
return Promise.resolve(dataUri); return Promise.resolve(dataUri);
} }
if(blurPromises[dataUri]) return blurPromises[dataUri]; if(blurPromises.size > CACHE_SIZE) {
return blurPromises[dataUri] = new Promise<string>((resolve) => { blurPromises.clear();
}
if(blurPromises.has(dataUri)) return blurPromises.get(dataUri);
const promise = new Promise<string>((resolve) => {
//return resolve(dataUri); //return resolve(dataUri);
pushHeavyTask({ addHeavyTask({
items: [[dataUri, radius, iterations]], items: [[dataUri, radius, iterations]],
context: null, context: null,
process: processBlur process: processBlur
}).then(results => { }, 'unshift').then(results => {
resolve(results[0]); resolve(results[0]);
}); });
}); });
blurPromises.set(dataUri, promise);
return promise;
} }

4
src/helpers/heavyQueue.ts

@ -17,13 +17,13 @@ type HeavyQueue<T> = {
const heavyQueue: HeavyQueue<any>[] = []; const heavyQueue: HeavyQueue<any>[] = [];
let processingQueue = false; let processingQueue = false;
export default function pushHeavyTask<T>(queue: HeavyQueue<T>) { export default function addHeavyTask<T>(queue: HeavyQueue<T>, method: 'push' | 'unshift' = 'push') {
if(!queue.items.length) { if(!queue.items.length) {
return Promise.resolve([]); return Promise.resolve([]);
} }
queue.promise = deferredPromise<T[]>(); queue.promise = deferredPromise<T[]>();
heavyQueue.push(queue); heavyQueue[method](queue);
processHeavyQueue(); processHeavyQueue();
return queue.promise; return queue.promise;

42
src/hooks/useHeavyAnimationCheck.ts

@ -6,7 +6,6 @@
// * Jolly Cobra's useHeavyAnimationCheck.ts, patched // * Jolly Cobra's useHeavyAnimationCheck.ts, patched
//import { useEffect } from '../lib/teact/teact';
import { AnyToVoidFunction } from '../types'; import { AnyToVoidFunction } from '../types';
import ListenerSetter from '../helpers/listenerSetter'; import ListenerSetter from '../helpers/listenerSetter';
import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise'; import { CancellablePromise, deferredPromise } from '../helpers/cancellablePromise';
@ -18,12 +17,14 @@ const ANIMATION_START_EVENT = 'event-heavy-animation-start';
const ANIMATION_END_EVENT = 'event-heavy-animation-end'; const ANIMATION_END_EVENT = 'event-heavy-animation-end';
let isAnimating = false; let isAnimating = false;
let heavyAnimationPromise: CancellablePromise<void> = Promise.resolve(); let heavyAnimationPromise: CancellablePromise<void> = deferredPromise<void>();
let promisesInQueue = 0; let promisesInQueue = 0;
heavyAnimationPromise.resolve();
const log = console.log.bind(console.log, '[HEAVY-ANIMATION]:'); const log = console.log.bind(console.log, '[HEAVY-ANIMATION]:');
export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: number) => { export function dispatchHeavyAnimationEvent(promise: Promise<any>, timeout?: number) {
if(!isAnimating) { if(!isAnimating) {
heavyAnimationPromise = deferredPromise<void>(); heavyAnimationPromise = deferredPromise<void>();
rootScope.broadcast(ANIMATION_START_EVENT); rootScope.broadcast(ANIMATION_START_EVENT);
@ -40,10 +41,27 @@ export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: num
].filter(Boolean); ].filter(Boolean);
const perf = performance.now(); const perf = performance.now();
const _heavyAnimationPromise = heavyAnimationPromise;
Promise.race(promises).then(() => { Promise.race(promises).then(() => {
if(heavyAnimationPromise !== _heavyAnimationPromise || heavyAnimationPromise.isFulfilled) { // interrupted
return;
}
--promisesInQueue; --promisesInQueue;
DEBUG && log('promise end, length:', promisesInQueue, performance.now() - perf); DEBUG && log('promise end, length:', promisesInQueue, performance.now() - perf);
if(!promisesInQueue) { if(promisesInQueue <= 0) {
onHeavyAnimationEnd();
}
});
return heavyAnimationPromise;
}
function onHeavyAnimationEnd() {
if(heavyAnimationPromise.isFulfilled) {
return;
}
isAnimating = false; isAnimating = false;
promisesInQueue = 0; promisesInQueue = 0;
rootScope.broadcast(ANIMATION_END_EVENT); rootScope.broadcast(ANIMATION_END_EVENT);
@ -51,18 +69,20 @@ export const dispatchHeavyAnimationEvent = (promise: Promise<any>, timeout?: num
DEBUG && log('end'); DEBUG && log('end');
} }
});
return heavyAnimationPromise; export function interruptHeavyAnimation() {
}; onHeavyAnimationEnd();
}
export const getHeavyAnimationPromise = () => heavyAnimationPromise; export function getHeavyAnimationPromise() {
return heavyAnimationPromise;
}
export default ( export default function(
handleAnimationStart: AnyToVoidFunction, handleAnimationStart: AnyToVoidFunction,
handleAnimationEnd: AnyToVoidFunction, handleAnimationEnd: AnyToVoidFunction,
listenerSetter?: ListenerSetter listenerSetter?: ListenerSetter
) => { ) {
//useEffect(() => { //useEffect(() => {
if(isAnimating) { if(isAnimating) {
handleAnimationStart(); handleAnimationStart();
@ -78,4 +98,4 @@ export default (
remove(ANIMATION_START_EVENT, handleAnimationStart); remove(ANIMATION_START_EVENT, handleAnimationStart);
}; };
//}, [handleAnimationEnd, handleAnimationStart]); //}, [handleAnimationEnd, handleAnimationStart]);
}; }

Loading…
Cancel
Save