xterm
[VSoRC/.git] / node_modules / xterm / src / common / input / WriteBuffer.ts
1
2 /**
3  * Copyright (c) 2019 The xterm.js authors. All rights reserved.
4  * @license MIT
5  */
6
7 declare const setTimeout: (handler: () => void, timeout?: number) => void;
8
9 /**
10  * Safety watermark to avoid memory exhaustion and browser engine crash on fast data input.
11  * Enable flow control to avoid this limit and make sure that your backend correctly
12  * propagates this to the underlying pty. (see docs for further instructions)
13  * Since this limit is meant as a safety parachute to prevent browser crashs,
14  * it is set to a very high number. Typically xterm.js gets unresponsive with
15  * a 100 times lower number (>500 kB).
16  */
17 const DISCARD_WATERMARK = 50000000; // ~50 MB
18
19 /**
20  * The max number of ms to spend on writes before allowing the renderer to
21  * catch up with a 0ms setTimeout. A value of < 33 to keep us close to
22  * 30fps, and a value of < 16 to try to run at 60fps. Of course, the real FPS
23  * depends on the time it takes for the renderer to draw the frame.
24  */
25 const WRITE_TIMEOUT_MS = 12;
26
27 /**
28  * Threshold of max held chunks in the write buffer, that were already processed.
29  * This is a tradeoff between extensive write buffer shifts (bad runtime) and high
30  * memory consumption by data thats not used anymore.
31  */
32 const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
33
34 export class WriteBuffer {
35   private _writeBuffer: (string | Uint8Array)[] = [];
36   private _callbacks: ((() => void) | undefined)[] = [];
37   private _pendingData = 0;
38   private _bufferOffset = 0;
39
40   constructor(private _action: (data: string | Uint8Array) => void) { }
41
42   public writeSync(data: string | Uint8Array): void {
43     // force sync processing on pending data chunks to avoid in-band data scrambling
44     // does the same as innerWrite but without event loop
45     if (this._writeBuffer.length) {
46       for (let i = this._bufferOffset; i < this._writeBuffer.length; ++i) {
47         const data = this._writeBuffer[i];
48         const cb = this._callbacks[i];
49         this._action(data);
50         if (cb) cb();
51       }
52       // reset all to avoid reprocessing of chunks with scheduled innerWrite call
53       this._writeBuffer = [];
54       this._callbacks = [];
55       this._pendingData = 0;
56       // stop scheduled innerWrite by offset > length condition
57       this._bufferOffset = 0x7FFFFFFF;
58     }
59     // handle current data chunk
60     this._action(data);
61   }
62
63   public write(data: string | Uint8Array, callback?: () => void): void {
64     if (this._pendingData > DISCARD_WATERMARK) {
65       throw new Error('write data discarded, use flow control to avoid losing data');
66     }
67
68     // schedule chunk processing for next event loop run
69     if (!this._writeBuffer.length) {
70       this._bufferOffset = 0;
71       setTimeout(() => this._innerWrite());
72     }
73
74     this._pendingData += data.length;
75     this._writeBuffer.push(data);
76     this._callbacks.push(callback);
77   }
78
79   protected _innerWrite(): void {
80     const startTime = Date.now();
81     while (this._writeBuffer.length > this._bufferOffset) {
82       const data = this._writeBuffer[this._bufferOffset];
83       const cb = this._callbacks[this._bufferOffset];
84       this._bufferOffset++;
85
86       this._action(data);
87       this._pendingData -= data.length;
88       if (cb) cb();
89
90       if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
91         break;
92       }
93     }
94     if (this._writeBuffer.length > this._bufferOffset) {
95       // Allow renderer to catch up before processing the next batch
96       // trim already processed chunks if we are above threshold
97       if (this._bufferOffset > WRITE_BUFFER_LENGTH_THRESHOLD) {
98         this._writeBuffer = this._writeBuffer.slice(this._bufferOffset);
99         this._callbacks = this._callbacks.slice(this._bufferOffset);
100         this._bufferOffset = 0;
101       }
102       setTimeout(() => this._innerWrite(), 0);
103     } else {
104       this._writeBuffer = [];
105       this._callbacks = [];
106       this._pendingData = 0;
107       this._bufferOffset = 0;
108     }
109   }
110 }