%PDF- %PDF-
| Direktori : /proc/self/root/data/old/home/stash/atlassian-stash/static/util/ |
| Current File : //proc/self/root/data/old/home/stash/atlassian-stash/static/util/performance.js |
/**
* This module holds functions for improving the performance of JS code.
* Currently it contains queueDOMRead and queueDOMWrite, which can be used to ensure all DOM reads are executed at
* once, and all DOM writes are executed at once to avoid reflows that occur when reads and writes are interleaved.
*
* See also: http://www.stubbornella.org/content/2009/03/27/reflows-repaints-css-performance-making-your-javascript-slow/
*/
define('util/performance', [
'jquery'
], function (
$
) {
'use strict';
/**
* This creates a queue of function callbacks. When the first callback is added (using the queue function),
* the whole queue is registered to be invoked as soon as the current JS event loop finishes executing
* (with a setTimeout(...,0)). If any other callbacks get registered in the meantime, they'll all be executed right
* after the first one. Then the queue will reset itself to empty and await the next callback to be added. The
* queue takes in a callback (executedCallback) that will be called whenever the queue is executed and reset.
*
* You can also force immediate queue execution without waiting for the setTimeout (forceExecute). Below I use
* this to avoid superfluous setTimeouts. If the Read queue executes, it will call the Write queue immediately
* afterwards, which will call the Read queue in case anything was added during the Write callbacks, and so forth
* until there are no more new callbacks being added to either queue.
* @param {Function} executedCallback - a callback which will be called whenever the queue is executed.
* @return {Object}
*/
function getDelayedExecutionQueue(name, executedCallback) {
var queue = new $.Callbacks();
var executing = false;
var pendingExecution = null;
function execute() {
pendingExecution = null;
executing = true;
// execute all the pending callbacks
queue.fire();
// reset the queue
queue = new $.Callbacks();
executing = false;
// call the callback since we just executed the queue.
executedCallback();
}
return {
// Helps with testing
name : name,
/**
* If there are callbacks scheduled to be executed, remove the scheduled execution, execute them now
* and reset the queue.
*/
forceExecute : function() {
if (pendingExecution) {
clearTimeout(pendingExecution);
execute();
}
},
/**
* Add a callback to the queue. If there is no scheduled execution for the queue, schedule one.
* @param fn {Function} the callback to add
*/
queue : function(fn) {
if (executing) { // if we're currently executing, there is no harm executing this immediately.
fn();
} else { // otherwise, add it to the queue for executing later.
queue.add(fn);
if (!pendingExecution) { // if there isn't an execution scheduled yet, schedule one.
pendingExecution = setTimeout(execute, 0);
}
}
}
};
}
// create a separate queue for DOM reads and DOM writes. Avoid the overhead of setTimeout by forcing immediate
// execution of the partner queue when all your own callbacks have finished.
var DOMReadQueue = getDelayedExecutionQueue('READ', function() { DOMWriteQueue.forceExecute(); });
var DOMWriteQueue = getDelayedExecutionQueue('WRITE', function() { DOMReadQueue.forceExecute(); });
/**
* Returns a function that ensures that no more than `opt_queueMax` callbacks are pending at any time. This is useful
* to ensure that multiple calls don't build up in the queue.
*
* @example
*
* $(window).on('scroll', enqueueCapped(requestAnimationFrame, somethingExpensive));
*
* @param {Function} queuingFn - A function that accepts functions to execute.
* @param {Function} queuedFn - The function that should be enqueued.
* @param {number} [opt_queueMax=1] - The maximum number of pending executions before further enqueue requests are ignored.
* @returns {Function}
*/
function enqueueCapped(queuingFn, queuedFn, opt_queueMax) {
opt_queueMax = opt_queueMax || 1;
var waiting = 0;
return function enqueue() {
if (waiting >= opt_queueMax) {
return;
}
waiting++;
queuingFn(function dequeue() {
try {
return queuedFn.apply(this, arguments);
} finally {
waiting--;
}
});
};
}
/**
* Return a function that will async map through items in the input array, returning a promise that will resolve
* to the output array when it's complete.
*
* The batch size is adaptive, but you can set min and max values to constrain it.
*
* We return a Deferred rather than a Promise. You can reject or resolve the deferred yourself and no more batches will run.
*
* @param {Function} fn - iterator
* @param {Object} [batchLimits]
* @param {number} [batchLimits.initial=500]
* @param {number} [batchLimits.min=10]
* @param {number} [batchLimits.max=Infinity]
* @param {Function} [runBatch] - if you want to run the batch in a wrapper function
* @returns {Function}
*/
function frameBatchedMap(fn, batchLimits, runBatch) {
batchLimits = $.extend({
initial : 500,
min : 10,
max : Infinity
}, batchLimits);
runBatch = runBatch || function(fn) { fn(); };
function clamp(n) {
return Math.min(batchLimits.max, Math.max(batchLimits.min, n));
}
return function(arr) {
var batchSize = batchLimits.initial;
var deferred = $.Deferred();
var i = 0;
var out = [];
function singleBatch() {
for (var end = Math.min(i + batchSize, arr.length); i < end; i++) {
out[i] = fn(arr[i]);
}
}
requestAnimationFrame(function loop() {
if (deferred.state() !== 'pending') {
return;
}
var start = new Date().getTime();
runBatch(singleBatch);
var end = new Date().getTime();
var timeSpent = end - start;
// adjust so the next batch takes 15ms
// Assumes each item takes the same amount of time
// batchSize / timeSpent == newBatchSize / 15ms
// newBatchSize = 15 * batchSize / timeSpent
// We use ceil to get an integer out of it (that isn't 0)
// We clamp this within the requested min/max limits
batchSize = clamp(Math.ceil(batchSize * (15 / timeSpent)));
if (i === arr.length) {
deferred.resolve(out);
} else {
requestAnimationFrame(loop);
}
});
return deferred;
};
}
return {
queueDOMRead : DOMReadQueue.queue.bind(DOMReadQueue),
queueDOMWrite : DOMWriteQueue.queue.bind(DOMWriteQueue),
enqueueCapped : enqueueCapped,
frameBatchedMap : frameBatchedMap
};
});