javascript timing issues
Im working on processing data. I setup a worker function thats called every 5ms by setInterval
. I'm calculating the amount of time it takes the function to run, its 0 to 1ms. I'm also calculating the speed at which the function is actually invoked and its about 80ms.
My question is, if it takes only 1ms to run th开发者_运维百科rough the function, and I'm calling it every 5ms, why is it invoked every 80ms?
I do have a second worker function updating the canvas based on the current data the processing function is doing, but its run in its own setInterval.
These numbers were taken while in Chrome. I've tested in Opera, Safari, FF3 and FF4 as well and the issue is the same, though the invoke time is different. (all this on a mac)
I know this code does not yet work in IE. I know parts of this are ugly.. I haven't started to clean up the code yet... Lines 77 to 84 are the setInterval
calls.
You can see a working example here.
(function ($) {
var methods = {
init: function (options) {
return this.each(function () {
var $this = $(this);
$this.addClass('ansiScreen');
var data = $this.data('ansi');
if (!data) {
data = new Object();
data.target = $this;
data.fontheight = 16; //12, 16, 22
data.fontwidth = Math.round(data.fontheight * 0.6) - 1;
data.canvas = $('<canvas width="' + (data.fontwidth * 80) + 'px" height="' + (data.fontheight * 25) + 'px">');
$this.append(data.canvas);
data.ctx = data.canvas[0].getContext('2d');
data.ctx.font = data.fontheight + 'px Courier New';
data.processInterval = null;
data.screenInterval = null;
data.ansi = null;
data.ansiCode = null;
data.ansiPos = 0;
data.fgcolor = 'rgb(170, 170, 170)';
data.bgcolor = 'rgb(0, 0, 0)';
data.bold = false;
data.blink = false;
data.pos = [0, 0];
data.savepos = [0, 0];
data.screen = Array();
data.last = 0;
for (var i = 0; i < 25; i++) {
data.screen.push(Array());
for (var j = 0; j < 80; j++) {
data.screen[i].push([data.bgcolor, data.fgcolor, ' ']);
}
}
$this.data('ansi', data);
}
});
},
destroy: function () {
return this.each(function () {
var $this = $(this);
var data = $this.data('ansi');
// Clean up
$(window).unbind('.ansi');
data.tooltip.remove();
$this.removeData('ansi');
});
},
load: function (ansiUrl) {
return this.each(function () {
var $this = $(this);
var me = this;
$.ajax({
'url': ansiUrl,
'data': 'text',
beforeSend: function (jqXHR, settings) {
jqXHR.overrideMimeType('text/plain; charset=x-user-defined');
},
success: function(ansiData) {
var data = $this.data('ansi');
if (data.processInterval != null) {
clearInterval(data.processInterval);
clearInterval(data.screenInterval);
}
data.ansi = ansiData;
data.ansiPos = 0;
data.fgcolor = 'rgb(170, 170, 170)';
data.bgcolor = 'rgb(0, 0, 0)';
data.bold = false;
data.blink = false;
data.pos = [0, 0];
data.savepos = [0, 0];
var interval = setInterval(function () {
processAnsi.call(me);
}, 5);
data.processInterval = interval;
interval = setInterval(function () {
updateDisplay.call(me);
}, 30);
data.screenInterval = interval;
$this.data('ansi', data);
updateDisplay.call(me);
}
});
});
}
};
$.fn.ansi = function (method) {
// Method calling logic
if (methods[method]) {
return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
} else if (typeof method === 'object' || !method) {
return methods.init.apply(this, arguments);
} else {
$.error('Method ' + method + ' does not exist on jQuery.ansi');
}
};
// Process a byte from teh ansi data
function processAnsi() {
var start = new Date().getTime();
var $this = $(this);
var data = $this.data('ansi');
if (data.ansiPos > data.ansi.length)
{
clearInterval(data.processInterval);
clearInterval(data.screenInterval);
return;
}
var code = data.ansi.charCodeAt(data.ansiPos) & 0xff;
var char = data.ansi[data.ansiPos];
data.ansiPos += 1;
var now = new Date().getTime();
$this.data('ansi', data);
if (code < 33 || code > 126)
{
switch (code)
{
case 0: char = ''; break;
case 10: char = ''; cursorStartOfLine.call(this); break;
case 13: char = ''; cursorDown.call(this, 1); break;
case 27: char = ansiCode.call(this); break;
case 32: char = ' '; break;
case 176: char = '\u2591'; break;
case 177: char = '\u2592'; break;
case 178: char = '\u2593'; break;
case 179: char = '\u2502'; break;
case 185: char = '\u2563'; break;
case 186: char = '\u2551'; break;
case 187: char = '\u2557'; break;
case 188: char = '\u255D'; break;
case 191: char = '\u2510'; break;
case 192: char = '\u2514'; break;
case 193: char = '\u2534'; break;
case 194: char = '\u252C'; break;
case 195: char = '\u251C'; break;
case 196: char = '\u2500'; break;
case 197: char = '\u253C'; break;
case 180: char = '\u2524'; break;
case 200: char = '\u255A'; break;
case 201: char = '\u2554'; break;
case 204: char = '\u2560'; break;
case 205: char = '\u2550'; break;
case 215: char = '\u256B'; break;
case 217: char = '\u2518'; break;
case 218: char = '\u250C'; break;
case 219: char = '\u2588'; break;
case 220: char = '\u2584'; break;
case 221: char = '\u258C'; break;
case 222: char = '\u2590'; break;
case 223: char = '\u2580'; break;
case 250: char = '\u2022'; break;
case 254: char = '\u25a0'; break;
default: char = ''; var s = String.fromCharCode(code); break;
}
}
if (char === undefined) {
char = '[' + char + ']';
}
if (char != '') {
putCharacter.call(this, char);
}
var end = new Date().getTime();
$('#processTime').html('Requested Speed: 5ms, Process Speed: ' + (now-data.last) + 'ms, Process Time: ' + (end-start) + 'ms, Process Position: ' + data.ansiPos + ' of ' + data.ansi.length);
data = $this.data('ansi');
data.last = now;
$this.data('ansi', data);
}
function ansiCode() {
var $this = $(this);
var data = $this.data('ansi');
if (data.ansiPos > data.ansi.length)
return '-';
var valid = /^[0-9;HABCDRsuJKmh]$/;
var end = /^[HABCDRsuJKmh]$/;
var char = data.ansi[data.ansiPos];
var escape = '';
if (char == '[')
{
var stop = false;
do {
data.ansiPos += 1;
var char = data.ansi[data.ansiPos];
escape += char;
stop = end.test(char);
} while (valid.test(char) && !stop)
data.ansiPos += 1;
}
switch(escape[escape.length - 1])
{
case 'J':
if (escape == '2J') {
clearDisplay.call(this);
}
break;
case 'A':
var lines = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(lines)) { lines = 1; }
data.pos[1] -= lines;
if (data.pos[1] < 0) { data.pos[1] = 0; }
break;
case 'B':
var lines = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(lines)) { lines = 1; }
cursorDown.call(this, lines);
break;
case 'C':
var spaces = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(spaces)) { spaces = 1; }
cursorForward.call(this, spaces);
break;
case 'D':
var lines = parseInt(escape.substring(0, escape.length - 1));
if (isNaN(lines)) { lines = 1; }
data.pos[0] -= lines;
if (data.pos[0] < 0) { data.pos[0] = 0; }
break;
case 'H':
var codes = escape.substring(0, escape.length - 1).split(';');
if (isNaN(codes[0])) { codes[0] = 1; }
if (isNaN(codes[1])) { codes[1] = 1; }
data.pos[0] = codes[1] - 1;
data.pos[1] = codes[0] - 1;
break;
case 's':
data.savepos[0] = data.pos[0];
data.savepos[1] = data.pos[1];
break;
case 'u':
data.pos[0] = data.savepos[0];
data.pos[1] = data.savepos[1];
break;
case 'm':
var codes = escape.substring(0, escape.length - 1).split(';');
for (var i=0; i < codes.length; i++) {
var code = codes[i];
switch (code) {
case '0':
data.bold = false;
data.blink = false;
data.fgcolor = 'rgb(170, 170, 170)';
data.bgcolor = 'rgb(0, 0, 0)';
break;
case '1':
data.bold = true;
break;
case '5':
data.blink = true;
break;
case '30':
data.fgcolor = !data.bold ? 'rgb(0, 0, 0)' : 'rgb(85, 85, 85)';
break;
case '31':
data.fgcolor = !data.bold ? 'rgb(170, 0, 0)' : 'rgb(255, 85, 85)';
break;
case '32':
data.fgcolor = !data.bold ? 'rgb(0, 170, 0)' : 'rgb(85, 255, 85)';
break;
case '33':
data.fgcolor = !data.bold ? 'rgb(170, 85, 0)' : 'rgb(255, 255, 0)';
break;
case '34':
data.fgcolor = !data.bold ? 'rgb(0, 0, 170)' : 'rgb(85, 85, 255)';
break;
case '35':
data.fgcolor = !data.bold ? 'rgb(170, 0, 170)' : 'rgb(255, 85, 255)';
break;
case '36':
data.fgcolor = !data.bold ? 'rgb(0, 170, 170)' : 'rgb(85, 255, 255)';
break;
case '37':
data.fgcolor = !data.bold ? 'rgb(170, 170, 170)' : 'rgb(255, 255, 255)';
break;
case '40':
data.bgcolor = !data.bold ? 'rgb(0, 0, 0)' : 'rgb(85, 85, 85)';
break;
case '41':
data.bgcolor = !data.bold ? 'rgb(170, 0, 0)' : 'rgb(255, 85, 85)';
break;
case '42':
data.bgcolor = !data.bold ? 'rgb(0, 170, 0)' : 'rgb(85, 255, 85)';
break;
case '43':
data.bgcolor = !data.bold ? 'rgb(170, 85, 0)' : 'rgb(255, 255, 0)';
break;
case '44':
data.bgcolor = !data.bold ? 'rgb(0, 0, 170)' : 'rgb(85, 85, 255)';
break;
case '45':
data.bgcolor = !data.bold ? 'rgb(170, 0, 170)' : 'rgb(255, 85, 255)';
break;
case '46':
data.bgcolor = !data.bold ? 'rgb(0, 170, 170)' : 'rgb(85, 255, 255)';
break;
case '47':
data.bgcolor = !data.bold ? 'rgb(170, 170, 170)' : 'rgb(255, 255, 255)';
break;
default:
$('#debug').html($('#debug').html() + '<br>Unknown Attribute: ' + code);
break;
}
}
break;
default:
$('#debug').html($('#debug').html() + '<br>' + escape);
break;
}
$this.data('ansi', data);
return '';
}
// Move the cursor position up a number of lines
function cursorStartOfLine(lines) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[0] = 0;
$this.data('ansi', data);
}
// Move the cursor position up a number of lines
function cursorDown(lines) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[1] += lines;
if (data.pos[1] > data.screen.length - 1) {
data.pos[1] = data.screen.length - 1;
for (var i = 0, length1 = data.screen.length - 1; i < length1; ++i) {
var a = data.screen[i]; // cache object
var b = data.screen[i+1]; // cache object
for (var j = 0, length2 = a.length; j < length2; ++j) {
a[j] = b[j];
}
}
for (var j = 0, length2 = a.length; j < length2; ++j) {
data.screen[data.screen.length-1][j] = ['#000000', '#ffffff', ' '];
}
}
$this.data('ansi', data);
}
// Move the cursor position back a number of columns
function cursorBack(cols) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[0] -= cols;
if (data.pos[0] < 0) { data.pos[0] = 0; }
$this.data('ansi', data);
}
// Move the cursor position forward a number of columns
function cursorForward(cols) {
var $this = $(this);
var data = $this.data('ansi');
data.pos[0] += cols;
if (data.pos[0] > data.screen[0].length - 1) {
//data.pos[0] = data.screen[0].length - 1;
data.pos[0] = 0;
cursorDown.call(this,1);
}
$this.data('ansi', data);
}
// Puts a character on screen
function putCharacter(character) {
var $this = $(this);
var data = $this.data('ansi');
var style = 'background-color:' + data.bgcolor+';';
style += 'color:' + data.fgcolor+';';
data.screen[data.pos[1]][data.pos[0]] = [data.bgcolor, data.fgcolor, character, data.blink];
$this.data('ansi', data);
// Move forward 1 character
cursorForward.call(this, 1);
}
// Clear the screen
function clearDisplay() {
var $this = $(this);
var data = $this.data('ansi');
for (var i = 0; i < data.screen.length; i++) {
for (var j = 0; j < data.screen[i].length; j++) {
data.screen[i][j] = [data.bgcolor, data.fgcolor, ' ', data.blink];
}
}
data.pos = [0, 0];
$this.data('ansi', data);
}
// Update the container with the current screen
function updateDisplay() {
var start = new Date().getTime();
var $this = $(this);
var data = $this.data('ansi');
for (var i = 0, length1 = data.screen.length; i < length1; ++i) {
var a = data.screen[i]; // cache object
for (var j = 0, length2 = a.length; j < length2; ++j) {
data.ctx.fillStyle = a[j][0];
data.ctx.fillRect (data.fontwidth * j, data.fontheight * i, data.fontwidth, data.fontheight);
data.ctx.fillStyle = a[j][1];
data.ctx.textBaseline = "top";
data.ctx.fillText(a[j][2], data.fontwidth * j, data.fontheight * i);
}
}
var end = new Date().getTime();
$('#frameTime').html('Frame Draw Time: ' + (end-start));
}
})(jQuery);
Also remember that different browsers have different minimal interval. Setting the interval to 5ms may trigger some browsers minimum interval check. Additionally, the more timers you have running the longer it's going to take for the browser to get back to the begining of the queue
Javascript is single threaded, so the 2nd function you're calling with setInterval delays the 1st.
精彩评论