111 lines
3.8 KiB
TypeScript
111 lines
3.8 KiB
TypeScript
|
|
/**
|
|
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
|
* @license MIT
|
|
*/
|
|
|
|
declare const setTimeout: (handler: () => void, timeout?: number) => void;
|
|
|
|
/**
|
|
* Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
|
|
* Enable flow control to avoid this limit and make sure that your backend correctly
|
|
* propagates this to the underlying pty. (see docs for further instructions)
|
|
* Since this limit is meant as a safety parachute to prevent browser crashs,
|
|
* it is set to a very high number. Typically xterm.js gets unresponsive with
|
|
* a 100 times lower number (>500 kB).
|
|
*/
|
|
const DISCARD_WATERMARK = 50000000; // ~50 MB
|
|
|
|
/**
|
|
* The max number of ms to spend on writes before allowing the renderer to
|
|
* catch up with a 0ms setTimeout. A value of < 33 to keep us close to
|
|
* 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
|
|
* depends on the time it takes for the renderer to draw the frame.
|
|
*/
|
|
const WRITE_TIMEOUT_MS = 12;
|
|
|
|
/**
|
|
* Threshold of max held chunks in the write buffer, that were already processed.
|
|
* This is a tradeoff between extensive write buffer shifts (bad runtime) and high
|
|
* memory consumption by data thats not used anymore.
|
|
*/
|
|
const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
|
|
|
|
export class WriteBuffer {
|
|
private _writeBuffer: (string | Uint8Array)[] = [];
|
|
private _callbacks: ((() => void) | undefined)[] = [];
|
|
private _pendingData = 0;
|
|
private _bufferOffset = 0;
|
|
|
|
constructor(private _action: (data: string | Uint8Array) => void) { }
|
|
|
|
public writeSync(data: string | Uint8Array): void {
|
|
// force sync processing on pending data chunks to avoid in-band data scrambling
|
|
// does the same as innerWrite but without event loop
|
|
if (this._writeBuffer.length) {
|
|
for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
|
|
const data = this._writeBuffer[i];
|
|
const cb = this._callbacks[i];
|
|
this._action(data);
|
|
if (cb) cb();
|
|
}
|
|
// reset all to avoid reprocessing of chunks with scheduled innerWrite call
|
|
this._writeBuffer = [];
|
|
this._callbacks = [];
|
|
this._pendingData = 0;
|
|
// stop scheduled innerWrite by offset > length condition
|
|
this._bufferOffset = 0x7FFFFFFF;
|
|
}
|
|
// handle current data chunk
|
|
this._action(data);
|
|
}
|
|
|
|
public write(data: string | Uint8Array, callback?: () => void): void {
|
|
if (this._pendingData > DISCARD_WATERMARK) {
|
|
throw new Error('write data discarded, use flow control to avoid losing data');
|
|
}
|
|
|
|
// schedule chunk processing for next event loop run
|
|
if (!this._writeBuffer.length) {
|
|
this._bufferOffset = 0;
|
|
setTimeout(() => this._innerWrite());
|
|
}
|
|
|
|
this._pendingData += data.length;
|
|
this._writeBuffer.push(data);
|
|
this._callbacks.push(callback);
|
|
}
|
|
|
|
protected _innerWrite(): void {
|
|
const startTime = Date.now();
|
|
while (this._writeBuffer.length > this._bufferOffset) {
|
|
const data = this._writeBuffer[this._bufferOffset];
|
|
const cb = this._callbacks[this._bufferOffset];
|
|
this._bufferOffset++;
|
|
|
|
this._action(data);
|
|
this._pendingData -= data.length;
|
|
if (cb) cb();
|
|
|
|
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
|
|
break;
|
|
}
|
|
}
|
|
if (this._writeBuffer.length > this._bufferOffset) {
|
|
// Allow renderer to catch up before processing the next batch
|
|
// trim already processed chunks if we are above threshold
|
|
if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
|
|
this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
|
|
this._callbacks = this._callbacks.slice(this._bufferOffset);
|
|
this._bufferOffset = 0;
|
|
}
|
|
setTimeout(() => this._innerWrite(), 0);
|
|
} else {
|
|
this._writeBuffer = [];
|
|
this._callbacks = [];
|
|
this._pendingData = 0;
|
|
this._bufferOffset = 0;
|
|
}
|
|
}
|
|
}
|