From 752d131e12d3fd3b27fec0a869b5a2845a565d53 Mon Sep 17 00:00:00 2001 From: Alexander Zinchuk Date: Mon, 10 Apr 2023 00:54:14 +0200 Subject: [PATCH] Schedulers: Fix race condition, get rid of `isPrimary` --- src/util/schedulers.ts | 114 +++++++++++++---------------------------- 1 file changed, 37 insertions(+), 77 deletions(-) diff --git a/src/util/schedulers.ts b/src/util/schedulers.ts index ef1ab3f74..fe21031ba 100644 --- a/src/util/schedulers.ts +++ b/src/util/schedulers.ts @@ -65,37 +65,21 @@ export function throttle( } export function fastRafWithFallback(fn: F) { - return fastRaf(fn, false, true); -} - -export function fastRafPrimaryWithFallback(fn: F) { - return fastRaf(fn, true, true); + return fastRaf(fn, true); } export function throttleWithRafFallback(fn: F) { return throttleWith(fastRafWithFallback, fn); } -export function throttleWithPrimaryRafFallback(fn: F) { - return throttleWith(fastRafPrimaryWithFallback, fn); -} - export function throttleWithRaf(fn: F) { return throttleWith(fastRaf, fn); } -export function throttleWithPrimaryRaf(fn: F) { - return throttleWith(fastRafPrimary, fn); -} - export function throttleWithTickEnd(fn: F) { return throttleWith(onTickEnd, fn); } -export function throttleWithPrimaryTickEnd(fn: F) { - return throttleWith(onTickEndPrimary, fn); -} - export function throttleWith(schedulerFn: Scheduler, fn: F) { let waiting = false; let args: Parameters; @@ -134,102 +118,78 @@ export function rafPromise() { }); } -let fastRafCallbacks: NoneToVoidFunction[] | undefined; -let fastRafPrimaryCallbacks: NoneToVoidFunction[] | undefined; -let timeoutCallbacks: NoneToVoidFunction[] | undefined; -let timeoutPrimaryCallbacks: NoneToVoidFunction[] | undefined; -let timeout: NodeJS.Timeout | undefined; - const FAST_RAF_TIMEOUT_FALLBACK_MS = 300; +let fastRafCallbacks: Set | undefined; +let fastRafFallbackCallbacks: Set | undefined; +let fastRafFallbackTimeout: number | undefined; + // May result in an immediate execution if called from another RAF callback which was scheduled // (and therefore is executed) earlier than RAF callback scheduled by `fastRaf` -export function fastRaf(callback: NoneToVoidFunction, isPrimary = false, withTimeoutFallback = false) { +export function fastRaf(callback: NoneToVoidFunction, withTimeoutFallback = false) { if (!fastRafCallbacks) { - fastRafCallbacks = !withTimeoutFallback && !isPrimary ? [callback] : []; - fastRafPrimaryCallbacks = !withTimeoutFallback && isPrimary ? [callback] : []; - timeoutCallbacks = withTimeoutFallback && !isPrimary ? [callback] : []; - timeoutPrimaryCallbacks = withTimeoutFallback && isPrimary ? [callback] : []; + fastRafCallbacks = new Set([callback]); requestAnimationFrame(() => { const currentCallbacks = fastRafCallbacks!; - const currentPrimaryCallbacks = fastRafPrimaryCallbacks!; - const currentTimeoutCallbacks = timeoutCallbacks!; - const currentTimeoutPrimaryCallbacks = timeoutPrimaryCallbacks!; - - if (timeout) clearTimeout(timeout); - timeout = undefined; fastRafCallbacks = undefined; - fastRafPrimaryCallbacks = undefined; - timeoutCallbacks = undefined; - timeoutPrimaryCallbacks = undefined; + fastRafFallbackCallbacks = undefined; + + if (fastRafFallbackTimeout) { + clearTimeout(fastRafFallbackTimeout); + fastRafFallbackTimeout = undefined; + } - currentPrimaryCallbacks.forEach((cb) => cb()); - currentTimeoutPrimaryCallbacks.forEach((cb) => cb()); currentCallbacks.forEach((cb) => cb()); - currentTimeoutCallbacks.forEach((cb) => cb()); }); - } else if (isPrimary) { - if (withTimeoutFallback) { - timeoutPrimaryCallbacks!.push(callback); - } else { - fastRafPrimaryCallbacks!.push(callback); - } - } else if (withTimeoutFallback) { - timeoutCallbacks!.push(callback); } else { - fastRafCallbacks.push(callback); + fastRafCallbacks.add(callback); } - if (!timeout && withTimeoutFallback) { - timeout = setTimeout(() => { - const currentTimeoutCallbacks = timeoutCallbacks!; - const currentTimeoutPrimaryCallbacks = timeoutPrimaryCallbacks!; + if (withTimeoutFallback) { + if (!fastRafFallbackCallbacks) { + fastRafFallbackCallbacks = new Set([callback]); + } else { + fastRafFallbackCallbacks.add(callback); + } - if (timeout) clearTimeout(timeout); - timeout = undefined; + if (!fastRafFallbackTimeout) { + fastRafFallbackTimeout = window.setTimeout(() => { + const currentTimeoutCallbacks = fastRafFallbackCallbacks!; - timeoutCallbacks = []; - timeoutPrimaryCallbacks = []; + if (fastRafCallbacks) { + currentTimeoutCallbacks.forEach(fastRafCallbacks.delete, fastRafCallbacks); + } + fastRafFallbackCallbacks = undefined; - currentTimeoutPrimaryCallbacks.forEach((cb) => cb()); - currentTimeoutCallbacks.forEach((cb) => cb()); - }, FAST_RAF_TIMEOUT_FALLBACK_MS); + if (fastRafFallbackTimeout) { + clearTimeout(fastRafFallbackTimeout); + fastRafFallbackTimeout = undefined; + } + + currentTimeoutCallbacks.forEach((cb) => cb()); + }, FAST_RAF_TIMEOUT_FALLBACK_MS); + } } } -export function fastRafPrimary(callback: NoneToVoidFunction) { - fastRaf(callback, true); -} - let onTickEndCallbacks: NoneToVoidFunction[] | undefined; -let onTickEndPrimaryCallbacks: NoneToVoidFunction[] | undefined; -export function onTickEnd(callback: NoneToVoidFunction, isPrimary = false) { +export function onTickEnd(callback: NoneToVoidFunction) { if (!onTickEndCallbacks) { - onTickEndCallbacks = isPrimary ? [] : [callback]; - onTickEndPrimaryCallbacks = isPrimary ? [callback] : []; + onTickEndCallbacks = [callback]; Promise.resolve().then(() => { const currentCallbacks = onTickEndCallbacks!; - const currentPrimaryCallbacks = onTickEndPrimaryCallbacks!; onTickEndCallbacks = undefined; - onTickEndPrimaryCallbacks = undefined; - currentPrimaryCallbacks.forEach((cb) => cb()); currentCallbacks.forEach((cb) => cb()); }); - } else if (isPrimary) { - onTickEndPrimaryCallbacks!.push(callback); } else { onTickEndCallbacks.push(callback); } } -export function onTickEndPrimary(callback: NoneToVoidFunction) { - onTickEnd(callback, true); -} - let beforeUnloadCallbacks: NoneToVoidFunction[] | undefined; export function onBeforeUnload(callback: NoneToVoidFunction, isLast = false) {