74 lines
2.5 KiB
JavaScript
74 lines
2.5 KiB
JavaScript
// Microtask-coalesced set. Adds are O(1); the apply callback runs once per
|
|
// scheduler tick with every id collected since the last flush.
|
|
//
|
|
// Used by the renderer to merge a burst of done-jobs (e.g. 500 jobs all
|
|
// finishing within milliseconds) into a single queueJobs.filter() pass —
|
|
// without this each event was its own O(N) sweep, so 500 finishes were
|
|
// O(N²) and visibly froze the UI on completion.
|
|
//
|
|
// Loaded both as a CommonJS module (Node tests) and as a browser global
|
|
// (renderer/app.js via index.html script tag).
|
|
|
|
(function (root) {
|
|
'use strict';
|
|
|
|
/**
|
|
* Build a coalesced set.
|
|
* @param {{ apply: (Set) => void, scheduler?: (cb: () => void) => void }} opts
|
|
* apply: called once per scheduler tick with the accumulated ids.
|
|
* scheduler: defaults to queueMicrotask. Tests can pass a synchronous
|
|
* stand-in to avoid async waits.
|
|
*/
|
|
function makeCoalescedSet(opts) {
|
|
if (!opts || typeof opts.apply !== 'function') {
|
|
throw new TypeError('makeCoalescedSet: { apply: fn } required');
|
|
}
|
|
const apply = opts.apply;
|
|
const scheduler = typeof opts.scheduler === 'function'
|
|
? opts.scheduler
|
|
: (typeof queueMicrotask === 'function' ? queueMicrotask : (cb) => Promise.resolve().then(cb));
|
|
let pending = new Set();
|
|
let scheduled = false;
|
|
|
|
function flush() {
|
|
scheduled = false;
|
|
if (pending.size === 0) return;
|
|
const drop = pending;
|
|
pending = new Set();
|
|
try { apply(drop); } catch (e) {
|
|
// Don't let a failing apply lock out the next batch — surface it
|
|
// but keep the coalescer usable.
|
|
if (typeof console !== 'undefined' && console.error) console.error(e);
|
|
}
|
|
}
|
|
|
|
return {
|
|
add(id) {
|
|
pending.add(id);
|
|
if (!scheduled) {
|
|
scheduled = true;
|
|
scheduler(flush);
|
|
}
|
|
},
|
|
/**
|
|
* Synchronously consume any pending ids. Used by beforeunload paths
|
|
* where we can't wait for the next microtask before persisting.
|
|
*/
|
|
drainSync() {
|
|
if (pending.size === 0) return;
|
|
const drop = pending;
|
|
pending = new Set();
|
|
scheduled = false;
|
|
apply(drop);
|
|
},
|
|
/** Introspection for tests + diagnostics. */
|
|
pendingSize() { return pending.size; },
|
|
isScheduled() { return scheduled; }
|
|
};
|
|
}
|
|
|
|
const api = { makeCoalescedSet };
|
|
if (typeof module !== 'undefined' && module.exports) module.exports = api;
|
|
else if (root) root.CoalescedSet = api;
|
|
})(typeof window !== 'undefined' ? window : this);
|