85 lines
3.3 KiB
JavaScript
85 lines
3.3 KiB
JavaScript
(function(root,factory){
|
|
const api=factory();
|
|
if(typeof module==='object'&&module.exports)module.exports=api;
|
|
root.BendFrameScheduler=api;
|
|
})(typeof globalThis!=='undefined'?globalThis:this,function(){
|
|
'use strict';
|
|
|
|
const DEFAULT_INTERVAL=1000/60;
|
|
|
|
function createFrameScheduler(options={}){
|
|
const requestFrame=options.requestFrame||globalThis.requestAnimationFrame?.bind(globalThis);
|
|
const cancelFrame=options.cancelFrame||globalThis.cancelAnimationFrame?.bind(globalThis);
|
|
const setDelay=options.setDelay||globalThis.setTimeout?.bind(globalThis);
|
|
const clearDelay=options.clearDelay||globalThis.clearTimeout?.bind(globalThis);
|
|
const now=options.now||(()=>globalThis.performance?.now?.()||Date.now());
|
|
const interval=Math.max(1,Number(options.interval)||DEFAULT_INTERVAL);
|
|
const tolerance=Math.max(0,Number(options.tolerance)||0);
|
|
const watchdogDelay=Math.max(interval,Number(options.watchdogDelay)||interval+2);
|
|
const commit=options.commit;
|
|
const validTiming=typeof requestFrame==='function'&&typeof cancelFrame==='function'
|
|
&&typeof setDelay==='function'&&typeof clearDelay==='function';
|
|
if(!validTiming)throw new TypeError('Frame scheduler timing functions are required');
|
|
if(typeof commit!=='function')throw new TypeError('Frame scheduler commit callback is required');
|
|
|
|
let frame=0,timer=0,lastCommitAt=0,latest=null,revision=0,committedRevision=0,disposed=false;
|
|
const clearArmed=()=>{
|
|
if(frame)cancelFrame(frame);
|
|
if(timer)clearDelay(timer);
|
|
frame=0;timer=0;
|
|
};
|
|
const arm=()=>{
|
|
if(disposed||frame||timer||latest==null)return false;
|
|
const step=timestamp=>{
|
|
if(disposed||latest==null){clearArmed();return false}
|
|
const at=Number.isFinite(timestamp)?timestamp:now();
|
|
if(lastCommitAt&&at+tolerance<lastCommitAt+interval){
|
|
if(frame)cancelFrame(frame);
|
|
frame=requestFrame(step);
|
|
return false;
|
|
}
|
|
clearArmed();
|
|
const value=latest,currentRevision=revision;
|
|
latest=null;lastCommitAt=at;committedRevision=currentRevision;
|
|
commit(value,at,currentRevision);
|
|
if(latest!=null)arm();
|
|
return true;
|
|
};
|
|
frame=requestFrame(step);
|
|
timer=setDelay(()=>{
|
|
timer=0;
|
|
if(typeof options.onWatchdog==='function')options.onWatchdog();
|
|
step(now());
|
|
},watchdogDelay);
|
|
return true;
|
|
};
|
|
const push=value=>{
|
|
if(disposed)return false;
|
|
latest=value;revision++;arm();return revision;
|
|
};
|
|
const flush=()=>{
|
|
if(disposed||latest==null)return false;
|
|
clearArmed();
|
|
const value=latest,currentRevision=revision,at=now();
|
|
latest=null;lastCommitAt=at;committedRevision=currentRevision;
|
|
commit(value,at,currentRevision);
|
|
if(latest!=null)arm();
|
|
return true;
|
|
};
|
|
const cancel=({resetCadence=false}={})=>{
|
|
clearArmed();latest=null;
|
|
if(resetCadence)lastCommitAt=0;
|
|
};
|
|
const dispose=()=>{cancel({resetCadence:true});disposed=true};
|
|
const inspect=()=>Object.freeze({
|
|
armed:Boolean(frame||timer),
|
|
pending:latest!=null,
|
|
revision,
|
|
committedRevision,
|
|
lastCommitAt
|
|
});
|
|
return Object.freeze({push,flush,cancel,dispose,inspect});
|
|
}
|
|
|
|
return Object.freeze({DEFAULT_INTERVAL,createFrameScheduler});
|
|
});
|