Browse Source

Fix freeze on fast chat switch

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

9
src/components/chat/bubbles.ts

@ -45,7 +45,7 @@ import AudioElement from "../audio"; @@ -45,7 +45,7 @@ import AudioElement from "../audio";
import { Message, MessageEntity, MessageReplyHeader } from "../../layer";
import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config";
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 { deferredPromise } from "../../helpers/cancellablePromise";
import RepliesElement from "./replies";
@ -67,6 +67,7 @@ import reflowScrollableElement from "../../helpers/dom/reflowScrollableElement"; @@ -67,6 +67,7 @@ import reflowScrollableElement from "../../helpers/dom/reflowScrollableElement";
import replaceContent from "../../helpers/dom/replaceContent";
import setInnerHTML from "../../helpers/dom/setInnerHTML";
import whichChild from "../../helpers/dom/whichChild";
import { cancelAnimationByKey } from "../../helpers/animation";
const USE_MEDIA_TAILS = false;
const IGNORE_ACTIONS: Message.messageService['action']['_'][] = [/* 'messageActionHistoryClear' */];
@ -1403,6 +1404,12 @@ export default class ChatBubbles { @@ -1403,6 +1404,12 @@ export default class ChatBubbles {
this.scrollable.loadedAll.top = 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) {
TEST_SCROLL = TEST_SCROLL_TIMES;
}

46
src/helpers/blur.ts

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
import _DEBUG from '../config/debug';
import fastBlur from '../vendor/fastBlur';
import pushHeavyTask from './heavyQueue';
import addHeavyTask from './heavyQueue';
const RADIUS = 2;
const ITERATIONS = 2;
@ -16,53 +16,67 @@ const DEBUG = _DEBUG && true; @@ -16,53 +16,67 @@ const DEBUG = _DEBUG && true;
function processBlur(dataUri: string, radius: number, iterations: number) {
return new Promise<string>((resolve) => {
const img = new Image();
const perf = performance.now();
if(DEBUG) {
console.log('[blur] start');
}
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d')!;
//ctx.filter = 'blur(2px)';
ctx.drawImage(img, 0, 0);
fastBlur(ctx, 0, 0, canvas.width, canvas.height, radius, iterations);
//resolve(canvas.toDataURL());
canvas.toBlob(blob => {
resolve(canvas.toDataURL());
if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
/* canvas.toBlob(blob => {
resolve(URL.createObjectURL(blob));
if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
});
}); */
};
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) {
if(!dataUri) {
console.error('no dataUri for blur', dataUri);
return Promise.resolve(dataUri);
}
if(blurPromises.size > CACHE_SIZE) {
blurPromises.clear();
}
if(blurPromises[dataUri]) return blurPromises[dataUri];
return blurPromises[dataUri] = new Promise<string>((resolve) => {
if(blurPromises.has(dataUri)) return blurPromises.get(dataUri);
const promise = new Promise<string>((resolve) => {
//return resolve(dataUri);
pushHeavyTask({
addHeavyTask({
items: [[dataUri, radius, iterations]],
context: null,
process: processBlur
}).then(results => {
}, 'unshift').then(results => {
resolve(results[0]);
});
});
blurPromises.set(dataUri, promise);
return promise;
}

4
src/helpers/heavyQueue.ts

@ -17,13 +17,13 @@ type HeavyQueue<T> = { @@ -17,13 +17,13 @@ type HeavyQueue<T> = {
const heavyQueue: HeavyQueue<any>[] = [];
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) {
return Promise.resolve([]);
}
queue.promise = deferredPromise<T[]>();
heavyQueue.push(queue);
heavyQueue[method](queue);
processHeavyQueue();
return queue.promise;

50
src/hooks/useHeavyAnimationCheck.ts

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

Loading…
Cancel
Save