early version of async support for CSI and ESC handlers

This commit is contained in:
Jörg Breitbart
2021-01-20 20:05:14 +01:00
parent 74ea558bfc
commit bc1048062d
6 changed files with 245 additions and 32 deletions
+57 -8
View File
@@ -37,8 +37,9 @@ export class WriteBuffer {
private _pendingData = 0;
private _bufferOffset = 0;
constructor(private _action: (data: string | Uint8Array) => void) { }
constructor(private _action: (data: string | Uint8Array, promiseResult?: boolean) => void | Promise<boolean>) { }
// FIXME: does not work that way anymore with async handlers!!!
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
@@ -76,16 +77,64 @@ export class WriteBuffer {
this._callbacks.push(callback);
}
protected _innerWrite(): void {
const startTime = Date.now();
protected _innerWrite(d: number = 0, promiseResult: boolean = true): void {
let result: void | Promise<boolean>;
const startTime = d || 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 (result = this._action(data, promiseResult)) {
/**
* If we get a promise as return value, we re-schedule the continuation
* as thenable on the promise and exit right away.
*
* The exit here means, that we block input processing at the current active chunk,
* the exact execution position within the chunk is preserved by the saved
* stack content in InputHandler and EscapeSequenceParser.
*
* Resuming happens automatically from that saved stack state.
* Also the resolved promise value is passed along the callstack to
* `EscapeSequenceParser.parse` to correctly resume the stopped handler loop.
*
* Exceptions on async handlers will be logged to console async, but do not interrupt
* the input processing (continues with next handler at the current input position).
* FIXME: No clear exception handling rules for sync handlers yet (will exit whole processing?).
*/
/**
* If a promise takes long to resolve, we should schedule continuation behind setTimeout.
* This might already be too late, if our .then enters really late (executor + prev thens took very long).
* This cannot be solved here for the handler itself (it is the handlers responsibility to slice hard work),
* but we can at least schedule a screen update as we gain control.
*/
const continuation: (r: boolean) => void = (r: boolean) => Date.now() - startTime >= WRITE_TIMEOUT_MS
? setTimeout(() => this._innerWrite(0, r))
: this._innerWrite(startTime, r);
/**
* Optimization considerations:
* The continuation above favors FPS over throughput by eval'ing `startTime` on resolve.
* This might schedule too many screen updates with bad throughput drops (in case a slow
* resolving handler sliced its work properly behind setTimeout calls). We cannot spot
* this condition here, also the renderer has no way to spot nonsense updates either.
* FIXME: A proper fix for this would track the FPS at the renderer entry level separately.
*
* If favoring of FPS shows bad throughtput impact, use the following instead. It favors
* throughput by eval'ing `startTime` upfront pulling at least one more chunk into the
* current microtask queue (executed before setTimeout).
*/
// const continuation: (r: boolean) => void = Date.now() - startTime >= WRITE_TIMEOUT_MS
// ? r => setTimeout(() => this._innerWrite(0, r))
// : r => this._innerWrite(startTime, r);
result.then(continuation, err => { setTimeout(() => { throw err; }); continuation(true); });
return;
}
const cb = this._callbacks[this._bufferOffset];
if (cb) cb();
this._bufferOffset++;
this._pendingData -= data.length;
if (Date.now() - startTime >= WRITE_TIMEOUT_MS) {
break;
@@ -99,7 +148,7 @@ export class WriteBuffer {
this._callbacks = this._callbacks.slice(this._bufferOffset);
this._bufferOffset = 0;
}
setTimeout(() => this._innerWrite(), 0);
setTimeout(() => this._innerWrite());
} else {
this._writeBuffer = [];
this._callbacks = [];