Files
Endoleap/src/lib/canvas-runtime/import-budget.ts
T

50 lines
2.8 KiB
TypeScript

/**
* 共享合作调度软预算,供导入、复制、恢复编码等逐项计算调用。
* 既按耗时判断,也按工作量兜底,避免粗粒度/倒退时钟让长任务一直不让出;单个不可分步骤仍由调用方限大小。
*/
import {fault} from '../canvas-core/values.ts';
/** 注入时钟、让出任务与目标时间片;它们不改变记录语义或操作身份。 */
export type ImportScheduling = {
now?: () => number;
yieldTask?: () => Promise<void>;
frameBudgetMs?: number;
};
/** A soft time slice with a work threshold, even when the clock is coarse.
* The caller bounds each indivisible parse/decode operation separately. */
/** 默认 6ms 软目标,闭包保存本时间片 started/work/unchecked 三个计数,不存任务或文档。
* step 可能同步返回 undefined;只有返回 Promise 且调用方 await 时才形成有效背压,应串行使用同一预算。
* 自定义 yieldTask 决定让出到微任务还是后续任务;预算不保证浏览器一定在该点完成一帧绘制。
* 构造只校验 frameBudgetMs,不预先检查 signal;units 应由调用者提供非负有限工作量。 */
export function createImportBudget(signal: AbortSignal, options: ImportScheduling = {}) {
const budget = options.frameBudgetMs ?? 6;
if (!Number.isFinite(budget) || budget <= 0) throw fault('invalid_import_budget');
const now = options.now ?? (() => performance.now());
const yieldTask = options.yieldTask ?? (() => new Promise<void>(resolve => setTimeout(resolve, 0)));
// 每128工作单位才读一次时钟,减少高频开销;总量16384强制让出,不能因时间测不准而无限执行。
let started = now(), work = 0, unchecked = 0;
return {
/** 累加 units;未满 128 个未检查单位且总量未满 16384 时直接返回,连 signal 也尚未检查。
* 阈值到达才读时钟;未超时保留 work 累计,超时/时间倒退或非有限值/工作量封顶则 await yieldTask。
* yield 后复查取消,成功才重设 started/work;读取时钟或 yield 抛错直接传播,不隐式重试。
* units 无运行时校验,负数/NaN 会破坏计数假设;本函数不测量单次实际 CPU 成本或字节数。 */
step(units = 1): Promise<void> | undefined {
work += units;
unchecked += units;
if (unchecked < 128 && work < 16384) return;
unchecked = 0;
if (signal.aborted) throw fault('import_aborted');
const elapsed = now() - started;
if (work < 16384 && elapsed >= 0 && elapsed < budget) return;
// 这里的异步边界才实际交出执行权;预算是 cooperative,不能抢占前面已经运行的同步工作。
return (async () => {
await yieldTask();
if (signal.aborted) throw fault('import_aborted');
started = now();
work = 0;
})();
},
};
}