Skip to content

Instantly share code, notes, and snippets.

@thysultan
Last active January 23, 2017 11:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thysultan/3761df3e73d0092baa0527a83c35a5aa to your computer and use it in GitHub Desktop.
Save thysultan/3761df3e73d0092baa0527a83c35a5aa to your computer and use it in GitHub Desktop.
function frameScheduler (fps) {
var highPriWork = [];
var lowPriWork = [];
var highPriWorkLen = 0;
var lowPriWorkLen = 0;
var frameBudget = 1000 / fps;
var lastTime = 0;
var asyncCallback;
var Promise = window.Promise;
var requestAnimationFrame = window.requestAnimationFrame;
var promiseSupport = Promise !== void 0;
var requestAnimationFrameSupport = requestAnimationFrame !== void 0;
if (promiseSupport) {
asyncCallback = function asyncCallback (callback) { Promise.resolve().then(callback); }
} else if (requestAnimationFrameSupport) {
asyncCallback = function asyncCallback (callback) { requestAnimationFrame(callback); }
} else {
asyncCallback = function asyncCallback (callback) { setTimeout(callback, 0); }
}
function flushWork (highPriority) {
var task = highPriority ? highPriWork.shift() : lowPriWork.shift();
var func = task[0];
var length = task[1];
var args = length !== 0 ? task[2] : null;
var context = task[3];
switch (length) {
case 0: func.call(context); break;
case 1: func.call(context, args[0]); break;
case 2: func.call(context, args[0], args[1]); break;
case 3: func.call(context, args[0], args[1], args[2]); break;
case 4: func.call(context, args[0], args[1], args[2], args[3]); break;
case 5: func.call(context, args[0], args[1], args[2], args[3], args[4]); break;
case 6: func.call(context, args[0], args[1], args[2], args[3], args[4], args[5]); break;
}
}
function throttleWork () {
var time = Date.now();
var delta = time - lastTime;
if (delta > frameBudget) {
lastTime = time - (delta % frameBudget);
if (highPriWorkLen !== 0) {
flushWork((highPriWorkLen--, true));
} else if (lowPriWorkLen !== 0) {
flushWork((lowPriWorkLen--, false));
}
} else if (highPriWorkLen !== 0 || lowPriWorkLen !== 0) {
asyncCallback(throttleWork);
}
}
/**
* @param {boolean} highPriority
* @param {function} func
* @param {number} length - number of arguments passed to func
* @param {args} args - arguments passed to func
* @param {context} context - the this context to execute the function with
*/
return function assignWork (highPriority, func, length, args, context) {
if (highPriority) {
highPriWork[highPriWorkLen++] = [func, length, args, context];
} else {
lowPriWork[lowPriWorkLen++] = [func, length, args, context];
}
asyncCallback(throttleWork);
}
}
@thysultan
Copy link
Author

thysultan commented Jan 23, 2017

@example

var fiberOfWork = frameScheduler(60);

// low priority
fiberOfWork(false, function () {
	console.log('first', arguments);
}, 3, [1, 2, 3], null);

// high priority
fiberOfWork(true, function () {
	console.log('second', arguments);	
}, 3, [1, 2, 3], null);

But you don't need to use closures

var obj = {
    foo: function (a, b, c) {
        return a+b+c;
    }
}

// reads, add high priority work `foo` that is called with 3 arguments `[1, 2, 3]` within the context of obj. 
fiberOfWork(true, obj.foo, 3, [1, 2, 3], obj);

High priority work will take precedence of low priority regardless of the order in which the work is added to the queue, work is executed in the order it is added to the queue for work sharing the same priority level.


curious to know how this could be improved.

@thysultan
Copy link
Author

a visualisation of work timeline cycles - https://codepen.io/thysultan/pen/VPboOB.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment