@oxog/delay
Version:
A comprehensive, zero-dependency delay/timeout utility library with advanced timing features
141 lines • 4.92 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.DelayScheduler = void 0;
exports.createBatchScheduler = createBatchScheduler;
exports.preciseDelay = preciseDelay;
exports.createDriftCompensatedTimer = createDriftCompensatedTimer;
const time_js_1 = require("../utils/time.js");
const delay_js_1 = require("./delay.js");
function createBatchScheduler(options = {}) {
const { maxBatchSize = 100, batchWindow = 16 } = options; // 16ms ≈ 1 frame
const pendingDelays = [];
let batchTimeoutId;
let isProcessing = false;
const processBatch = async () => {
if (isProcessing || pendingDelays.length === 0) {
return;
}
isProcessing = true;
batchTimeoutId = undefined;
// Group delays by duration for efficiency
const delayGroups = new Map();
const itemsToProcess = pendingDelays.splice(0, maxBatchSize);
for (const item of itemsToProcess) {
const group = delayGroups.get(item.ms) || [];
group.push({ resolve: item.resolve, reject: item.reject });
delayGroups.set(item.ms, group);
}
// Process each group
const promises = Array.from(delayGroups.entries()).map(async ([ms, items]) => {
try {
await (0, delay_js_1.createBasicDelay)(ms);
items.forEach(item => item.resolve());
}
catch (error) {
items.forEach(item => item.reject(error));
}
});
await Promise.all(promises);
isProcessing = false;
// Schedule next batch if there are more items
if (pendingDelays.length > 0) {
scheduleBatch();
}
};
const scheduleBatch = () => {
if (batchTimeoutId === undefined && !isProcessing) {
batchTimeoutId = setTimeout(processBatch, batchWindow);
}
};
return {
add(ms) {
return new Promise((resolve, reject) => {
pendingDelays.push({ ms, resolve, reject });
scheduleBatch();
});
},
flush() {
if (batchTimeoutId !== undefined) {
if (typeof batchTimeoutId === 'number') {
clearTimeout(batchTimeoutId);
}
else {
clearTimeout(batchTimeoutId);
}
batchTimeoutId = undefined;
}
void processBatch();
},
clear() {
if (batchTimeoutId !== undefined) {
if (typeof batchTimeoutId === 'number') {
clearTimeout(batchTimeoutId);
}
else {
clearTimeout(batchTimeoutId);
}
batchTimeoutId = undefined;
}
// Reject all pending delays
const error = new Error('Batch scheduler cleared');
pendingDelays.forEach(item => item.reject(error));
pendingDelays.length = 0;
isProcessing = false;
},
};
}
async function preciseDelay(ms) {
const startTime = (0, time_js_1.getHighResolutionTime)();
let remaining = ms;
// Use a combination of setTimeout and busy waiting for high precision
const threshold = 4; // Switch to busy waiting when less than 4ms remain
while (remaining > threshold) {
const sleepTime = Math.min(remaining - threshold, 15); // Sleep in chunks
await (0, delay_js_1.createBasicDelay)(sleepTime);
const elapsed = (0, time_js_1.getHighResolutionTime)() - startTime;
remaining = ms - elapsed;
}
// Busy wait for the remaining time
while ((0, time_js_1.getHighResolutionTime)() - startTime < ms) {
// Busy wait - this is intentionally blocking for precision
}
}
class DelayScheduler {
constructor() {
this.scheduler = createBatchScheduler();
}
schedule(ms) {
return this.scheduler.add(ms);
}
flush() {
this.scheduler.flush();
}
clear() {
this.scheduler.clear();
}
}
exports.DelayScheduler = DelayScheduler;
function createDriftCompensatedTimer(callback, interval) {
const start = (0, time_js_1.getHighResolutionTime)();
let count = 0;
let timeoutId;
const tick = () => {
count++;
const target = start + count * interval;
const current = (0, time_js_1.getHighResolutionTime)();
const drift = current - target;
callback();
const nextInterval = Math.max(0, interval - drift);
timeoutId = setTimeout(tick, nextInterval);
};
timeoutId = setTimeout(tick, interval);
return () => {
if (typeof timeoutId === 'number') {
clearTimeout(timeoutId);
}
else {
clearTimeout(timeoutId);
}
};
}
//# sourceMappingURL=scheduler.js.map