Schedulers: Fix race condition, get rid of isPrimary

This commit is contained in:
Alexander Zinchuk 2023-04-10 00:54:14 +02:00
parent cafbaf91df
commit 752d131e12

View File

@ -65,37 +65,21 @@ export function throttle<F extends AnyToVoidFunction>(
} }
export function fastRafWithFallback<F extends AnyToVoidFunction>(fn: F) { export function fastRafWithFallback<F extends AnyToVoidFunction>(fn: F) {
return fastRaf(fn, false, true); return fastRaf(fn, true);
}
export function fastRafPrimaryWithFallback<F extends AnyToVoidFunction>(fn: F) {
return fastRaf(fn, true, true);
} }
export function throttleWithRafFallback<F extends AnyToVoidFunction>(fn: F) { export function throttleWithRafFallback<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(fastRafWithFallback, fn); return throttleWith(fastRafWithFallback, fn);
} }
export function throttleWithPrimaryRafFallback<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(fastRafPrimaryWithFallback, fn);
}
export function throttleWithRaf<F extends AnyToVoidFunction>(fn: F) { export function throttleWithRaf<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(fastRaf, fn); return throttleWith(fastRaf, fn);
} }
export function throttleWithPrimaryRaf<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(fastRafPrimary, fn);
}
export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) { export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(onTickEnd, fn); return throttleWith(onTickEnd, fn);
} }
export function throttleWithPrimaryTickEnd<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(onTickEndPrimary, fn);
}
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) { export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
let waiting = false; let waiting = false;
let args: Parameters<F>; let args: Parameters<F>;
@ -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; const FAST_RAF_TIMEOUT_FALLBACK_MS = 300;
let fastRafCallbacks: Set<NoneToVoidFunction> | undefined;
let fastRafFallbackCallbacks: Set<NoneToVoidFunction> | undefined;
let fastRafFallbackTimeout: number | undefined;
// May result in an immediate execution if called from another RAF callback which was scheduled // 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` // (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) { if (!fastRafCallbacks) {
fastRafCallbacks = !withTimeoutFallback && !isPrimary ? [callback] : []; fastRafCallbacks = new Set([callback]);
fastRafPrimaryCallbacks = !withTimeoutFallback && isPrimary ? [callback] : [];
timeoutCallbacks = withTimeoutFallback && !isPrimary ? [callback] : [];
timeoutPrimaryCallbacks = withTimeoutFallback && isPrimary ? [callback] : [];
requestAnimationFrame(() => { requestAnimationFrame(() => {
const currentCallbacks = fastRafCallbacks!; const currentCallbacks = fastRafCallbacks!;
const currentPrimaryCallbacks = fastRafPrimaryCallbacks!;
const currentTimeoutCallbacks = timeoutCallbacks!;
const currentTimeoutPrimaryCallbacks = timeoutPrimaryCallbacks!;
if (timeout) clearTimeout(timeout);
timeout = undefined;
fastRafCallbacks = undefined; fastRafCallbacks = undefined;
fastRafPrimaryCallbacks = undefined; fastRafFallbackCallbacks = undefined;
timeoutCallbacks = undefined;
timeoutPrimaryCallbacks = undefined; if (fastRafFallbackTimeout) {
clearTimeout(fastRafFallbackTimeout);
fastRafFallbackTimeout = undefined;
}
currentPrimaryCallbacks.forEach((cb) => cb());
currentTimeoutPrimaryCallbacks.forEach((cb) => cb());
currentCallbacks.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 { } else {
fastRafCallbacks.push(callback); fastRafCallbacks.add(callback);
} }
if (!timeout && withTimeoutFallback) { if (withTimeoutFallback) {
timeout = setTimeout(() => { if (!fastRafFallbackCallbacks) {
const currentTimeoutCallbacks = timeoutCallbacks!; fastRafFallbackCallbacks = new Set([callback]);
const currentTimeoutPrimaryCallbacks = timeoutPrimaryCallbacks!; } else {
fastRafFallbackCallbacks.add(callback);
}
if (timeout) clearTimeout(timeout); if (!fastRafFallbackTimeout) {
timeout = undefined; fastRafFallbackTimeout = window.setTimeout(() => {
const currentTimeoutCallbacks = fastRafFallbackCallbacks!;
timeoutCallbacks = []; if (fastRafCallbacks) {
timeoutPrimaryCallbacks = []; currentTimeoutCallbacks.forEach(fastRafCallbacks.delete, fastRafCallbacks);
}
fastRafFallbackCallbacks = undefined;
currentTimeoutPrimaryCallbacks.forEach((cb) => cb()); if (fastRafFallbackTimeout) {
currentTimeoutCallbacks.forEach((cb) => cb()); clearTimeout(fastRafFallbackTimeout);
}, FAST_RAF_TIMEOUT_FALLBACK_MS); fastRafFallbackTimeout = undefined;
}
currentTimeoutCallbacks.forEach((cb) => cb());
}, FAST_RAF_TIMEOUT_FALLBACK_MS);
}
} }
} }
export function fastRafPrimary(callback: NoneToVoidFunction) {
fastRaf(callback, true);
}
let onTickEndCallbacks: NoneToVoidFunction[] | undefined; let onTickEndCallbacks: NoneToVoidFunction[] | undefined;
let onTickEndPrimaryCallbacks: NoneToVoidFunction[] | undefined;
export function onTickEnd(callback: NoneToVoidFunction, isPrimary = false) { export function onTickEnd(callback: NoneToVoidFunction) {
if (!onTickEndCallbacks) { if (!onTickEndCallbacks) {
onTickEndCallbacks = isPrimary ? [] : [callback]; onTickEndCallbacks = [callback];
onTickEndPrimaryCallbacks = isPrimary ? [callback] : [];
Promise.resolve().then(() => { Promise.resolve().then(() => {
const currentCallbacks = onTickEndCallbacks!; const currentCallbacks = onTickEndCallbacks!;
const currentPrimaryCallbacks = onTickEndPrimaryCallbacks!;
onTickEndCallbacks = undefined; onTickEndCallbacks = undefined;
onTickEndPrimaryCallbacks = undefined;
currentPrimaryCallbacks.forEach((cb) => cb());
currentCallbacks.forEach((cb) => cb()); currentCallbacks.forEach((cb) => cb());
}); });
} else if (isPrimary) {
onTickEndPrimaryCallbacks!.push(callback);
} else { } else {
onTickEndCallbacks.push(callback); onTickEndCallbacks.push(callback);
} }
} }
export function onTickEndPrimary(callback: NoneToVoidFunction) {
onTickEnd(callback, true);
}
let beforeUnloadCallbacks: NoneToVoidFunction[] | undefined; let beforeUnloadCallbacks: NoneToVoidFunction[] | undefined;
export function onBeforeUnload(callback: NoneToVoidFunction, isLast = false) { export function onBeforeUnload(callback: NoneToVoidFunction, isLast = false) {