perf: route PTY stream through a single shared IPC listener
Every pane registered its own global onPtyData/onPtyExit listener, so each output chunk woke N listeners and N-1 discarded it after an id compare. With many terminals under heavy output that fan-out is pure overhead. Now one IPC listener dispatches through a Map keyed by sessionId: one lookup per chunk, only the owning pane runs.
This commit is contained in:
1 parent
bbee1d642d
commit
be7332db17
2 files changed
+57
-8
No files matched your search
@@ -9,11 +9,11 @@ import '@xterm/xterm/css/xterm.css'
|
||||
import { ClearOutlined, CopyOutlined, FolderOpenOutlined, PauseOutlined, SearchOutlined, SelectOutlined, SnippetsOutlined, SoundOutlined } from '@ant-design/icons'
|
||||
import { getThemeById } from '@shared/theme'
|
||||
import type { CommandItem } from '@shared/commands'
|
||||
import type { PtyDataEvent, PtyExitEvent } from '@shared/ipc'
|
||||
import type { TerminalSettings } from '@shared/settings'
|
||||
import { useSettingsStore } from '@renderer/settings/store'
|
||||
import { writeBroadcast } from '@renderer/workspace/broadcastStore'
|
||||
import { compileRules, HighlightStream, type CompiledRule } from './highlightEngine'
|
||||
import { subscribePtyData, subscribePtyExit } from './ptyDispatcher'
|
||||
import { cdArgument, conemuCwd } from './cwdTracker'
|
||||
import { getSessionCwd, setSessionCwd } from '@renderer/workspace/sessionCwdStore'
|
||||
import './terminal.css'
|
||||
@@ -792,18 +792,19 @@ export const TerminalView: ForwardRefExoticComponent<TerminalViewProps & { ref?:
|
||||
let replayDone = false
|
||||
const pending: string[] = []
|
||||
const unsubscribes: (() => void)[] = [
|
||||
window.api.onPtyData((e: PtyDataEvent) => {
|
||||
if (e.id !== sessionId || deadRef.current) return
|
||||
// Routed via the shared dispatcher (one IPC listener for all panes)
|
||||
// instead of a per-pane global listener.
|
||||
subscribePtyData(sessionId, (data: string) => {
|
||||
if (deadRef.current) return
|
||||
if (!replayDone) {
|
||||
pending.push(e.data)
|
||||
pending.push(data)
|
||||
return
|
||||
}
|
||||
writeHighlighted(e.data)
|
||||
writeHighlighted(data)
|
||||
}),
|
||||
window.api.onPtyExit((e: PtyExitEvent) => {
|
||||
if (e.id !== sessionId) return
|
||||
subscribePtyExit(sessionId, (code: number) => {
|
||||
deadRef.current = true
|
||||
setExitCode(e.exitCode)
|
||||
setExitCode(code)
|
||||
setDead(true)
|
||||
})
|
||||
]
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { PtyDataEvent, PtyExitEvent } from '@shared/ipc'
|
||||
|
||||
/**
|
||||
* Single shared PTY stream listener. Every pane used to register its own
|
||||
* window.api.onPtyData/onPtyExit listener, so each output chunk woke N
|
||||
* listeners (N = pane count) and N-1 of them discarded it after an id
|
||||
* compare. With many terminals under heavy output that per-chunk fan-out is
|
||||
* pure overhead. Here one IPC listener dispatches through a Map keyed by
|
||||
* sessionId: one lookup per chunk, and only the owning pane runs.
|
||||
*/
|
||||
|
||||
type DataHandler = (data: string) => void
|
||||
type ExitHandler = (exitCode: number) => void
|
||||
|
||||
const dataHandlers = new Map<string, DataHandler>()
|
||||
const exitHandlers = new Map<string, ExitHandler>()
|
||||
let bound = false
|
||||
|
||||
function ensureBound(): void {
|
||||
if (bound) return
|
||||
bound = true
|
||||
window.api.onPtyData((e: PtyDataEvent) => {
|
||||
dataHandlers.get(e.id)?.(e.data)
|
||||
})
|
||||
window.api.onPtyExit((e: PtyExitEvent) => {
|
||||
exitHandlers.get(e.id)?.(e.exitCode)
|
||||
})
|
||||
}
|
||||
|
||||
/** Subscribe to one session's output. Returns the unsubscribe function. */
|
||||
export function subscribePtyData(id: string, cb: DataHandler): () => void {
|
||||
ensureBound()
|
||||
dataHandlers.set(id, cb)
|
||||
return () => {
|
||||
// A rebind (template apply) may have replaced the handler already; only
|
||||
// delete when this subscription is still the registered one.
|
||||
if (dataHandlers.get(id) === cb) dataHandlers.delete(id)
|
||||
}
|
||||
}
|
||||
|
||||
/** Subscribe to one session's exit. Returns the unsubscribe function. */
|
||||
export function subscribePtyExit(id: string, cb: ExitHandler): () => void {
|
||||
ensureBound()
|
||||
exitHandlers.set(id, cb)
|
||||
return () => {
|
||||
if (exitHandlers.get(id) === cb) exitHandlers.delete(id)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user