/**
* @private
*/
Ext.define('Ext.AnimationQueue', {
singleton: true,
constructor: function() {
this.queue = [];
this.taskQueue = [];
this.runningQueue = [];
this.idleQueue = [];
this.isRunning = false;
this.isIdle = true;
this.run = Ext.Function.bind(this.run, this);
// iOS has a nasty bug which causes pending requestAnimationFrame to not release
// the callback when the WebView is switched back and forth from / to being background process
// We use a watchdog timer to workaround this, and restore the pending state correctly if this happens
// This timer has to be set as an interval from the very beginning and we have to keep it running for
// as long as the app lives, setting it later doesn't seem to work
if (Ext.os.is.iOS) {
Ext.interval(this.watch, 500, this);
}
},
/**
*
* @param {Function} fn
* @param {Object} [scope]
* @param {Object} [args]
*/
start: function(fn, scope, args) {
this.queue.push(arguments);
if (!this.isRunning) {
if (this.hasOwnProperty('idleTimer')) {
clearTimeout(this.idleTimer);
delete this.idleTimer;
}
if (this.hasOwnProperty('idleQueueTimer')) {
clearTimeout(this.idleQueueTimer);
delete this.idleQueueTimer;
}
this.isIdle = false;
this.isRunning = true;
//
this.startCountTime = Ext.now();
this.count = 0;
//
this.doStart();
}
},
watch: function() {
if (this.isRunning && Ext.now() - this.lastRunTime >= 500) {
this.run();
}
},
run: function() {
if (!this.isRunning) {
return;
}
var queue = this.runningQueue,
i, ln;
this.lastRunTime = Ext.now();
this.frameStartTime = Ext.now();
queue.push.apply(queue, this.queue);
for (i = 0, ln = queue.length; i < ln; i++) {
this.invoke(queue[i]);
}
queue.length = 0;
//
var now = this.frameStartTime,
startCountTime = this.startCountTime,
elapse = now - startCountTime,
count = ++this.count;
if (elapse >= 200) {
this.onFpsChanged(count * 1000 / elapse, count, elapse);
this.startCountTime = now;
this.count = 0;
}
//
this.doIterate();
},
//
onFpsChanged: Ext.emptyFn,
onStop: Ext.emptyFn,
//
doStart: function() {
this.animationFrameId = Ext.Function.requestAnimationFrame(this.run);
this.lastRunTime = Ext.now();
},
doIterate: function() {
this.animationFrameId = Ext.Function.requestAnimationFrame(this.run);
},
doStop: function() {
Ext.Function.cancelAnimationFrame(this.animationFrameId);
},
/**
*
* @param {Function} fn
* @param {Object} [scope]
* @param {Object} [args]
*/
stop: function(fn, scope, args) {
if (!this.isRunning) {
return;
}
var queue = this.queue,
ln = queue.length,
i, item;
for (i = 0; i < ln; i++) {
item = queue[i];
if (item[0] === fn && item[1] === scope && item[2] === args) {
queue.splice(i, 1);
i--;
ln--;
}
}
if (ln === 0) {
this.doStop();
//
this.onStop();
//
this.isRunning = false;
this.idleTimer = Ext.defer(this.whenIdle, 100, this);
}
},
onIdle: function(fn, scope, args) {
var listeners = this.idleQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
return;
}
}
listeners.push(arguments);
if (this.isIdle) {
this.processIdleQueue();
}
},
unIdle: function(fn, scope, args) {
var listeners = this.idleQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
listeners.splice(i, 1);
return true;
}
}
return false;
},
queueTask: function(fn, scope, args) {
this.taskQueue.push(arguments);
this.processTaskQueue();
},
dequeueTask: function(fn, scope, args) {
var listeners = this.taskQueue,
i, ln, listener;
for (i = 0, ln = listeners.length; i < ln; i++) {
listener = listeners[i];
if (fn === listener[0] && scope === listener[1] && args === listener[2]) {
listeners.splice(i, 1);
i--;
ln--;
}
}
},
invoke: function(listener) {
var fn = listener[0],
scope = listener[1],
args = listener[2];
fn = (typeof fn == 'string' ? scope[fn] : fn);
if (Ext.isArray(args)) {
fn.apply(scope, args);
}
else {
fn.call(scope, args);
}
},
whenIdle: function() {
this.isIdle = true;
this.processIdleQueue();
},
processIdleQueue: function() {
if (!this.hasOwnProperty('idleQueueTimer')) {
this.idleQueueTimer = Ext.defer(this.processIdleQueueItem, 1, this);
}
},
processIdleQueueItem: function() {
delete this.idleQueueTimer;
if (!this.isIdle) {
return;
}
var listeners = this.idleQueue,
listener;
if (listeners.length > 0) {
listener = listeners.shift();
this.invoke(listener);
this.processIdleQueue();
}
},
processTaskQueue: function() {
if (!this.hasOwnProperty('taskQueueTimer')) {
this.taskQueueTimer = Ext.defer(this.processTaskQueueItem, 15, this);
}
},
processTaskQueueItem: function() {
delete this.taskQueueTimer;
var listeners = this.taskQueue,
listener;
if (listeners.length > 0) {
listener = listeners.shift();
this.invoke(listener);
this.processTaskQueue();
}
},
showFps: function() {
Ext.onInternalReady(function() {
Ext.Viewport.add([{
xtype: 'component',
bottom: 50,
left: 0,
width: 50,
height: 20,
html: 'Average',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__averageFps',
xtype: 'component',
bottom: 0,
left: 0,
width: 50,
height: 50,
html: '0',
style: 'background-color: red; color: white; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 50,
width: 50,
height: 20,
html: 'Min (Last 1k)',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__minFps',
xtype: 'component',
bottom: 0,
left: 50,
width: 50,
height: 50,
html: '0',
style: 'background-color: orange; color: white; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 100,
width: 50,
height: 20,
html: 'Max (Last 1k)',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__maxFps',
xtype: 'component',
bottom: 0,
left: 100,
width: 50,
height: 50,
html: '0',
style: 'background-color: yellow; color: black; text-align: center; line-height: 50px;'
},
{
xtype: 'component',
bottom: 50,
left: 150,
width: 50,
height: 20,
html: 'Current',
style: 'background-color: black; color: white; text-align: center; line-height: 20px; font-size: 8px;'
},
{
id: '__currentFps',
xtype: 'component',
bottom: 0,
left: 150,
width: 50,
height: 50,
html: '0',
style: 'background-color: green; color: white; text-align: center; line-height: 50px;'
}
]);
Ext.AnimationQueue.resetFps();
});
},
resetFps: function() {
var currentFps = Ext.getCmp('__currentFps'),
averageFps = Ext.getCmp('__averageFps'),
minFps = Ext.getCmp('__minFps'),
maxFps = Ext.getCmp('__maxFps'),
min = 1000,
max = 0,
count = 0,
sum = 0;
Ext.AnimationQueue.onFpsChanged = function(fps) {
count++;
if (!(count % 10)) {
min = 1000;
max = 0;
}
sum += fps;
min = Math.min(min, fps);
max = Math.max(max, fps);
currentFps.setHtml(Math.round(fps));
averageFps.setHtml(Math.round(sum / count));
minFps.setHtml(Math.round(min));
maxFps.setHtml(Math.round(max));
};
}
}, function() {
/*
Global FPS indicator. Add ?showfps to use in any application. Note that this REQUIRES true requestAnimationFrame
to be accurate.
*/
//
var paramsString = window.location.search.substr(1),
paramsArray = paramsString.split("&");
if (Ext.Array.contains(paramsArray, "showfps")) {
Ext.AnimationQueue.showFps();
}
//
});