diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 251ffc59..f9be0d5c 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -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"; 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 { 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; } diff --git a/src/helpers/blur.ts b/src/helpers/blur.ts index cf5b894e..69e809e0 100644 --- a/src/helpers/blur.ts +++ b/src/helpers/blur.ts @@ -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; function processBlur(dataUri: string, radius: number, iterations: number) { return new Promise((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} = {}; +const blurPromises: Map> = 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((resolve) => { + if(blurPromises.has(dataUri)) return blurPromises.get(dataUri); + const promise = new Promise((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; } diff --git a/src/helpers/heavyQueue.ts b/src/helpers/heavyQueue.ts index e2058986..1afdf6e2 100644 --- a/src/helpers/heavyQueue.ts +++ b/src/helpers/heavyQueue.ts @@ -17,13 +17,13 @@ type HeavyQueue = { const heavyQueue: HeavyQueue[] = []; let processingQueue = false; -export default function pushHeavyTask(queue: HeavyQueue) { +export default function addHeavyTask(queue: HeavyQueue, method: 'push' | 'unshift' = 'push') { if(!queue.items.length) { return Promise.resolve([]); } queue.promise = deferredPromise(); - heavyQueue.push(queue); + heavyQueue[method](queue); processHeavyQueue(); return queue.promise; diff --git a/src/hooks/useHeavyAnimationCheck.ts b/src/hooks/useHeavyAnimationCheck.ts index bad4a331..bc25bf4f 100644 --- a/src/hooks/useHeavyAnimationCheck.ts +++ b/src/hooks/useHeavyAnimationCheck.ts @@ -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'; const ANIMATION_END_EVENT = 'event-heavy-animation-end'; let isAnimating = false; -let heavyAnimationPromise: CancellablePromise = Promise.resolve(); +let heavyAnimationPromise: CancellablePromise = deferredPromise(); let promisesInQueue = 0; +heavyAnimationPromise.resolve(); + const log = console.log.bind(console.log, '[HEAVY-ANIMATION]:'); -export const dispatchHeavyAnimationEvent = (promise: Promise, timeout?: number) => { +export function dispatchHeavyAnimationEvent(promise: Promise, timeout?: number) { if(!isAnimating) { heavyAnimationPromise = deferredPromise(); rootScope.broadcast(ANIMATION_START_EVENT); @@ -40,29 +41,48 @@ export const dispatchHeavyAnimationEvent = (promise: Promise, 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 ( remove(ANIMATION_START_EVENT, handleAnimationStart); }; //}, [handleAnimationEnd, handleAnimationStart]); -}; +}