Merge branch 'CendioOssman-delay' into master

Closes #668
This commit is contained in:
Samuel Mannehed 2016-10-24 14:43:28 +02:00
commit ab251ad9ea
4 changed files with 98 additions and 69 deletions

View File

@ -20,6 +20,7 @@
this._c_forceCanvas = false;
this._renderQ = []; // queue drawing actions for in-oder rendering
this._flushing = false;
// the full frame buffer (logical canvas) size
this._fb_width = 0;
@ -44,7 +45,8 @@
'colourMap': [],
'scale': 1.0,
'viewport': false,
'render_mode': ''
'render_mode': '',
"onFlush": function () {},
});
Util.Debug(">> Display.constructor");
@ -363,9 +365,21 @@
this._renderQ = [];
},
pending: function() {
return this._renderQ.length > 0;
},
flush: function() {
if (this._renderQ.length === 0) {
this._onFlush();
} else {
this._flushing = true;
}
},
fillRect: function (x, y, width, height, color, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this.renderQ_push({
this._renderQ_push({
'type': 'fill',
'x': x,
'y': y,
@ -381,7 +395,7 @@
copyImage: function (old_x, old_y, new_x, new_y, w, h, from_queue) {
if (this._renderQ.length !== 0 && !from_queue) {
this.renderQ_push({
this._renderQ_push({
'type': 'copy',
'old_x': old_x,
'old_y': old_y,
@ -400,6 +414,17 @@
}
},
imageRect: function(x, y, mime, arr) {
var img = new Image();
img.src = "data: " + mime + ";base64," + Base64.encode(arr);
this._renderQ_push({
'type': 'img',
'img': img,
'x': x,
'y': y
});
},
// start updating a tile
startTile: function (x, y, width, height, color) {
this._tile_x = x;
@ -480,7 +505,7 @@
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
this._renderQ_push({
'type': 'blit',
'data': new_arr,
'x': x,
@ -502,7 +527,7 @@
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 3);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
this._renderQ_push({
'type': 'blitRgb',
'data': new_arr,
'x': x,
@ -525,7 +550,7 @@
// this probably isn't getting called *nearly* as much
var new_arr = new Uint8Array(width * height * 4);
new_arr.set(new Uint8Array(arr.buffer, 0, new_arr.length));
this.renderQ_push({
this._renderQ_push({
'type': 'blitRgbx',
'data': new_arr,
'x': x,
@ -552,16 +577,6 @@
this._drawCtx.drawImage(img, x - this._viewportLoc.x, y - this._viewportLoc.y);
},
renderQ_push: function (action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will start polling the queue (every
// requestAnimationFrame interval)
this._scan_renderQ();
}
},
changeCursor: function (pixels, mask, hotx, hoty, w, h) {
if (this._cursor_uri === false) {
Util.Warn("changeCursor called but no cursor data URI support");
@ -741,6 +756,22 @@
this._drawCtx.putImageData(img, x - vx, y - vy);
},
_renderQ_push: function (action) {
this._renderQ.push(action);
if (this._renderQ.length === 1) {
// If this can be rendered immediately it will be, otherwise
// the scanner will wait for the relevant event
this._scan_renderQ();
}
},
_resume_renderQ: function() {
// "this" is the object that is ready, not the
// display object
this.removeEventListener('load', this._noVNC_display._resume_renderQ);
this._noVNC_display._scan_renderQ();
},
_scan_renderQ: function () {
var ready = true;
while (ready && this._renderQ.length > 0) {
@ -765,6 +796,8 @@
if (a.img.complete) {
this.drawImage(a.img, a.x, a.y);
} else {
a.img._noVNC_display = this;
a.img.addEventListener('load', this._resume_renderQ);
// We need to wait for this image to 'load'
// to keep things in-order
ready = false;
@ -777,8 +810,9 @@
}
}
if (this._renderQ.length > 0) {
requestAnimationFrame(this._scan_renderQ.bind(this));
if (this._renderQ.length === 0 && this._flushing) {
this._flushing = false;
this._onFlush();
}
},
};
@ -799,7 +833,9 @@
['render_mode', 'ro', 'str'], // Canvas rendering mode (read-only)
['prefer_js', 'rw', 'str'], // Prefer Javascript over canvas methods
['cursor_uri', 'rw', 'raw'] // Can we render cursor using data URI
['cursor_uri', 'rw', 'raw'], // Can we render cursor using data URI
['onFlush', 'rw', 'func'], // onFlush(): A flush request has finished
]);
// Class Methods

View File

@ -78,10 +78,10 @@
this._sock = null; // Websock object
this._display = null; // Display object
this._flushing = false; // Display flushing state
this._keyboard = null; // Keyboard input handler object
this._mouse = null; // Mouse input handler object
this._disconnTimer = null; // disconnection timer
this._msgTimer = null; // queued handle_msg timer
this._supportsFence = false;
@ -190,7 +190,8 @@
// NB: nothing that needs explicit teardown should be done
// before this point, since this can throw an exception
try {
this._display = new Display({target: this._target});
this._display = new Display({target: this._target,
onFlush: this._onFlush.bind(this)});
} catch (exc) {
Util.Error("Display exception: " + exc);
throw exc;
@ -415,11 +416,6 @@
},
_cleanup: function () {
if (this._msgTimer) {
clearInterval(this._msgTimer);
this._msgTimer = null;
}
if (this._display && this._display.get_context()) {
if (!this._view_only) { this._keyboard.ungrab(); }
if (!this._view_only) { this._mouse.ungrab(); }
@ -573,17 +569,15 @@
Util.Error("Got data while disconnected");
break;
case 'connected':
if (this._normal_msg() && this._sock.rQlen() > 0) {
// true means we can continue processing
// Give other events a chance to run
if (this._msgTimer === null) {
Util.Debug("More data to process, creating timer");
this._msgTimer = setTimeout(function () {
this._msgTimer = null;
this._handle_message();
}.bind(this), 0);
} else {
Util.Debug("More data to process, existing timer");
while (true) {
if (this._flushing) {
break;
}
if (!this._normal_msg()) {
break;
}
if (this._sock.rQlen() === 0) {
break;
}
}
break;
@ -1250,6 +1244,14 @@
}
},
_onFlush: function() {
this._flushing = false;
// Resume processing
if (this._sock.rQlen() > 0) {
this._handle_message();
}
},
_framebufferUpdate: function () {
var ret = true;
var now;
@ -1264,6 +1266,14 @@
now = (new Date()).getTime();
Util.Info("First FBU latency: " + (now - this._timing.fbu_rt_start));
}
// Make sure the previous frame is fully rendered first
// to avoid building up an excessive queue
if (this._display.pending()) {
this._flushing = true;
this._display.flush();
return false;
}
}
while (this._FBU.rects > 0) {
@ -1710,10 +1720,6 @@
return (new DES(passwd)).encrypt(challenge);
};
RFB.extract_data_uri = function (arr) {
return ";base64," + Base64.encode(arr);
};
RFB.encodingHandlers = {
RAW: function () {
if (this._FBU.lines === 0) {
@ -2216,16 +2222,8 @@
// We have everything, render it
this._sock.rQskipBytes(1 + cl_header); // shift off clt + compact length
var img = new Image();
img.src = "data: image/" + cmode +
RFB.extract_data_uri(this._sock.rQshiftBytes(cl_data));
this._display.renderQ_push({
'type': 'img',
'img': img,
'x': this._FBU.x,
'y': this._FBU.y
});
img = null;
data = this._sock.rQshiftBytes(cl_data);
this._display.imageRect(this._FBU.x, this._FBU.y, "image/" + cmode, data);
break;
case "filter":
var filterId = rQ[rQi + 1];

View File

@ -384,11 +384,6 @@ describe('Display/Canvas Helper', function () {
display = new Display({ target: document.createElement('canvas'), prefer_js: false });
display.resize(4, 4);
sinon.spy(display, '_scan_renderQ');
this.old_requestAnimationFrame = window.requestAnimationFrame;
window.requestAnimationFrame = function (cb) {
this.next_frame_cb = cb;
}.bind(this);
this.next_frame = function () { this.next_frame_cb(); };
});
afterEach(function () {
@ -396,18 +391,18 @@ describe('Display/Canvas Helper', function () {
});
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
display.renderQ_push({ type: 'noop' }); // does nothing
display._renderQ_push({ type: 'noop' }); // does nothing
expect(display._scan_renderQ).to.have.been.calledOnce;
});
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
display._renderQ.length = 2;
display.renderQ_push({ type: 'noop' });
display._renderQ_push({ type: 'noop' });
expect(display._scan_renderQ).to.not.have.been.called;
});
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
var img = { complete: false };
var img = { complete: false, addEventListener: sinon.spy() }
display._renderQ = [{ type: 'img', x: 3, y: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
@ -416,44 +411,46 @@ describe('Display/Canvas Helper', function () {
display._scan_renderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
expect(img.addEventListener).to.have.been.calledOnce;
display._renderQ[0].img.complete = true;
this.next_frame();
display._scan_renderQ();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce;
});
it('should draw a blit image on type "blit"', function () {
display.blitImage = sinon.spy();
display.renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
display._renderQ_push({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should draw a blit RGB image on type "blitRgb"', function () {
display.blitRgbImage = sinon.spy();
display.renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
display._renderQ_push({ type: 'blitRgb', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitRgbImage).to.have.been.calledOnce;
expect(display.blitRgbImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display.renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
display._renderQ_push({ type: 'copy', x: 3, y: 4, width: 5, height: 6, old_x: 7, old_y: 8 });
expect(display.copyImage).to.have.been.calledOnce;
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
});
it('should fill a rect with a given color on type "fill"', function () {
display.fillRect = sinon.spy();
display.renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
display._renderQ_push({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
expect(display.fillRect).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
});
it('should draw an image from an image object on type "img" (if complete)', function () {
display.drawImage = sinon.spy();
display.renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
display._renderQ_push({ type: 'img', x: 3, y: 4, img: { complete: true } });
expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith({ complete: true }, 3, 4);
});

View File

@ -1274,7 +1274,7 @@ describe('Remote Frame Buffer Protocol Client', function() {
client._sock._websocket._receive_data(new Uint8Array([0, 0, 0, 3]));
expect(client._sock._websocket._get_sent_data()).to.have.length(0);
client._framebufferUpdate = function () { return true; }; // we magically have enough data
client._framebufferUpdate = function () { this._sock.rQskip8(); return true; }; // we magically have enough data
// 247 should *not* be used as the message type here
client._sock._websocket._receive_data(new Uint8Array([247]));
expect(client._sock).to.have.sent(expected_msg._sQ);
@ -2080,14 +2080,12 @@ describe('Remote Frame Buffer Protocol Client', function() {
expect(client._init_msg).to.have.been.calledOnce;
});
it('should split up the handling of muplitle normal messages across 10ms intervals', function () {
it('should process all normal messages directly', function () {
client.connect('host', 8675);
client._sock._websocket._open();
client._rfb_connection_state = 'connected';
client.set_onBell(sinon.spy());
client._sock._websocket._receive_data(new Uint8Array([0x02, 0x02]));
expect(client.get_onBell()).to.have.been.calledOnce;
this.clock.tick(20);
expect(client.get_onBell()).to.have.been.calledTwice;
});