From 7af1d5cee68658fd28c7a8950a59f39f1c1821ab Mon Sep 17 00:00:00 2001 From: sawka Date: Fri, 12 Aug 2022 18:34:56 -0700 Subject: [PATCH] lazy loading, cached TUR values --- src/main.tsx | 146 +++++++++++++++++++++++++++++++++++++++++---------- src/model.ts | 139 +++++++++++++++++++++++++----------------------- src/term.ts | 5 +- 3 files changed, 196 insertions(+), 94 deletions(-) diff --git a/src/main.tsx b/src/main.tsx index 9e1ca9dfc..74051c35e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -14,6 +14,38 @@ import {GlobalModel, GlobalInput, Session, Cmd, Window, Screen, ScreenWindow} fr dayjs.extend(localizedFormat) +type InterObsValue = { + sessionid : string, + windowid : string, + lineid : string, + cmdid : string, + visible : mobx.IObservableValue, + timeoutid? : any, +}; + +let globalLineWeakMap = new WeakMap(); + +function interObsCallback(entries) { + let now = Date.now(); + entries.forEach((entry) => { + let line = globalLineWeakMap.get(entry.target); + if ((line.timeoutid != null) && (line.visible.get() == entry.isIntersecting)) { + clearTimeout(line.timeoutid); + line.timeoutid = null; + return; + } + if (line.visible.get() != entry.isIntersecting && line.timeoutid == null) { + line.timeoutid = setTimeout(() => { + line.timeoutid = null; + mobx.action(() => { + line.visible.set(entry.isIntersecting); + })(); + }, 250); + return; + } + }); +} + function getLineId(line : LineType) : string { return sprintf("%s-%s-%s", line.sessionid, line.windowid, line.lineid); } @@ -107,30 +139,78 @@ class LineText extends React.Component<{sw : ScreenWindow, line : LineType}, {}> } @mobxReact.observer -class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width: number}, {}> { +class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> { termLoaded : mobx.IObservableValue = mobx.observable.box(false); + lineRef : React.RefObject = React.createRef(); + iobsVal : InterObsValue = null; + autorunDisposer : () => void = null; constructor(props) { super(props); + + let line = props.line; + let ival : InterObsValue = { + sessionid: line.sessionid, + windowid: line.windowid, + lineid: line.lineid, + cmdid: line.cmdid, + visible: mobx.observable.box(this.props.initVis), + }; + this.iobsVal = ival; } - - componentDidMount() { + + visibilityChanged(vis : boolean) : void { + if (vis && !this.termLoaded.get()) { + this.loadTerminal(); + } + else if (!vis && this.termLoaded.get()) { + let {line} = this.props; + } + } + + loadTerminal() : void { let {sw, line} = this.props; let model = GlobalModel; let cmd = model.getCmd(line); - if (cmd != null) { - let termElem = document.getElementById("term-" + getLineId(line)); - cmd.connectElem(termElem, sw.screenId, sw.windowId, this.props.width); - mobx.action(() => this.termLoaded.set(true))(); + if (cmd == null) { + return; + } + let termId = "term-" + getLineId(line); + let termElem = document.getElementById(termId); + if (termElem == null) { + console.log("cannot load terminal, no term elem found", termId); + return; + } + sw.connectElem(termElem, cmd, this.props.width); + mobx.action(() => this.termLoaded.set(true))(); + } + + componentDidMount() { + let {line} = this.props; + if (this.lineRef.current == null || this.props.interObs == null) { + console.log("LineCmd lineRef current is null or interObs is null", line, this.lineRef.current, this.props.interObs); + } + else { + globalLineWeakMap.set(this.lineRef.current, this.iobsVal); + this.props.interObs.observe(this.lineRef.current); + this.autorunDisposer = mobx.autorun(() => { + let vis = this.iobsVal.visible.get(); + this.visibilityChanged(vis); + }); } } componentWillUnmount() { let {sw, line} = this.props; let model = GlobalModel; - let cmd = model.getCmd(line); - if (cmd != null) { - cmd.disconnectElem(sw.screenId, sw.windowId); + if (this.termLoaded.get()) { + sw.disconnectElem(line.cmdid); + } + if (this.lineRef.current != null && this.props.interObs != null) { + this.props.interObs.unobserve(this.lineRef.current); + } + if (this.autorunDisposer != null) { + this.autorunDisposer(); } } @@ -143,12 +223,9 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width doRefresh() { let {sw, line} = this.props; let model = GlobalModel; - let cmd = model.getCmd(line); - if (cmd != null) { - let termWrap = cmd.getTermWrap(sw.screenId, sw.windowId); - if (termWrap != null) { - termWrap.reloadTerminal(500); - } + let termWrap = sw.getTermWrap(line.cmdid); + if (termWrap != null) { + termWrap.reloadTerminal(500); } } @@ -170,28 +247,32 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width } render() { - let {sw, line} = this.props; + let {sw, line, width} = this.props; let model = GlobalModel; let lineid = line.lineid.toString(); let formattedTime = getLineDateStr(line.ts); let cmd = model.getCmd(line); if (cmd == null) { - return
[cmd not found '{line.cmdid}']
; + return ( +
+ [cmd not found '{line.cmdid}'] +
+ ); } let termLoaded = this.termLoaded.get(); let cellHeightPx = 16; let cellWidthPx = 8; - let termWidth = Math.max(Math.trunc((this.props.width - 20)/cellWidthPx), 10); - let usedRows = cmd.getUsedRows(sw.screenId, sw.windowId); + let termWidth = Math.max(Math.trunc((width - 20)/cellWidthPx), 10); + let usedRows = sw.getUsedRows(cmd, width); let totalHeight = cellHeightPx * usedRows; let remote = model.getRemote(cmd.remoteId); let status = cmd.getStatus(); let running = (status == "running"); let detached = (status == "detached"); let termOpts = cmd.getTermOpts(); - let isFocused = cmd.getIsFocused(sw.screenId, sw.windowId); + let isFocused = sw.getIsFocused(line.cmdid); return ( -
+
= 5}, {"running": running}, {"detached": detached})} onClick={this.doRefresh}> {lineid} @@ -212,6 +293,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width
+
(loading)
); @@ -219,7 +301,7 @@ class LineCmd extends React.Component<{sw : ScreenWindow, line : LineType, width } @mobxReact.observer -class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number}, {}> { +class Line extends React.Component<{sw : ScreenWindow, line : LineType, width : number, interObs : IntersectionObserver, initVis : boolean}, {}> { render() { let line = this.props.line; if (line.linetype == "text") { @@ -237,7 +319,6 @@ class CmdInput extends React.Component<{}, {}> { lastTab : boolean = false; lastHistoryUpDown : boolean = false; lastTabCurLine : mobx.IObservableValue = mobx.observable.box(null); - textareaRef : React.RefObject = React.createRef(); isModKeyPress(e : any) { return e.code.match(/^(Control|Meta|Alt|Shift)(Left|Right)$/); @@ -423,7 +504,7 @@ class CmdInput extends React.Component<{}, {}> {
{promptStr}
- +
@@ -441,7 +522,8 @@ class CmdInput extends React.Component<{}, {}> { @mobxReact.observer class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { mutObs : any; - rszObs : any + rszObs : any; + interObs : IntersectionObserver; randomId : string; width : mobx.IObservableValue = mobx.observable.box(0); lastHeight : number = null; @@ -462,7 +544,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { let target = event.target; let atBottom = (target.scrollTop + 30 > (target.scrollHeight - target.offsetHeight)); if (sw && sw.shouldFollow.get() != atBottom) { - mobx.action(() => sw.shouldFollow.set(atBottom)); + mobx.action(() => sw.shouldFollow.set(atBottom))(); } // console.log("scroll-handler>", atBottom, target.scrollTop, target.scrollHeight); } @@ -477,6 +559,11 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { if (sw && sw.shouldFollow.get()) { setTimeout(() => this.scrollToBottom("mount"), 0); } + this.interObs = new IntersectionObserver(interObsCallback, { + root: elem, + rootMargin: "800px", + threshold: 0.0, + }); } let wvElem = document.getElementById(this.getWindowViewDOMId()); if (wvElem != null) { @@ -498,6 +585,9 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> { if (this.rszObs) { this.rszObs.disconnect(); } + if (this.interObs) { + this.interObs.disconnect(); + } } handleResize(entries : any) { @@ -619,7 +709,7 @@ class ScreenWindowView extends React.Component<{sw : ScreenWindow}, {}> {
- + win.lines.length-1-7}/>
diff --git a/src/model.ts b/src/model.ts index 5a1c3fd3c..42f98e63d 100644 --- a/src/model.ts +++ b/src/model.ts @@ -51,7 +51,6 @@ class Cmd { cmdId : string; data : OV; watching : boolean = false; - instances : Record = {}; constructor(cmd : CmdDataType) { this.sessionId = cmd.sessionid; @@ -60,58 +59,6 @@ class Cmd { this.data = mobx.observable.box(cmd, {deep: false}); } - connectElem(elem : Element, screenId : string, windowId : string, width : number) { - let termWrap = this.getTermWrap(screenId, windowId); - if (termWrap != null) { - console.log("term-wrap already exists for", screenId, windowId); - return; - } - termWrap = new TermWrap(elem, this.sessionId, this.cmdId, 0, this.getTermOpts(), {height: 0, width: width}, this.handleKey.bind(this)); - this.instances[screenId + "/" + windowId] = termWrap; - return; - } - - disconnectElem(screenId : string, windowId : string) { - let key = screenId + "/" + windowId; - let termWrap = this.instances[key]; - if (termWrap != null) { - termWrap.dispose(); - delete this.instances[key]; - } - } - - updatePtyData(ptyMsg : PtyDataUpdateType) { - for (let key in this.instances) { - let tw = this.instances[key]; - let data = base64ToArray(ptyMsg.ptydata64); - tw.updatePtyData(ptyMsg.ptypos, data); - } - } - - getTermWrap(screenId : string, windowId : string) : TermWrap { - return this.instances[screenId + "/" + windowId]; - } - - getUsedRows(screenId : string, windowId : string) : number { - let termOpts = this.getTermOpts(); - if (!termOpts.flexrows) { - return termOpts.rows; - } - let termWrap = this.getTermWrap(screenId, windowId); - if (termWrap == null) { - return 2; - } - return termWrap.usedRows.get(); - } - - getIsFocused(screenId : string, windowId : string) : boolean { - let termWrap = this.getTermWrap(screenId, windowId); - if (termWrap == null) { - return false; - } - return termWrap.isFocused.get(); - } - setCmd(cmd : CmdDataType) { mobx.action(() => { this.data.set(cmd); @@ -205,10 +152,7 @@ class Screen { updatePtyData(ptyMsg : PtyDataUpdateType) { for (let i=0; i; shouldFollow : OV = mobx.observable.box(true); + // cmdid => TermWrap + terms : Record = {}; + constructor(swdata : ScreenWindowType) { this.sessionId = swdata.sessionid; this.screenId = swdata.screenid; @@ -245,6 +192,65 @@ class ScreenWindow { this.layout = mobx.observable.box(swdata.layout); } + updatePtyData(ptyMsg : PtyDataUpdateType) { + let cmdId = ptyMsg.cmdid; + let term = this.terms[cmdId]; + if (term == null) { + return; + } + let data = base64ToArray(ptyMsg.ptydata64); + term.updatePtyData(ptyMsg.ptypos, data); + } + + getTermWrap(cmdId : string) : TermWrap { + return this.terms[cmdId]; + } + + connectElem(elem : Element, cmd : Cmd, width : number) { + let cmdId = cmd.cmdId; + let termWrap = this.getTermWrap(cmdId); + if (termWrap != null) { + console.log("term-wrap already exists for", this.screenId, this.windowId, cmdId); + return; + } + let usedRows = GlobalModel.getTUR(this.sessionId, cmdId, width); + termWrap = new TermWrap(elem, this.sessionId, cmdId, usedRows, cmd.getTermOpts(), {height: 0, width: width}, cmd.handleKey.bind(cmd)); + this.terms[cmdId] = termWrap; + return; + } + + disconnectElem(cmdId : string) { + let termWrap = this.terms[cmdId]; + if (cmdId != null) { + termWrap.dispose(); + delete this.terms[cmdId]; + } + } + + getUsedRows(cmd : Cmd, width : number) : number { + let termOpts = cmd.getTermOpts(); + if (!termOpts.flexrows) { + return termOpts.rows; + } + let termWrap = this.getTermWrap(cmd.cmdId); + if (termWrap == null) { + let usedRows = GlobalModel.getTUR(this.sessionId, cmd.cmdId, width); + if (usedRows != null) { + return usedRows; + } + return 2; + } + return termWrap.usedRows.get(); + } + + getIsFocused(cmdId : string) : boolean { + let termWrap = this.getTermWrap(cmdId); + if (termWrap == null) { + return false; + } + return termWrap.isFocused.get(); + } + reset() { mobx.action(() => { this.shouldFollow.set(true); @@ -271,14 +277,6 @@ class Window { this.windowId = windowId; } - updatePtyData(ptyMsg : PtyDataUpdateType) { - let cmd = this.cmds[ptyMsg.cmdid]; - if (cmd == null) { - return; - } - cmd.updatePtyData(ptyMsg); - } - updateWindow(win : WindowDataType, load : boolean) { mobx.action(() => { if (!isBlank(win.curremote)) { @@ -652,6 +650,7 @@ class Model { infoMsg : OV = mobx.observable.box(null); infoTimeoutId : any = null; inputModel : InputModel; + termUsedRowsCache : Record = {}; constructor() { this.clientId = getApi().getId(); @@ -666,6 +665,16 @@ class Model { getApi().onDigitCmd(this.onDigitCmd.bind(this)); } + getTUR(sessionId : string, cmdId : string, width : number) : number { + let key = sessionId + "/" + cmdId + "/" + width; + return this.termUsedRowsCache[key]; + } + + setTUR(sessionId : string, cmdId : string, width : number, usedRows : number) : void { + let key = sessionId + "/" + cmdId + "/" + width; + this.termUsedRowsCache[key] = usedRows; + } + contextScreen(e : any, screenId : string) { console.log("model", screenId); getApi().contextScreen({screenId: screenId}, {x: e.x, y: e.y}); diff --git a/src/term.ts b/src/term.ts index 09a221174..c3742d087 100644 --- a/src/term.ts +++ b/src/term.ts @@ -46,15 +46,17 @@ class TermWrap { reloading : boolean = false; dataUpdates : DataUpdate[] = []; loadError : mobx.IObservableValue = mobx.observable.box(false); + winSize : WindowSize; constructor(elem : Element, sessionId : string, cmdId : string, usedRows : number, termOpts : TermOptsType, winSize : WindowSize, keyHandler : (event : any) => void) { this.sessionId = sessionId; this.cmdId = cmdId; this.connectedElem = elem; this.flexRows = termOpts.flexrows ?? false; + this.winSize = winSize; if (this.flexRows) { this.atRowMax = false; - this.usedRows = mobx.observable.box(usedRows || 2); + this.usedRows = mobx.observable.box(usedRows ?? 2); } else { this.atRowMax = true; @@ -141,6 +143,7 @@ class TermWrap { mobx.action(() => { let oldUsedRows = this.usedRows.get(); this.usedRows.set(tur); + GlobalModel.setTUR(this.sessionId, this.cmdId, this.winSize.width, tur); if (this.connectedElem) { let resizeEvent = new CustomEvent("termresize", { bubbles: true,