[Init] Initial commit - NetMesh terminal manager
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
This commit is contained in:
145
components/terminal/runtime/webglRendererController.ts
Normal file
145
components/terminal/runtime/webglRendererController.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
export interface WebglRendererAddon {
|
||||
onContextLoss(callback: () => void): unknown;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export interface WebglRendererController<TAddon extends WebglRendererAddon> {
|
||||
ensure(): void;
|
||||
suspend(): void;
|
||||
dispose(): void;
|
||||
getAddon(): TAddon | null;
|
||||
}
|
||||
|
||||
interface WebglRendererControllerOptions<TAddon extends WebglRendererAddon> {
|
||||
enabled: boolean;
|
||||
createAddon: () => TAddon;
|
||||
loadAddon: (addon: TAddon) => void;
|
||||
repaint: () => void;
|
||||
setLoaded: (loaded: boolean) => void;
|
||||
warn: (message: string, error?: unknown) => void;
|
||||
now?: () => number;
|
||||
setTimer?: (callback: () => void, delayMs: number) => ReturnType<typeof setTimeout>;
|
||||
clearTimer?: (timer: ReturnType<typeof setTimeout>) => void;
|
||||
recoveryDelayMs?: number;
|
||||
recoveryWindowMs?: number;
|
||||
maxRecoveriesPerWindow?: number;
|
||||
}
|
||||
|
||||
/** Owns WebGL addon recovery without depending on xterm or browser globals. */
|
||||
export function createWebglRendererController<TAddon extends WebglRendererAddon>(
|
||||
options: WebglRendererControllerOptions<TAddon>,
|
||||
): WebglRendererController<TAddon> {
|
||||
const now = options.now ?? Date.now;
|
||||
const setTimer = options.setTimer ?? setTimeout;
|
||||
const clearTimer = options.clearTimer ?? clearTimeout;
|
||||
const recoveryDelayMs = options.recoveryDelayMs ?? 50;
|
||||
// Keep a wide enough window that periodic GPU context loss (commonly ~15s on
|
||||
// some Linux drivers/compositors) still accumulates and trips the breaker.
|
||||
// A 10s window aged those losses out, so WebGL kept dispose -> rebuild flashing.
|
||||
const recoveryWindowMs = options.recoveryWindowMs ?? 60_000;
|
||||
const maxRecoveriesPerWindow = options.maxRecoveriesPerWindow ?? 2;
|
||||
const contextLosses: number[] = [];
|
||||
let addon: TAddon | null = null;
|
||||
let loaded = false;
|
||||
let circuitBroken = false;
|
||||
let disposed = false;
|
||||
let recoveryTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
||||
const publishLoaded = (value: boolean) => {
|
||||
loaded = value;
|
||||
options.setLoaded(value);
|
||||
};
|
||||
|
||||
const cancelRecovery = () => {
|
||||
if (recoveryTimer === null) return;
|
||||
clearTimer(recoveryTimer);
|
||||
recoveryTimer = null;
|
||||
};
|
||||
|
||||
const ensure = () => {
|
||||
if (loaded || circuitBroken || disposed || !options.enabled) return;
|
||||
let nextAddon: TAddon | null = null;
|
||||
try {
|
||||
nextAddon = options.createAddon();
|
||||
const ownedAddon = nextAddon;
|
||||
ownedAddon.onContextLoss(() => {
|
||||
if (addon !== ownedAddon || disposed) return;
|
||||
options.warn("[XTerm] WebGL context loss detected, rebuilding renderer");
|
||||
try {
|
||||
ownedAddon.dispose();
|
||||
} catch (error) {
|
||||
options.warn("[XTerm] Failed to dispose lost WebGL renderer", error);
|
||||
}
|
||||
addon = null;
|
||||
publishLoaded(false);
|
||||
options.repaint();
|
||||
|
||||
const lossTime = now();
|
||||
while (contextLosses.length > 0 && lossTime - contextLosses[0] > recoveryWindowMs) {
|
||||
contextLosses.shift();
|
||||
}
|
||||
contextLosses.push(lossTime);
|
||||
if (contextLosses.length > maxRecoveriesPerWindow) {
|
||||
circuitBroken = true;
|
||||
cancelRecovery();
|
||||
options.warn("[XTerm] Repeated WebGL context loss, staying on DOM renderer");
|
||||
return;
|
||||
}
|
||||
|
||||
cancelRecovery();
|
||||
recoveryTimer = setTimer(() => {
|
||||
recoveryTimer = null;
|
||||
if (disposed) return;
|
||||
ensure();
|
||||
options.repaint();
|
||||
}, recoveryDelayMs);
|
||||
});
|
||||
addon = ownedAddon;
|
||||
options.loadAddon(ownedAddon);
|
||||
publishLoaded(true);
|
||||
} catch (error) {
|
||||
try {
|
||||
nextAddon?.dispose();
|
||||
} catch {
|
||||
// Preserve the original load failure.
|
||||
}
|
||||
if (addon === nextAddon) addon = null;
|
||||
publishLoaded(false);
|
||||
options.warn(
|
||||
"[XTerm] WebGL addon failed, using DOM renderer. Error:",
|
||||
error instanceof Error ? error.message : error,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const suspend = () => {
|
||||
cancelRecovery();
|
||||
const currentAddon = addon;
|
||||
addon = null;
|
||||
publishLoaded(false);
|
||||
if (!currentAddon) return;
|
||||
try {
|
||||
currentAddon.dispose();
|
||||
} catch (error) {
|
||||
options.warn("[XTerm] Failed to suspend WebGL renderer", error);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
ensure,
|
||||
suspend,
|
||||
dispose: () => {
|
||||
disposed = true;
|
||||
cancelRecovery();
|
||||
const currentAddon = addon;
|
||||
addon = null;
|
||||
publishLoaded(false);
|
||||
try {
|
||||
currentAddon?.dispose();
|
||||
} catch (error) {
|
||||
options.warn("[XTerm] webglAddon dispose failed", error);
|
||||
}
|
||||
},
|
||||
getAddon: () => addon,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user