UNPKG

@oxog/delay

Version:

A comprehensive, zero-dependency delay/timeout utility library with advanced timing features

141 lines 4.92 kB
"use strict"; 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