big performance update for lines, only render 100 lines around anchor. auto-load more. now anchor is an observable and will change what LinesView renders

This commit is contained in:
sawka 2023-03-24 18:37:02 -07:00
parent 11649f4c66
commit dcdf04bc82
3 changed files with 105 additions and 47 deletions

View File

@ -12,7 +12,7 @@ import type {SessionDataType, LineType, CmdDataType, RemoteType, RemoteStateType
import type * as T from "./types";
import localizedFormat from 'dayjs/plugin/localizedFormat';
import {GlobalModel, GlobalCommandRunner, Session, Cmd, ScreenLines, Screen, riToRPtr, windowWidthToCols, windowHeightToRows, termHeightFromRows, termWidthFromCols, TabColors, RemoteColors} from "./model";
import {isModKeyPress} from "./util";
import {isModKeyPress, boundInt} from "./util";
import ReactMarkdown from 'react-markdown'
import remarkGfm from 'remark-gfm'
import {BookmarksView} from "./bookmarks";
@ -1688,8 +1688,8 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
lastOffsetHeight : number = 0;
lastOffsetWidth : number = 0;
ignoreNextScroll : boolean = false;
visibleMap : Map<string, OV<boolean>>; // lineid => OV<vis>
collapsedMap : Map<string, OV<boolean>>; // lineid => OV<collapsed>
visibleMap : Map<string, OV<boolean>>; // linenum => OV<vis>
collapsedMap : Map<string, OV<boolean>>; // linenum => OV<collapsed>
lastLinesLength : number = 0;
lastSelectedLine : number = 0;
@ -1788,9 +1788,11 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
isVis = true
}
// console.log("line", lineElem.dataset.linenum, "top=" + lineTop, "bot=" + lineTop, isVis);
let lineNumInt = parseInt(lineElem.dataset.linenum);
newMap.set(lineElem.dataset.linenum, isVis);
// console.log("setvis", sprintf("%4d %4d-%4d (%4d) %s", lineElem.dataset.linenum, lineTop, lineBot, lineElem.offsetHeight, isVis));
}
// console.log("compute vismap", "[" + this.firstVisLine + "," + this.lastVisLine + "]");
mobx.action(() => {
for (let [k, v] of newMap) {
let oldVal = this.visibleMap.get(k);
@ -1824,31 +1826,29 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
}
restoreAnchorOffset(reason : string) : void {
let {screen} = this.props;
let {lines} = this.props;
let linesElem = this.linesRef.current;
if (linesElem == null) {
return;
}
if (screen.anchorLine == null || screen.anchorLine == 0) {
return;
}
let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", screen.anchorLine));
let anchor = this.getAnchor();
let anchorElem = linesElem.querySelector(sprintf(".line[data-linenum=\"%d\"]", anchor.anchorLine));
if (anchorElem == null) {
return;
}
let isLastLine = screen.isLastLine(screen.anchorLine);
let isLastLine = (anchor.anchorIndex == lines.length-1);
let scrollTop = linesElem.scrollTop;
let height = linesElem.clientHeight;
let containerBottom = scrollTop + height;
let curAnchorOffset = containerBottom - (anchorElem.offsetTop + anchorElem.offsetHeight);
let newAnchorOffset = screen.anchorOffset;
let newAnchorOffset = anchor.anchorOffset;
if (isLastLine && newAnchorOffset == 0) {
newAnchorOffset = 10;
}
if (curAnchorOffset != newAnchorOffset) {
let offsetDiff = curAnchorOffset - newAnchorOffset;
let newScrollTop = scrollTop - offsetDiff;
// console.log("update scrolltop", reason, "line=" + screen.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop);
// console.log("update scrolltop", reason, "line=" + anchor.anchorLine, -offsetDiff, linesElem.scrollTop, "=>", newScrollTop);
linesElem.scrollTop = newScrollTop;
this.ignoreNextScroll = true;
}
@ -1857,11 +1857,10 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
componentDidMount() : void {
let {screen, lines} = this.props;
let linesElem = this.linesRef.current;
let anchorLineObj = screen.getLineByNum(screen.anchorLine);
if (anchorLineObj == null) {
// scroll to bottom
let anchor = this.getAnchor();
if (anchor.anchorIndex == lines.length-1) {
if (linesElem != null) {
linesElem.scrollTop = linesElem.clientHeight;
linesElem.scrollTop = linesElem.scrollHeight;
}
this.computeAnchorLine();
}
@ -1870,7 +1869,6 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
}
this.lastSelectedLine = screen.getSelectedLine();
this.lastLinesLength = lines.length;
if (linesElem != null) {
this.lastOffsetHeight = linesElem.offsetHeight;
this.lastOffsetWidth = linesElem.offsetWidth;
@ -1937,24 +1935,29 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
if (newLine == 0) {
return;
}
let lidx = this.findClosestLineIndex(newLine);
this.setLineVisible(newLine, true);
// console.log("update selected line", this.lastSelectedLine, "=>", newLine, sprintf("anchor=%d:%d", screen.anchorLine, screen.anchorOffset));
let viewInfo = this.getLineViewInfo(newLine);
let isFirst = (lidx.index == 0);
let isLast = (lidx.index == lines.length-1);
let offsetDelta = (isLast ? 10 : (isFirst ? -10 : 0));
if (viewInfo == null) {
return;
screen.setAnchorFields(newLine, 0+offsetDelta, "updateSelectedLine");
}
screen.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine");
let isFirst = (newLine == lines[0].linenum);
let isLast = (newLine == lines[lines.length-1].linenum);
if (viewInfo.botOffset > 0) {
linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + (isLast ? 10 : 0);
else if (viewInfo.botOffset > 0) {
linesElem.scrollTop = linesElem.scrollTop + viewInfo.botOffset + offsetDelta;
this.ignoreNextScroll = true;
screen.anchorOffset = (isLast ? 10 : 0);
screen.setAnchorFields(newLine, offsetDelta, "updateSelectedLine");
}
else if (viewInfo.topOffset < 0) {
linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + (isFirst ? -10 : 0);
linesElem.scrollTop = linesElem.scrollTop + viewInfo.topOffset + offsetDelta;
this.ignoreNextScroll = true;
screen.anchorOffset = linesElem.clientHeight - viewInfo.height;
let newOffset = linesElem.clientHeight - viewInfo.height;
screen.setAnchorFields(newLine, newOffset, "updateSelectedLine");
}
else {
screen.setAnchorFields(newLine, viewInfo.anchorOffset, "updateSelectedLine");
}
// console.log("new anchor", screen.getAnchorStr());
}
@ -2008,6 +2011,9 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
@boundMethod
onHeightChange(lineNum : number, newHeight : number, oldHeight : number) : void {
if (oldHeight == null) {
return;
}
// console.log("height-change", lineNum, oldHeight, "=>", newHeight);
this.restoreAnchorOffset("height-change");
this.computeVisibleMap_debounced();
@ -2032,6 +2038,38 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
let prevLineFormat = dayjs(prevLineDate).format("YYYY-MM-DD");
return null;
}
findClosestLineIndex(lineNum : number) : {line : LineType, index : number} {
let {lines} = this.props;
if (lines.length == 0) {
throw new Error("invalid lines, cannot have 0 length in LinesView");
}
if (lineNum == null || lineNum == 0) {
return {line: lines[lines.length-1], index: lines.length-1};
}
// todo: bsearch
// lines is sorted by linenum
for (let idx=0; idx<lines.length; idx++) {
let line = lines[idx];
if (line.linenum >= lineNum) {
return {line: line, index: idx};
}
}
return {line: lines[lines.length-1], index: lines.length-1};
}
getAnchor() : {anchorLine : number, anchorOffset : number, anchorIndex : number} {
let {screen, lines} = this.props;
let anchor = screen.getAnchor();
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
return {anchorLine: lines[lines.length-1].linenum, anchorOffset: 0, anchorIndex: lines.length-1};
}
let lidx = this.findClosestLineIndex(anchor.anchorLine);
if (lidx.line.linenum == anchor.anchorLine) {
return {anchorLine: anchor.anchorLine, anchorOffset: anchor.anchorOffset, anchorIndex: lidx.index};
}
return {anchorLine: lidx.line.linenum, anchorOffset: 0, anchorIndex: lidx.index};
}
render() {
let {screen, width, lines, renderMode} = this.props;
@ -2052,7 +2090,11 @@ class LinesView extends React.Component<{sessionId : string, screen : Screen, wi
let todayStr = getTodayStr();
let yesterdayStr = getYesterdayStr();
let prevDateStr : string = null;
for (let idx=0; idx<lines.length; idx++) {
let anchor = this.getAnchor();
let startIdx = boundInt(anchor.anchorIndex-50, 0, lines.length-1);
let endIdx = boundInt(anchor.anchorIndex+50, 0, lines.length-1);
// console.log("render", anchor, "[" + startIdx + "," + endIdx + "]");
for (let idx=startIdx; idx<=endIdx; idx++) {
let line = lines[idx];
let lineNumStr = String(line.linenum);
let dateSepStr = null;

View File

@ -310,8 +310,7 @@ class Screen {
lastRows : number;
selectedLine : OV<number>;
focusType : OV<FocusTypeStrs>;
anchorLine : number = null;
anchorOffset : number = 0;
anchor : OV<{anchorLine : number, anchorOffset : number}>;
termLineNumFocus : OV<number>;
setAnchor_debounced : (anchorLine : number, anchorOffset : number) => void;
terminals : Record<string, TermWrap> = {}; // cmdid => TermWrap
@ -328,7 +327,7 @@ class Screen {
this.selectedLine = mobx.observable.box(sdata.selectedline == 0 ? null : sdata.selectedline, {name: "selectedLine"});
this.setAnchor_debounced = debounce(1000, this.setAnchor.bind(this));
if (sdata.selectedline != 0) {
this.setAnchorFields(sdata.selectedline, 0, "init");
this.anchor = mobx.observable.box({anchorLine: sdata.selectedline, anchorOffset: 0}, {name: "screen-anchor"});
}
this.termLineNumFocus = mobx.observable.box(0, {name: "termLineNumFocus"});
this.curRemote = mobx.observable.box(sdata.curremote, {name: "screen-curRemote"});
@ -368,10 +367,11 @@ class Screen {
}
getAnchorStr() : string {
if (this.anchorLine == null || this.anchorLine == 0) {
let anchor = this.anchor.get();
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
return "0";
}
return sprintf("%d:%d", this.anchorLine, this.anchorOffset);
return sprintf("%d:%d", anchor.anchorLine, anchor.anchorOffset);
}
getTabColor() : string {
@ -393,8 +393,9 @@ class Screen {
}
setAnchorFields(anchorLine : number, anchorOffset : number, reason : string) {
this.anchorLine = anchorLine;
this.anchorOffset = anchorOffset;
mobx.action(() => {
this.anchor.set({anchorLine: anchorLine, anchorOffset: anchorOffset});
})();
// console.log("set-anchor-fields", anchorLine, anchorOffset, reason);
}
@ -435,6 +436,14 @@ class Screen {
GlobalCommandRunner.screenSetAnchor(this.sessionId, this.screenId, setVal);
}
getAnchor() : {anchorLine : number, anchorOffset : number} {
let anchor = this.anchor.get();
if (anchor.anchorLine == null || anchor.anchorLine == 0) {
return {anchorLine: this.selectedLine.get(), anchorOffset: 0};
}
return anchor;
}
getMaxLineNum() : number {
let win = this.getScreenLines();
if (win == null) {
@ -467,19 +476,6 @@ class Screen {
return null;
}
isLastLine(lineNum : number) : boolean {
let win = this.getScreenLines();
if (win == null) {
return false;
}
let lines = win.lines;
if (lines == null || lines.length == 0) {
return false;
}
let lastLine = lines[lines.length-1];
return (lastLine.linenum == lineNum);
}
getPresentLineNum(lineNum : number) : number {
let win = this.getScreenLines();
if (win == null || !win.loaded.get()) {
@ -2708,7 +2704,13 @@ class Model {
runUpdate(genUpdate : UpdateMessage, interactive : boolean) {
mobx.action(() => {
let oldContext = this.getUIContext();
this.runUpdate_internal(genUpdate, oldContext, interactive);
try {
this.runUpdate_internal(genUpdate, oldContext, interactive);
}
catch (e) {
console.log("error running update", e, genUpdate);
throw e;
}
let newContext = this.getUIContext()
if (oldContext.sessionid != newContext.sessionid
|| oldContext.screenid != newContext.screenid) {

View File

@ -74,6 +74,10 @@ function genMergeSimpleData<T extends ISimpleDataType>(objs : mobx.IObservableAr
}
for (let i=0; i<dataArr.length; i++) {
let dataItem = dataArr[i];
if (dataItem == null) {
console.log("genMergeSimpleData, null item");
console.trace();
}
let id = idFn(dataItem);
if (dataItem.remove) {
delete objMap[id];
@ -113,6 +117,11 @@ function genMergeData<ObjType extends IObjType<DataType>, DataType extends IData
}
for (let i=0; i<dataArr.length; i++) {
let dataItem = dataArr[i];
if (dataItem == null) {
console.log("genMergeData, null item");
console.trace();
continue;
}
let id = dataIdFn(dataItem);
let obj = objMap[id];
if (dataItem.remove) {
@ -156,6 +165,11 @@ function genMergeDataMap<ObjType extends IObjType<DataType>, DataType extends ID
}
for (let i=0; i<dataArr.length; i++) {
let dataItem = dataArr[i];
if (dataItem == null) {
console.log("genMergeDataMap, null item");
console.trace();
continue;
}
let id = dataIdFn(dataItem);
let obj = objMap.get(id);
if (dataItem.remove) {