Add write buffer pause and refresh frame skip

This commit is contained in:
Daniel Imms
2017-01-03 15:26:41 -08:00
parent 6b8c43ed7f
commit e66b1c5704
+69 -35
View File
@@ -37,6 +37,19 @@ var document = (typeof window != 'undefined') ? window.document : null;
*/ */
var normal = 0, escaped = 1, csi = 2, osc = 3, charset = 4, dcs = 5, ignore = 6; var normal = 0, escaped = 1, csi = 2, osc = 3, charset = 4, dcs = 5, ignore = 6;
/**
* The amount of write requests to queue before sending an XOFF signal to the
* pty process. This number must be small in order for ^C and similar sequences
* to be responsive.
*/
var WRITE_BUFFER_PAUSE_THRESHOLD = 0;
/**
* The maximum number of refresh frames to skip when the write buffer is non-
* empty.
*/
var MAX_REFRESH_FRAME_SKIP = 6;
/** /**
* Terminal * Terminal
*/ */
@@ -199,7 +212,18 @@ function Terminal(options) {
// user input states // user input states
this.writeBuffer = []; this.writeBuffer = [];
this.writeInProgress = false; this.writeInProgress = false;
this.user_xoff = false; // user pressed XOFF this.refreshFramesSkipped = 0;
/**
* Whether _xterm.js_ sent XOFF in order to catch up with the pty process.
* This is a distinct state from writeStopped so that if the user requested
* XOFF via ^S that it will not automatically resume when the writeBuffer goes
* below threshold.
*/
this.xoffSentToCatchUp = false;
/** Whether writing has been stopped as a result of XOFF */
this.writeStopped = false;
// leftover surrogate high from previous write invocation // leftover surrogate high from previous write invocation
this.surrogate_high = ''; this.surrogate_high = '';
@@ -1014,27 +1038,36 @@ Terminal.prototype.queueRefresh = function(start, end) {
Terminal.prototype.refreshLoop = function() { Terminal.prototype.refreshLoop = function() {
// Don't refresh if there were no row changes // Don't refresh if there were no row changes
if (this.refreshRowsQueue.length > 0) { if (this.refreshRowsQueue.length > 0) {
var start; // Skip MAX_REFRESH_FRAME_SKIP frames if the writeBuffer is non-empty as it
var end; // will need to be immediately refreshed anyway. This saves a lot of
if (this.refreshRowsQueue.length > 4) { // rendering time as the viewport DOM does not need to be refreshed, no
// Just do a full refresh when 5+ refreshes are queued // scroll events, no layouts, etc.
start = 0; var skipFrame = this.writeBuffer.length > 0 && this.refreshFramesSkipped++ <= MAX_REFRESH_FRAME_SKIP;
end = this.rows - 1;
} else { if (!skipFrame) {
// Get start and end rows that need refreshing this.refreshFramesSkipped = 0;
start = this.refreshRowsQueue[0].start; var start;
end = this.refreshRowsQueue[0].end; var end;
for (var i = 1; i < this.refreshRowsQueue.length; i++) { if (this.refreshRowsQueue.length > 4) {
if (this.refreshRowsQueue[i].start < start) { // Just do a full refresh when 5+ refreshes are queued
start = this.refreshRowsQueue[i].start; start = 0;
} end = this.rows - 1;
if (this.refreshRowsQueue[i].end > end) { } else {
end = this.refreshRowsQueue[i].end; // Get start and end rows that need refreshing
start = this.refreshRowsQueue[0].start;
end = this.refreshRowsQueue[0].end;
for (var i = 1; i < this.refreshRowsQueue.length; i++) {
if (this.refreshRowsQueue[i].start < start) {
start = this.refreshRowsQueue[i].start;
}
if (this.refreshRowsQueue[i].end > end) {
end = this.refreshRowsQueue[i].end;
}
} }
} }
this.refreshRowsQueue = [];
this.refresh(start, end);
} }
this.refreshRowsQueue = [];
this.refresh(start, end);
} }
window.requestAnimationFrame(this.refreshLoop.bind(this)); window.requestAnimationFrame(this.refreshLoop.bind(this));
} }
@@ -1348,11 +1381,14 @@ Terminal.prototype.scrollToBottom = function() {
Terminal.prototype.write = function(data) { Terminal.prototype.write = function(data) {
this.writeBuffer.push(data); this.writeBuffer.push(data);
// Pause pty process if the write buffer becomes too large so xterm.js can catch up // Send XOFF to pause the pty process if the write buffer becomes too large so
if (this.writeBuffer.length > 1000 && !this.user_xoff) { // xterm.js can catch up before more data is sent. This is necessary in order
// to keep signals such as ^C responsive.
if (!this.xoffSentToCatchUp && this.writeBuffer.length > WRITE_BUFFER_PAUSE_THRESHOLD) {
// XOFF - stop pty pipe // XOFF - stop pty pipe
// XON will be triggered by emulator before processing data chunk // XON will be triggered by emulator before processing data chunk
this.send('\x13'); this.send('\x13');
this.xoffSentToCatchUp = true;
} }
if (!this.writeInProgress && this.writeBuffer.length > 0) { if (!this.writeInProgress && this.writeBuffer.length > 0) {
@@ -1368,17 +1404,12 @@ Terminal.prototype.write = function(data) {
Terminal.prototype.innerWrite = function(data) { Terminal.prototype.innerWrite = function(data) {
var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row; var l = data.length, i = 0, j, cs, ch, code, low, ch_width, row;
console.log('writeBuffer length: ' + this.writeBuffer.length);
// TODO: Need to have another buffer where data is held where write can grab lines from
// When this hits a certain threshold it should send this.write('\x13')
// XON - about to process data, thus we can get more // If XOFF was sent in order to catch up with the pty process, resume it if
// dont lift XOFF if user pressed it // the writeBuffer is empty to allow more data to come in.
if (!this.user_xoff) { if (this.xoffSentToCatchUp && this.writeBuffer.length === 0) {
// Resume pty process to get more data this.send('\x11');
if (this.writeBuffer.length < 200) { this.xoffSentToCatchUp = false;
this.send('\x11');
}
} }
this.refreshStart = this.y; this.refreshStart = this.y;
@@ -2421,10 +2452,13 @@ console.log('writeBuffer length: ' + this.writeBuffer.length);
if (this.writeBuffer.length > 0) { if (this.writeBuffer.length > 0) {
var self = this; var self = this;
// TODO: async makes this too slow, need to change to iterative to prevent potential stack overflow
// Start a new async innerWrite to prevent a stack overflow // Start a new async innerWrite to prevent a stack overflow
setTimeout(function () { //setTimeout(function () {
self.innerWrite(self.writeBuffer.shift()); self.innerWrite(self.writeBuffer.shift());
}); //});
} else { } else {
this.writeInProgress = false; this.writeInProgress = false;
} }
@@ -2472,9 +2506,9 @@ Terminal.prototype.keyDown = function(ev) {
var result = this.evaluateKeyEscapeSequence(ev); var result = this.evaluateKeyEscapeSequence(ev);
if (result.key === '\x13') { // XOFF if (result.key === '\x13') { // XOFF
this.user_xoff = true; this.writeStopped = true;
} else if (result.key === '\x11') { // XON } else if (result.key === '\x11') { // XON
this.user_xoff = false; this.writeStopped = false;
} }
if (result.scrollDisp) { if (result.scrollDisp) {