slackgmailskypefacebook-workplaceoutlookemailmicrosoft-teamsdiscordmessengercustom-servicesmacoslinuxwindowsinboxwhatsappicloudtweetdeckhipchattelegramhangouts
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
566 lines
15 KiB
566 lines
15 KiB
9 years ago
|
/**
|
||
|
* This class is used to bulk schedule a set of `Ext.util.Schedulable` items. The items
|
||
|
* in the scheduler request time by calling their `schedule` method and when the time has
|
||
|
* arrived its `react` method is called.
|
||
|
*
|
||
|
* The `react` methods are called in dependency order as determined by the sorting process.
|
||
|
* The sorting process relies on each item to implement its own `sort` method.
|
||
|
*
|
||
|
* @private
|
||
|
*/
|
||
|
Ext.define('Ext.util.Scheduler', {
|
||
|
mixins: [
|
||
|
'Ext.mixin.Observable'
|
||
|
],
|
||
|
|
||
|
requires: [
|
||
|
'Ext.util.Bag'
|
||
|
],
|
||
|
|
||
|
busyCounter: 0,
|
||
|
lastBusyCounter: 0,
|
||
|
|
||
|
destroyed: false,
|
||
|
|
||
|
firing: null,
|
||
|
|
||
|
notifyIndex: -1,
|
||
|
|
||
|
nextId: 0,
|
||
|
|
||
|
orderedItems: null,
|
||
|
|
||
|
passes: 0,
|
||
|
|
||
|
scheduledCount: 0,
|
||
|
|
||
|
validIdRe: null,
|
||
|
|
||
|
config: {
|
||
|
/**
|
||
|
* @cfg {Number} cycleLimit
|
||
|
* The maximum number of iterations to make over the items in one `notify` call.
|
||
|
* This is used to prevent run away logic from looping infinitely. If this limit
|
||
|
* is exceeded, an error is thrown (in development builds).
|
||
|
* @private
|
||
|
*/
|
||
|
cycleLimit: 5,
|
||
|
|
||
|
/**
|
||
|
* @cfg {String/Function} preSort
|
||
|
* If provided the `Schedulable` items will be pre-sorted by this function or
|
||
|
* property value before the dependency sort.
|
||
|
*/
|
||
|
preSort: null,
|
||
|
|
||
|
/**
|
||
|
* @cfg {Number} tickDelay
|
||
|
* The number of milliseconds to delay notification after the first `schedule`
|
||
|
* request.
|
||
|
*/
|
||
|
tickDelay: 5
|
||
|
},
|
||
|
|
||
|
constructor: function (config) {
|
||
|
//<debug>
|
||
|
if (Ext.util.Scheduler.instances) {
|
||
|
Ext.util.Scheduler.instances.push(this);
|
||
|
} else {
|
||
|
Ext.util.Scheduler.instances = [ this ];
|
||
|
}
|
||
|
this.id = Ext.util.Scheduler.count = (Ext.util.Scheduler.count || 0) + 1;
|
||
|
//</debug>
|
||
|
|
||
|
this.mixins.observable.constructor.call(this, config);
|
||
|
|
||
|
this.items = new Ext.util.Bag();
|
||
|
},
|
||
|
|
||
|
destroy: function () {
|
||
|
var me = this,
|
||
|
timer = me.timer;
|
||
|
|
||
|
if (timer) {
|
||
|
window.clearTimeout(timer);
|
||
|
me.timer = null;
|
||
|
}
|
||
|
|
||
|
me.destroyed = true;
|
||
|
me.items.destroy();
|
||
|
me.items = me.orderedItems = null;
|
||
|
|
||
|
me.destroy = Ext.emptyFn;
|
||
|
|
||
|
//<debug>
|
||
|
Ext.Array.remove(Ext.util.Scheduler.instances, this);
|
||
|
//</debug>
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Adds an item to the scheduler. This is called internally by the `constructor` of
|
||
|
* `{@link Ext.util.Schedulable}`.
|
||
|
*
|
||
|
* @param {Object} item The item to add.
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
add: function (item) {
|
||
|
var me = this,
|
||
|
items = me.items;
|
||
|
|
||
|
if (items === me.firing) {
|
||
|
me.items = items = items.clone();
|
||
|
}
|
||
|
|
||
|
item.id = item.id || ++me.nextId;
|
||
|
item.scheduler = me;
|
||
|
|
||
|
items.add(item);
|
||
|
|
||
|
if (!me.sortMap) {
|
||
|
// If we are sorting we don't want to invalidate this... we will pick up the
|
||
|
// new items just fine.
|
||
|
me.orderedItems = null;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Removes an item to the scheduler. This is called internally by the `destroy` method
|
||
|
* of `{@link Ext.util.Schedulable}`.
|
||
|
*
|
||
|
* @param {Object} item The item to remove.
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
remove: function (item) {
|
||
|
var me = this,
|
||
|
items = me.items;
|
||
|
|
||
|
if (me.destroyed) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//<debug>
|
||
|
if (me.sortMap) {
|
||
|
Ext.Error.raise('Items cannot be removed during sort');
|
||
|
}
|
||
|
//</debug>
|
||
|
|
||
|
if (items === me.firing) {
|
||
|
me.items = items = items.clone();
|
||
|
}
|
||
|
|
||
|
if (item.scheduled) {
|
||
|
me.unscheduleItem(item);
|
||
|
item.scheduled = false;
|
||
|
}
|
||
|
|
||
|
items.remove(item);
|
||
|
|
||
|
me.orderedItems = null;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method is called internally as needed to sort or resort the items in their
|
||
|
* proper dependency order.
|
||
|
*
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
sort: function () {
|
||
|
var me = this,
|
||
|
items = me.items,
|
||
|
sortMap = {},
|
||
|
preSort = me.getPreSort(),
|
||
|
i, item;
|
||
|
|
||
|
me.orderedItems = [];
|
||
|
me.sortMap = sortMap;
|
||
|
|
||
|
//<debug>
|
||
|
me.sortStack = [];
|
||
|
//</debug>
|
||
|
|
||
|
if (preSort) {
|
||
|
items.sort(preSort);
|
||
|
}
|
||
|
|
||
|
items = items.items; // grab the items array
|
||
|
|
||
|
// We reference items.length since items can be added during this loop
|
||
|
for (i = 0; i < items.length; ++i) {
|
||
|
item = items[i];
|
||
|
if (!sortMap[item.id]) {
|
||
|
me.sortItem(item);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
me.sortMap = null;
|
||
|
|
||
|
//<debug>
|
||
|
me.sortStack = null;
|
||
|
//</debug>
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Adds one item to the sorted items array. This can be called by the `sort` method of
|
||
|
* `{@link Ext.util.Sortable sortable}` objects to add an item on which it depends.
|
||
|
*
|
||
|
* @param {Object} item The item to add.
|
||
|
* @return {Ext.util.Scheduler} This instance.
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
sortItem: function (item) {
|
||
|
var me = this,
|
||
|
sortMap = me.sortMap,
|
||
|
orderedItems = me.orderedItems,
|
||
|
itemId;
|
||
|
|
||
|
if (!item.scheduler) {
|
||
|
me.add(item);
|
||
|
}
|
||
|
|
||
|
itemId = item.id;
|
||
|
|
||
|
//<debug>
|
||
|
if (item.scheduler !== me) {
|
||
|
Ext.Error.raise('Item ' + itemId + ' belongs to another Scheduler');
|
||
|
}
|
||
|
|
||
|
me.sortStack.push(item);
|
||
|
|
||
|
if (sortMap[itemId] === 0) {
|
||
|
for (var cycle = [], i = 0; i < me.sortStack.length; ++i) {
|
||
|
cycle[i] = me.sortStack[i].getFullName();
|
||
|
}
|
||
|
Ext.Error.raise('Dependency cycle detected: ' + cycle.join('\n --> '));
|
||
|
}
|
||
|
//</debug>
|
||
|
|
||
|
if (!(itemId in sortMap)) {
|
||
|
// In production builds the above "if" will kick out the items that have
|
||
|
// already been added (which it must) but also those that are being added
|
||
|
// and have created a cycle (by virtue of the setting to 0). This check
|
||
|
// should not be needed if cycles were all detected and removed in dev but
|
||
|
// this is better than infinite recursion.
|
||
|
sortMap[itemId] = 0;
|
||
|
|
||
|
if (!item.sort.$nullFn) {
|
||
|
item.sort();
|
||
|
}
|
||
|
|
||
|
sortMap[itemId] = 1;
|
||
|
|
||
|
item.order = me.orderedItems.length;
|
||
|
orderedItems.push(item);
|
||
|
}
|
||
|
|
||
|
//<debug>
|
||
|
me.sortStack.pop();
|
||
|
//</debug>
|
||
|
|
||
|
return me;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Adds multiple items to the sorted items array. This can be called by the `sort`
|
||
|
* method of `{@link Ext.util.Sortable sortable}` objects to add items on which it
|
||
|
* depends.
|
||
|
*
|
||
|
* @param {Object/Object[]} items The items to add. If this is an object, the values
|
||
|
* are considered the items and the keys are ignored.
|
||
|
* @return {Ext.util.Scheduler} This instance.
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
sortItems: function (items) {
|
||
|
var me = this,
|
||
|
sortItem = me.sortItem;
|
||
|
|
||
|
if (items) {
|
||
|
if (items instanceof Array) {
|
||
|
Ext.each(items, sortItem, me);
|
||
|
} else {
|
||
|
Ext.Object.eachValue(items, sortItem, me);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return me;
|
||
|
},
|
||
|
|
||
|
applyPreSort: function (preSort) {
|
||
|
if (typeof preSort === 'function') {
|
||
|
return preSort;
|
||
|
}
|
||
|
|
||
|
var parts = preSort.split(','),
|
||
|
direction = [],
|
||
|
length = parts.length,
|
||
|
c, i, s;
|
||
|
|
||
|
for (i = 0; i < length; ++i) {
|
||
|
direction[i] = 1;
|
||
|
s = parts[i];
|
||
|
|
||
|
if ((c = s.charAt(0)) === '-') {
|
||
|
direction[i] = -1;
|
||
|
} else if (c !== '+') {
|
||
|
c = 0;
|
||
|
}
|
||
|
|
||
|
if (c) {
|
||
|
parts[i] = s.substring(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return function (lhs, rhs) {
|
||
|
var ret = 0,
|
||
|
i, prop, v1, v2;
|
||
|
|
||
|
for (i = 0; !ret && i < length; ++i) {
|
||
|
prop = parts[i];
|
||
|
v1 = lhs[prop];
|
||
|
v2 = rhs[prop];
|
||
|
ret = direction[i] * ((v1 < v2) ? -1 : ((v2 < v1) ? 1 : 0));
|
||
|
}
|
||
|
|
||
|
return ret;
|
||
|
};
|
||
|
},
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Callback scheduling
|
||
|
// <editor-fold>
|
||
|
|
||
|
/**
|
||
|
* This method can be called to force the delivery of any scheduled items. This is
|
||
|
* called automatically on a timer when items request service.
|
||
|
*
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
notify: function () {
|
||
|
var me = this,
|
||
|
timer = me.timer,
|
||
|
cyclesLeft = me.getCycleLimit(),
|
||
|
globalEvents = Ext.GlobalEvents,
|
||
|
busyCounter, i, item, len, queue, firedEvent;
|
||
|
|
||
|
if (timer) {
|
||
|
window.clearTimeout(timer);
|
||
|
me.timer = null;
|
||
|
}
|
||
|
|
||
|
//<debug>
|
||
|
if (me.firing) {
|
||
|
Ext.Error.raise('Notify cannot be called recursively');
|
||
|
}
|
||
|
//</debug>
|
||
|
|
||
|
while (me.scheduledCount) {
|
||
|
if (cyclesLeft) {
|
||
|
--cyclesLeft;
|
||
|
} else {
|
||
|
me.firing = null;
|
||
|
//<debug>
|
||
|
if (me.onCycleLimitExceeded) {
|
||
|
me.onCycleLimitExceeded();
|
||
|
}
|
||
|
//</debug>
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (!firedEvent) {
|
||
|
firedEvent = true;
|
||
|
if (globalEvents.hasListeners.beforebindnotify) {
|
||
|
globalEvents.fireEvent('beforebindnotify', me);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
++me.passes;
|
||
|
|
||
|
// We need to sort before we start firing because items can be added as we
|
||
|
// loop.
|
||
|
if (!(queue = me.orderedItems)) {
|
||
|
me.sort();
|
||
|
queue = me.orderedItems;
|
||
|
}
|
||
|
|
||
|
len = queue.length;
|
||
|
if (len) {
|
||
|
me.firing = me.items;
|
||
|
|
||
|
for (i = 0; i < len; ++i) {
|
||
|
item = queue[i];
|
||
|
|
||
|
if (item.scheduled) {
|
||
|
item.scheduled = false;
|
||
|
--me.scheduledCount;
|
||
|
me.notifyIndex = i;
|
||
|
|
||
|
//Ext.log('React: ' + item.getFullName());
|
||
|
// This sequence allows the reaction to schedule items further
|
||
|
// down the queue without a second pass but also to schedule an
|
||
|
// item that is "upstream" or even itself.
|
||
|
item.react();
|
||
|
|
||
|
if (!me.scheduledCount) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
me.firing = null;
|
||
|
me.notifyIndex = -1;
|
||
|
|
||
|
// The last thing we do is check for idle state transition (now that whatever
|
||
|
// else that was queued up has been dispatched):
|
||
|
if ((busyCounter = me.busyCounter) !== me.lastBusyCounter) {
|
||
|
if (!(me.lastBusyCounter = busyCounter)) {
|
||
|
// Since the counters are not equal, we were busy and are not anymore,
|
||
|
// so we can fire the idle event:
|
||
|
me.fireEvent('idle', me);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* The method called by the timer. This cleans up the state and calls `notify`.
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
onTick: function () {
|
||
|
this.timer = null;
|
||
|
this.notify();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Called to indicate that an item needs to be scheduled. This should not be called
|
||
|
* directly. Call the item's `{@link Ext.util.Schedulable#schedule schedule}` method
|
||
|
* instead.
|
||
|
* @param {Object} item
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
scheduleItem: function (item) {
|
||
|
var me = this;
|
||
|
|
||
|
++me.scheduledCount;
|
||
|
//Ext.log('Schedule: ' + item.getFullName());
|
||
|
|
||
|
if (!me.timer && !me.firing) {
|
||
|
me.scheduleTick();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* This method starts the timer that will execute the next `notify`.
|
||
|
* @param {Object} item
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
scheduleTick: function () {
|
||
|
var me = this;
|
||
|
|
||
|
if (!me.destroyed && !me.timer) {
|
||
|
me.timer = Ext.Function.defer(me.onTick, me.getTickDelay(), me);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Called to indicate that an item needs to be removed from the schedule. This should
|
||
|
* not be called directly. Call the item's `{@link Ext.util.Schedulable#unschedule unschedule}`
|
||
|
* method instead.
|
||
|
* @param {Object} item
|
||
|
* @private
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
unscheduleItem: function (item) {
|
||
|
if (this.scheduledCount) {
|
||
|
--this.scheduledCount;
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// </editor-fold>
|
||
|
|
||
|
//-------------------------------------------------------------------------
|
||
|
// Busy/Idle state tracking
|
||
|
// <editor-fold>
|
||
|
|
||
|
/**
|
||
|
* This method should be called when items become busy or idle. These changes are
|
||
|
* useful outside to do things like update modal masks or status indicators. The
|
||
|
* changes are delivered as `busy` and `idle` events.
|
||
|
*
|
||
|
* @param {Number} adjustment Should be `1` or `-1` only to indicate transition to
|
||
|
* busy state or from busy state, respectively.
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
adjustBusy: function (adjustment) {
|
||
|
var me = this,
|
||
|
busyCounter = me.busyCounter + adjustment;
|
||
|
|
||
|
me.busyCounter = busyCounter;
|
||
|
|
||
|
if (busyCounter) {
|
||
|
// If we are now busy but were not previously, fire the busy event immediately
|
||
|
// and update lastBusyCounter.
|
||
|
if (!me.lastBusyCounter) {
|
||
|
me.lastBusyCounter = busyCounter;
|
||
|
me.fireEvent('busy', me);
|
||
|
}
|
||
|
} else if (me.lastBusyCounter && !me.timer) {
|
||
|
// If we are now not busy but were previously, defer this to make sure that
|
||
|
// we don't quickly start with some other activity.
|
||
|
me.scheduleTick();
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns `true` if this object contains one or more busy items.
|
||
|
* @return {Boolean}
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
isBusy: function () {
|
||
|
return !this.isIdle();
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Returns `true` if this object contains no busy items.
|
||
|
* @return {Boolean}
|
||
|
* @since 5.0.0
|
||
|
*/
|
||
|
isIdle: function () {
|
||
|
return !(this.busyCounter + this.lastBusyCounter);
|
||
|
},
|
||
|
|
||
|
// </editor-fold>
|
||
|
|
||
|
debugHooks: {
|
||
|
$enabled: false, // Disable by default
|
||
|
|
||
|
onCycleLimitExceeded: function () {
|
||
|
Ext.Error.raise('Exceeded cycleLimit ' + this.getCycleLimit());
|
||
|
},
|
||
|
|
||
|
scheduleItem: function (item) {
|
||
|
if (!item) {
|
||
|
Ext.Error.raise('scheduleItem: Invalid argument');
|
||
|
}
|
||
|
Ext.log('Schedule item: ' + item.getFullName() + ' - ' + (this.scheduledCount+1));
|
||
|
if (item.order <= this.notifyIndex) {
|
||
|
Ext.log.warn('Suboptimal order: ' + item.order + ' < ' + this.notifyIndex);
|
||
|
}
|
||
|
this.callParent([item]);
|
||
|
},
|
||
|
|
||
|
unscheduleItem: function (item) {
|
||
|
if (!this.scheduledCount) {
|
||
|
Ext.Error.raise('Invalid scheduleCount');
|
||
|
}
|
||
|
this.callParent([item]);
|
||
|
Ext.log('Unschedule item: ' + item.getFullName() + ' - ' + this.scheduledCount);
|
||
|
}
|
||
|
}
|
||
|
});
|