Skip to content

Commit 1f82c8e

Browse files
committed
avoid massive gc pause on large load in firefox
1 parent dfa25f4 commit 1f82c8e

File tree

1 file changed

+80
-61
lines changed

1 file changed

+80
-61
lines changed

code.js

Lines changed: 80 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -772,6 +772,8 @@
772772
const unicodeWidthCache = new Map();
773773
const lines = [];
774774
const progressChunkSize = 1 << 20;
775+
let runData = new Int32Array(1024);
776+
let runDataLength = 0;
775777
let prevProgressPoint = 0;
776778
let longestLineInColumns = 0;
777779
let lineStartOffset = 0;
@@ -784,10 +786,10 @@
784786
continue;
785787
}
786788

789+
const runBase = runDataLength;
790+
const n = raw.length + 1; // Add 1 for the extra character at the end
787791
let nextProgressPoint = progress ? prevProgressPoint + progressChunkSize - lineStartOffset : Infinity;
788-
let runs = [];
789792
let i = 0;
790-
let n = raw.length + 1; // Add 1 for the extra character at the end
791793
let column = 0;
792794

793795
while (i < n) {
@@ -904,16 +906,22 @@
904906
i++;
905907
}
906908

907-
runs.push({
908-
whitespaceAndFlags: whitespace | (isSingleChunk ? 0x100 /* isSingleChunk */ : 0),
909-
startIndex,
910-
endIndex: i,
911-
startColumn,
912-
endColumn: column,
913-
});
909+
// Append the run to the typed array
910+
if (runDataLength + 5 > runData.length) {
911+
const newData = new Int32Array(runData.length << 1);
912+
newData.set(runData);
913+
runData = newData;
914+
}
915+
runData[runDataLength] = whitespace | (isSingleChunk ? 0x100 /* isSingleChunk */ : 0);
916+
runData[runDataLength + 1] = startIndex;
917+
runData[runDataLength + 2] = i;
918+
runData[runDataLength + 3] = startColumn;
919+
runData[runDataLength + 4] = column;
920+
runDataLength += 5;
914921
}
915922

916-
lines.push({ raw, runs, runsText: {}, endIndex: i, endColumn: column });
923+
const runCount = (runDataLength - runBase) / 5;
924+
lines.push({ raw, runBase, runCount, runText: {}, endIndex: i, endColumn: column });
917925
longestLineInColumns = Math.max(longestLineInColumns, column);
918926
lineStartOffset += raw.length;
919927
}
@@ -922,15 +930,24 @@
922930
await progress(text.length - prevProgressPoint);
923931
}
924932

925-
return { lines, longestLineInColumns };
933+
return { lines, longestLineInColumns, runData: runData.subarray(0, runDataLength) };
926934
}
927935

928936
async function createTextArea({ sourceIndex, text, progress, mappings, mappingsOffset, otherSource, originalName, bounds }) {
929937
const shadowWidth = 16;
930938
const textPaddingX = 5;
931939
const textPaddingY = 1;
932940
const scrollbarThickness = 16;
933-
let { lines, longestLineInColumns } = await splitTextIntoLinesAndRuns(text, progress);
941+
942+
// Runs are stored in a flat typed array to improve loading time
943+
const run_whitespace = index => runData[index] & 0xFF;
944+
const run_isSingleChunk = index => runData[index] & 0x100;
945+
const run_startIndex = index => runData[index + 1];
946+
const run_endIndex = index => runData[index + 2];
947+
const run_startColumn = index => runData[index + 3];
948+
const run_endColumn = index => runData[index + 4];
949+
950+
let { lines, longestLineInColumns, runData } = await splitTextIntoLinesAndRuns(text, progress);
934951
let animate = null;
935952
let lastLineIndex = lines.length - 1;
936953
let scrollX = 0;
@@ -985,61 +1002,61 @@
9851002
return { columnWidth, maxScrollX, maxScrollY, scrollbarX, scrollbarY };
9861003
}
9871004

988-
const emptyLine = { raw: '', runs: [] };
1005+
const emptyLine = { raw: '', runCount: 0 };
9891006

9901007
function analyzeLine(row, column, fractionalColumn, tabStopBehavior) {
9911008
let index = column;
9921009
let firstRun = 0;
9931010
let nearbyRun = 0;
994-
let { raw, runs, runsText } = row < lines.length ? lines[row] : emptyLine;
995-
let runCount = runs.length;
1011+
let { raw, runBase, runCount, runText } = row < lines.length ? lines[row] : emptyLine;
1012+
let runLimit = runCount;
9961013
let endOfLineIndex = 0;
9971014
let endOfLineColumn = 0;
9981015
let beforeNewlineIndex = 0;
9991016
let hasTrailingNewline = false;
10001017

1001-
if (runCount > 0) {
1002-
let lastRun = runs[runCount - 1];
1003-
endOfLineIndex = lastRun.endIndex;
1004-
endOfLineColumn = lastRun.endColumn;
1005-
beforeNewlineIndex = lastRun.startIndex;
1006-
hasTrailingNewline = (lastRun.whitespaceAndFlags & 0xFF) === 0x0A /* newline */;
1018+
if (runLimit > 0) {
1019+
let lastRun = runBase + 5 * (runLimit - 1);
1020+
endOfLineIndex = run_endIndex(lastRun);
1021+
endOfLineColumn = run_endColumn(lastRun);
1022+
beforeNewlineIndex = run_startIndex(lastRun);
1023+
hasTrailingNewline = run_whitespace(lastRun) === 0x0A /* newline */;
10071024

10081025
// Binary search to find the first run
10091026
firstRun = 0;
1010-
while (runCount > 0) {
1011-
let step = runCount >> 1;
1027+
while (runLimit > 0) {
1028+
let step = runLimit >> 1;
10121029
let it = firstRun + step;
1013-
if (runs[it].endColumn < column) {
1030+
if (run_endColumn(runBase + 5 * it) < column) {
10141031
firstRun = it + 1;
1015-
runCount -= step + 1;
1032+
runLimit -= step + 1;
10161033
} else {
1017-
runCount = step;
1034+
runLimit = step;
10181035
}
10191036
}
10201037

10211038
// Use the last run if we're past the end of the line
1022-
if (firstRun >= runs.length) firstRun--;
1039+
if (firstRun >= runCount) firstRun--;
10231040

10241041
// Convert column to index
10251042
nearbyRun = firstRun;
1026-
while (runs[nearbyRun].startColumn > column && nearbyRun > 0) nearbyRun--;
1027-
while (runs[nearbyRun].endColumn < column && nearbyRun + 1 < runs.length) nearbyRun++;
1028-
let run = runs[nearbyRun];
1029-
if ((run.whitespaceAndFlags & 0x100 /* isSingleChunk */) && column <= run.endColumn) {
1043+
while (run_startColumn(runBase + 5 * nearbyRun) > column && nearbyRun > 0) nearbyRun--;
1044+
while (run_endColumn(runBase + 5 * nearbyRun) < column && nearbyRun + 1 < runCount) nearbyRun++;
1045+
let run = runBase + 5 * nearbyRun;
1046+
if (run_isSingleChunk(run) && column <= run_endColumn(run)) {
10301047
// A special case for single-character blocks such as tabs and emoji
10311048
if (
1032-
(tabStopBehavior === 'round' && fractionalColumn >= (run.startColumn + run.endColumn) / 2) ||
1033-
(tabStopBehavior === 'floor' && fractionalColumn >= run.endColumn)
1049+
(tabStopBehavior === 'round' && fractionalColumn >= (run_startColumn(run) + run_endColumn(run)) / 2) ||
1050+
(tabStopBehavior === 'floor' && fractionalColumn >= run_endColumn(run))
10341051
) {
1035-
index = run.endIndex;
1036-
column = run.endColumn;
1052+
index = run_endIndex(run);
1053+
column = run_endColumn(run);
10371054
} else {
1038-
index = run.startIndex;
1039-
column = run.startColumn;
1055+
index = run_startIndex(run);
1056+
column = run_startColumn(run);
10401057
}
10411058
} else {
1042-
index = run.startIndex + column - run.startColumn;
1059+
index = run_startIndex(run) + column - run_startColumn(run);
10431060
}
10441061
}
10451062

@@ -1076,23 +1093,23 @@
10761093
function columnToIndex(column) {
10771094
// If there is no underlying line, just use one index per column
10781095
let index = column;
1079-
if (runs.length > 0) {
1080-
while (runs[nearbyRun].startColumn > column && nearbyRun > 0) nearbyRun--;
1081-
while (runs[nearbyRun].endColumn < column && nearbyRun + 1 < runs.length) nearbyRun++;
1082-
let run = runs[nearbyRun];
1083-
index = column === run.endColumn ? run.endIndex : run.endIndex + column - run.startColumn;
1096+
if (runCount > 0) {
1097+
while (run_startColumn(runBase + 5 * nearbyRun) > column && nearbyRun > 0) nearbyRun--;
1098+
while (run_endColumn(runBase + 5 * nearbyRun) < column && nearbyRun + 1 < runCount) nearbyRun++;
1099+
let run = runBase + 5 * nearbyRun;
1100+
index = column === run_endColumn(run) ? run_endIndex(run) : run_endIndex(run) + column - run_startColumn(run);
10841101
}
10851102
return index;
10861103
}
10871104

10881105
function indexToColumn(index) {
10891106
// If there is no underlying line, just use one column per index
10901107
let column = index;
1091-
if (runs.length > 0) {
1092-
while (runs[nearbyRun].startIndex > index && nearbyRun > 0) nearbyRun--;
1093-
while (runs[nearbyRun].endIndex < index && nearbyRun + 1 < runs.length) nearbyRun++;
1094-
let run = runs[nearbyRun];
1095-
column = index === run.endIndex ? run.endColumn : run.startColumn + index - run.startIndex;
1108+
if (runCount > 0) {
1109+
while (run_startIndex(runBase + 5 * nearbyRun) > index && nearbyRun > 0) nearbyRun--;
1110+
while (run_endIndex(runBase + 5 * nearbyRun) < index && nearbyRun + 1 < runCount) nearbyRun++;
1111+
let run = runBase + 5 * nearbyRun;
1112+
column = index === run_endIndex(run) ? run_endColumn(run) : run_startColumn(run) + index - run_startIndex(run);
10961113
}
10971114
return column;
10981115
}
@@ -1135,8 +1152,9 @@
11351152
index,
11361153
column,
11371154
firstRun,
1138-
runs,
1139-
runsText,
1155+
runBase,
1156+
runCount,
1157+
runText,
11401158
firstMapping,
11411159
endOfLineIndex,
11421160
endOfLineColumn,
@@ -1336,38 +1354,39 @@
13361354
let dx = x - scrollX + margin + textPaddingX;
13371355
let dy = y - scrollY + textPaddingY;
13381356
dy += (row + 0.7) * rowHeight;
1339-
const { raw, firstRun, runs, runsText, firstMapping, endOfLineColumn, rangeOfMapping, columnToIndex } = analyzeLine(row, firstColumn, firstColumn, 'floor');
1357+
const { raw, firstRun, runBase, runCount, runText, firstMapping, endOfLineColumn, rangeOfMapping, columnToIndex } = analyzeLine(row, firstColumn, firstColumn, 'floor');
13401358
const lastIndex = columnToIndex(lastColumn);
13411359

13421360
// Don't draw any text if the whole line is offscreen
1343-
if (firstRun < runs.length) {
1361+
if (firstRun < runCount) {
13441362
// Scan to find the last run
13451363
let lastRun = firstRun;
1346-
while (lastRun + 1 < runs.length && runs[lastRun + 1].startColumn < lastColumn) {
1364+
while (lastRun + 1 < runCount && run_startColumn(runBase + 5 * (lastRun + 1)) < lastColumn) {
13471365
lastRun++;
13481366
}
13491367

13501368
// Draw the runs
13511369
let currentColumn = firstColumn;
13521370
for (let i = firstRun; i <= lastRun; i++) {
1353-
const run = runs[i];
1354-
let { whitespaceAndFlags, startColumn, endColumn } = run;
1355-
let whitespace = whitespaceAndFlags & 0xFF;
1356-
let text = runsText[i];
1371+
const run = runBase + 5 * i;
1372+
let startColumn = run_startColumn(run);
1373+
let endColumn = run_endColumn(run);
1374+
let whitespace = run_whitespace(run);
1375+
let text = runText[i];
13571376

13581377
// Lazily-generate text for runs to improve performance. When
13591378
// this happens, the run text is the code unit offset of the
13601379
// start of the line containing this run.
13611380
if (text === void 0) {
1362-
text = runsText[i] =
1363-
!whitespace ? raw.slice(run.startIndex, run.endIndex) :
1364-
whitespace === 0x20 /* space */ ? '·'.repeat(run.endIndex - run.startIndex) :
1381+
text = runText[i] =
1382+
!whitespace ? raw.slice(run_startIndex(run), run_endIndex(run)) :
1383+
whitespace === 0x20 /* space */ ? '·'.repeat(run_endIndex(run) - run_startIndex(run)) :
13651384
whitespace === 0x0A /* newline */ ? row === lines.length - 1 ? '∅' : '↵' :
13661385
'→' /* tab */;
13671386
}
13681387

13691388
// Limit the run to the visible columns (but only for ASCII runs)
1370-
if (!(run.whitespaceAndFlags & 0x100 /* isSingleChunk */)) {
1389+
if (!run_isSingleChunk(run)) {
13711390
if (startColumn < currentColumn) {
13721391
text = text.slice(currentColumn - startColumn);
13731392
startColumn = currentColumn;

0 commit comments

Comments
 (0)