41 lines
1.3 KiB
JavaScript
41 lines
1.3 KiB
JavaScript
let messageChannelYield;
|
|
let cooperativeTurn = 0;
|
|
|
|
export const COOPERATIVE_TIMER_TURN_INTERVAL = 8;
|
|
|
|
function createMessageChannelYield() {
|
|
if (typeof globalThis.MessageChannel !== "function") return undefined;
|
|
const channel = new globalThis.MessageChannel();
|
|
const queue = [];
|
|
channel.port1.onmessage = () => queue.shift()?.();
|
|
channel.port1.unref?.();
|
|
channel.port2.unref?.();
|
|
return () => new Promise((resolve) => {
|
|
queue.push(resolve);
|
|
channel.port2.postMessage(0);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Yield one cooperative task without relying on nested zero-delay timers.
|
|
* scheduler.yield() is preferred when the host implements it; MessageChannel
|
|
* keeps the fallback task-based and avoids browser timer clamping.
|
|
*/
|
|
export function cooperativeYield() {
|
|
cooperativeTurn += 1;
|
|
if (cooperativeTurn % COOPERATIVE_TIMER_TURN_INTERVAL === 0) {
|
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|
|
const hostScheduler = globalThis.scheduler;
|
|
if (typeof hostScheduler?.yield === "function") {
|
|
try {
|
|
return hostScheduler.yield();
|
|
} catch {
|
|
// A partial scheduler implementation must not make computation unusable.
|
|
}
|
|
}
|
|
messageChannelYield ??= createMessageChannelYield();
|
|
if (messageChannelYield) return messageChannelYield();
|
|
return new Promise((resolve) => setTimeout(resolve, 0));
|
|
}
|