marko/widgets/update-manager.js
2016-11-13 13:53:10 -07:00

145 lines
4.4 KiB
JavaScript

var AsyncValue = require('raptor-async/AsyncValue');
var afterUpdateAsyncValue = null;
var afterUpdateAsyncValue = null;
var updatesScheduled = false;
var batchStack = []; // A stack of batched updates
var unbatchedQueue = []; // Used for scheduled batched updates
/**
* This function is called when we schedule the update of "unbatched"
* updates to widgets.
*/
function updateUnbatchedWidgets() {
if (!unbatchedQueue.length) {
// No widgets to update
return;
}
try {
updateWidgets(unbatchedQueue);
} finally {
// Reset the flag now that this scheduled batch update
// is complete so that we can later schedule another
// batched update if needed
updatesScheduled = false;
}
}
function scheduleUpdates() {
if (updatesScheduled) {
// We have already scheduled a batched update for the
// process.nextTick so nothing to do
return;
}
updatesScheduled = true;
process.nextTick(updateUnbatchedWidgets);
}
function onAfterUpdate(callback) {
scheduleUpdates();
if (!afterUpdateAsyncValue) {
afterUpdateAsyncValue = new AsyncValue();
}
afterUpdateAsyncValue.done(callback);
}
function updateWidgets(queue) {
// Loop over the widgets in the queue and update them.
// NOTE: Is it okay if the queue grows during the iteration
// since we will still get to them at the end
for (var i=0; i<queue.length; i++) {
var widget = queue[i];
widget.__updateQueued = false; // Reset the "__updateQueued" flag
widget.update(); // Do the actual widget update
}
// Clear out the queue by setting the length to zero
queue.length = 0;
}
function batchUpdate(func) {
// If the batched update stack is empty then this
// is the outer batched update. After the outer
// batched update completes we invoke the "afterUpdate"
// event listeners.
var isOuter = batchStack.length === 0;
var batch = {
queue: null
};
batchStack.push(batch);
try {
func();
} finally {
try {
// Update all of the widgets that where queued up
// in this batch (if any)
if (batch.queue) {
updateWidgets(batch.queue);
}
} finally {
// Now that we have completed the update of all the widgets
// in this batch we need to remove it off the top of the stack
batchStack.length--;
if (isOuter) {
// If there were any listeners for the "afterUpdate" event
// then notify those listeners now
if (afterUpdateAsyncValue) {
afterUpdateAsyncValue.resolve();
afterUpdateAsyncValue = null;
}
}
}
}
}
function queueWidgetUpdate(widget) {
if (widget.__updateQueued) {
// The widget has already been queued up for an update. Once
// the widget has actually been updated we will reset the
// "__updateQueued" flag so that it can be queued up again.
// Since the widget has already been queued up there is nothing
// that needs to be done.
return;
}
widget.__updateQueued = true;
var batchStackLen = batchStack.length;
if (batchStackLen) {
// When a batch update is started we push a new batch on to a stack.
// If the stack has a non-zero length then we know that a batch has
// been started so we can just queue the widget on the top batch. When
// the batch is ended this widget will be updated.
var batch = batchStack[batchStackLen-1];
// We default the batch queue to null to avoid creating an Array instance
// unnecessarily. If it is null then we create a new Array, otherwise
// we push it onto the existing Array queue
if (batch.queue) {
batch.queue.push(widget);
} else {
batch.queue = [widget];
}
} else {
// We are not within a batched update. We need to schedule a batch update
// for the process.nextTick (if that hasn't been done already) and we will
// add the widget to the unbatched queued
scheduleUpdates();
unbatchedQueue.push(widget);
}
}
exports.queueWidgetUpdate = queueWidgetUpdate;
exports.batchUpdate = batchUpdate;
exports.onAfterUpdate = onAfterUpdate;