Optimize Fl_Text_Display scrolling speed (#596).

This commit is contained in:
Matthias Melcher 2024-08-04 00:32:05 +02:00
parent bc73580366
commit 9bb9cb3f96
3 changed files with 107 additions and 8 deletions

View File

@ -635,6 +635,13 @@ public:
*/ */
int count_lines(int startPos, int endPos) const; int count_lines(int startPos, int endPos) const;
/**
Estimate the number of newlines between \p startPos and \p endPos in buffer.
This call takes line wrapping into account. It assumes a line break at every
`lineLen` characters after the beginning of a line.
*/
int estimate_lines(int startPos, int endPos, int lineLen) const;
/** /**
Finds the first character of the line \p nLines forward from \p startPos Finds the first character of the line \p nLines forward from \p startPos
in the buffer and returns its position. in the buffer and returns its position.

View File

@ -1166,6 +1166,48 @@ int Fl_Text_Buffer::count_lines(int startPos, int endPos) const {
return lineCount; return lineCount;
} }
/**
Estimate the number of newlines between \p startPos and \p endPos in buffer.
This call takes line wrapping into account. It assumes a line break at every
`lineLen` characters after the beginning of a line.
*/
int Fl_Text_Buffer::estimate_lines(int startPos, int endPos, int lineLen) const
{
IS_UTF8_ALIGNED2(this, (startPos))
IS_UTF8_ALIGNED2(this, (endPos))
int gapLen = mGapEnd - mGapStart;
int lineCount = 0;
int softLineBreaks = 0, softLineBreakCount = lineLen;
int pos = startPos;
while (pos < mGapStart)
{
if (pos == endPos)
return lineCount + softLineBreaks;
if (mBuf[pos++] == '\n') {
softLineBreakCount = lineLen;
lineCount++;
}
if (--softLineBreakCount == 0) {
softLineBreakCount = lineLen;
softLineBreaks++;
}
}
while (pos < mLength) {
if (pos == endPos)
return lineCount + softLineBreaks;
if (mBuf[pos++ + gapLen] == '\n') {
softLineBreakCount = lineLen;
lineCount++;
}
if (--softLineBreakCount == 0) {
softLineBreakCount = lineLen;
softLineBreaks++;
}
}
return lineCount + softLineBreaks;
}
/* /*
Skip to the first character, n lines ahead. Skip to the first character, n lines ahead.

View File

@ -560,9 +560,9 @@ void Fl_Text_Display::recalc_display() {
if (mContinuousWrap && !mWrapMarginPix && text_area.w != oldTAWidth) { if (mContinuousWrap && !mWrapMarginPix && text_area.w != oldTAWidth) {
int oldFirstChar = mFirstChar; int oldFirstChar = mFirstChar;
mNBufferLines = count_lines(0, buffer()->length(), true);
mFirstChar = line_start(mFirstChar); mFirstChar = line_start(mFirstChar);
mTopLineNum = count_lines(0, mFirstChar, true)+1; mTopLineNum = count_lines(0, mFirstChar, true)+1;
mNBufferLines = mTopLineNum-1 + count_lines(mFirstChar, buffer()->length(), true);
absolute_top_line_number(oldFirstChar); absolute_top_line_number(oldFirstChar);
#ifdef DEBUG2 #ifdef DEBUG2
printf(" mNBufferLines=%d\n", mNBufferLines); printf(" mNBufferLines=%d\n", mNBufferLines);
@ -1436,6 +1436,56 @@ int Fl_Text_Display::count_lines(int startPos, int endPos,
if (!mContinuousWrap) if (!mContinuousWrap)
return buffer()->count_lines(startPos, endPos); return buffer()->count_lines(startPos, endPos);
/*
Correctly counting wrapped lines is very slow. We have to query the length
of every segment of text for every line change and style change and find
potential soft line breaks.
Most of the resulting information is needed for calculating the vertical
scroll bar size. After a certain text length, the scroll bar size is no
longer very precise anyway, so we optimize line count for all lines but
the visible ones (plus minus a few lines for rounding).
The optimized code is several magnitudes faster and makes scrolling and
window resizing of long texts quite responsive. There is a slight but IMHO
tollerable drawback: when walking huge files using arrow up and down, the
text display sometimes jumps 2 or 3 lines instead of 1, but the overall
buffer stays intact as well as the scroll position.
*/
if (buffer()->length() > 16384) {
// Optimized line counting
int nLines = 0;
int firstVisibleChar = buffer()->rewind_lines(mFirstChar, 3);
int lastVisibleChar = buffer()->skip_lines(mLastChar, 3);
// Calculate the averga number of characters up to a soft line break
if (mColumnScale==0.0) x_to_col(1.0);
int avgCharsPerLine = mWrapMarginPix;
if (!avgCharsPerLine) avgCharsPerLine = text_area.w;
avgCharsPerLine = (int)(avgCharsPerLine / mColumnScale) + 1;
// first segment, lines up to display, count fast
if (startPos < firstVisibleChar) {
int tmpEnd = endPos<firstVisibleChar ? endPos : firstVisibleChar;
nLines += buffer()->estimate_lines(startPos, tmpEnd, avgCharsPerLine);
startPos = tmpEnd;
}
// second segement, count displayed liens
if (startPos < endPos && startPos < mLastChar) {
// Precisse line counting only for visible text:
int tmpEnd = endPos<lastVisibleChar ? endPos : lastVisibleChar;
wrapped_line_counter(buffer(), startPos, tmpEnd, INT_MAX,
startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
&retLineEnd);
nLines += retLines;
startPos = tmpEnd;
}
// third segement is everything after displayed lines
if (startPos < endPos && startPos >= lastVisibleChar) {
nLines += buffer()->estimate_lines(startPos, endPos, avgCharsPerLine);
}
return nLines;
} else {
// Precise line counting only for small text buffer sizes:
wrapped_line_counter(buffer(), startPos, endPos, INT_MAX, wrapped_line_counter(buffer(), startPos, endPos, INT_MAX,
startPosIsLineStart, 0, &retPos, &retLines, &retLineStart, startPosIsLineStart, 0, &retPos, &retLines, &retLineStart,
&retLineEnd); &retLineEnd);
@ -1444,9 +1494,9 @@ int Fl_Text_Display::count_lines(int startPos, int endPos,
printf(" # after WLC: retPos=%d, retLines=%d, retLineStart=%d, retLineEnd=%d\n", printf(" # after WLC: retPos=%d, retLines=%d, retLineStart=%d, retLineEnd=%d\n",
retPos, retLines, retLineStart, retLineEnd); retPos, retLines, retLineStart, retLineEnd);
#endif // DEBUG #endif // DEBUG
return retLines; return retLines;
} }
}