From 0ee6c1ea216f2b06283adc82c7d0c4ec821a8199 Mon Sep 17 00:00:00 2001 From: Ingo Weinhold Date: Tue, 10 Jun 2008 22:20:14 +0000 Subject: [PATCH] * TermView does now maintain an additional visible area sized BasicTerminalBuffer. It synchronizes this buffer with the actual terminal buffer and uses it for drawing. This does significantly decrease the time the terminal buffer is locked by the window thread, and avoids the necessity to do invalidations in Draw() when the terminal buffer changed in the meantime. * When the view detects heavy scrolling of the terminal buffer due to lots of continued output, it throttles redraws to one every 0.1s. The detection condition might need some fine-tuning -- it's probably a bit too strict for most situations. I guess I'm done now with optimizations. After testing on real iron for the first time, I'm a little impressed. In the "time seq ..." speed competition Haiku's Terminal easily beats Konsole by 10 to 20% for 80x25 and by factor 3+ for full screen. And if I interpret the results correctly 90% of the time is actually used by "seq" itself which seems to be about 5 times slower than under Linux. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@25917 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/apps/terminal/TermView.cpp | 207 ++++++++++++++++++++++++++++----- src/apps/terminal/TermView.h | 14 ++- 2 files changed, 191 insertions(+), 30 deletions(-) diff --git a/src/apps/terminal/TermView.cpp b/src/apps/terminal/TermView.cpp index c71dc37aa5..1063d44b68 100644 --- a/src/apps/terminal/TermView.cpp +++ b/src/apps/terminal/TermView.cpp @@ -94,6 +94,8 @@ static const uint32 kUpdateSigWinch = 'Rwin'; static const uint32 kBlinkCursor = 'BlCr'; static const uint32 kAutoScroll = 'AScr'; +static const bigtime_t kSyncUpdateGranularity = 100000; // 0.1 s + static const rgb_color kBlackColor = { 0, 0, 0, 255 }; static const rgb_color kWhiteColor = { 255, 255, 255, 255 }; @@ -207,6 +209,7 @@ TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize fTermColumns(COLUMNS_DEFAULT), fEncoding(M_UTF8), fTextBuffer(NULL), + fVisibleTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), fTextBackColor(kWhiteColor), @@ -216,6 +219,10 @@ TermView::TermView(BRect frame, int32 argc, const char **argv, int32 historySize fSelectBackColor(kBlackColor), fScrollOffset(0), fScrBufSize(historySize), + fLastSyncTime(0), + fScrolledSinceLastSync(0), + fSyncRunner(NULL), + fConsiderClockedSync(false), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -248,6 +255,7 @@ TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 h fTermColumns(columns), fEncoding(M_UTF8), fTextBuffer(NULL), + fVisibleTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), fTextBackColor(kWhiteColor), @@ -257,6 +265,10 @@ TermView::TermView(int rows, int columns, int32 argc, const char **argv, int32 h fSelectBackColor(kBlackColor), fScrollOffset(0), fScrBufSize(historySize), + fLastSyncTime(0), + fScrolledSinceLastSync(0), + fSyncRunner(NULL), + fConsiderClockedSync(false), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -301,6 +313,7 @@ TermView::TermView(BMessage *archive) fTermColumns(COLUMNS_DEFAULT), fEncoding(M_UTF8), fTextBuffer(NULL), + fVisibleTextBuffer(NULL), fScrollBar(NULL), fTextForeColor(kBlackColor), fTextBackColor(kWhiteColor), @@ -309,6 +322,10 @@ TermView::TermView(BMessage *archive) fSelectForeColor(kWhiteColor), fSelectBackColor(kBlackColor), fScrBufSize(1000), + fLastSyncTime(0), + fScrolledSinceLastSync(0), + fSyncRunner(NULL), + fConsiderClockedSync(false), fSelStart(-1, -1), fSelEnd(-1, -1), fMouseTracking(false), @@ -347,6 +364,10 @@ TermView::_InitObject(int32 argc, const char **argv) if (fTextBuffer == NULL) return B_NO_MEMORY; + fVisibleTextBuffer = new(std::nothrow) BasicTerminalBuffer; + if (fVisibleTextBuffer == NULL) + return B_NO_MEMORY; + // TODO: Make the special word chars user-settable! fCharClassifier = new(std::nothrow) CharClassifier( kDefaultSpecialWordChars); @@ -358,6 +379,11 @@ TermView::_InitObject(int32 argc, const char **argv) return error; fTextBuffer->SetEncoding(fEncoding); + error = fVisibleTextBuffer->Init(fTermColumns, fTermRows + 2, + fTermRows + 2); + if (error != B_OK) + return error; + fShell = new (std::nothrow) Shell(); if (fShell == NULL) return B_NO_MEMORY; @@ -392,8 +418,10 @@ TermView::~TermView() _DetachShell(); + delete fSyncRunner; delete fAutoScrollRunner; - delete fCharClassifier; + delete fCharClassifier; + delete fVisibleTextBuffer; delete fTextBuffer; delete shell; } @@ -474,8 +502,11 @@ TermView::SetTermSize(int rows, int columns, bool resize) { BAutolock _(fTextBuffer); - if (fTextBuffer->ResizeTo(columns, rows) != B_OK) + if (fTextBuffer->ResizeTo(columns, rows) != B_OK + || fVisibleTextBuffer->ResizeTo(columns, rows + 2, rows + 2) + != B_OK) { return Bounds(); + } } fTermRows = rows; @@ -493,6 +524,17 @@ TermView::SetTermSize(int rows, int columns, bool resize) if (resize) ResizeTo(rect.Width(), rect.Height()); + + + // synchronize the visible text buffer + { + BAutolock _(fTextBuffer); + + _SynchronizeWithTextBuffer(0, -1); + int32 offset = _LineAt(0); + fVisibleTextBuffer->SynchronizeWith(fTextBuffer, offset, offset, + offset + rows + 2); + } return rect; } @@ -858,12 +900,14 @@ TermView::_DrawCursor() BRect rect(fFontWidth * fCursor.x, _LineOffset(fCursor.y), 0, 0); rect.right = rect.left + fFontWidth - 1; rect.bottom = rect.top + fCursorHeight - 1; + int32 firstVisible = _LineAt(0); UTF8Char character; uint16 attr; bool selected = _CheckSelectedRegion(TermPos(fCursor.x, fCursor.y)); - if (fTextBuffer->GetChar(fCursor.y, fCursor.x, character, attr) == A_CHAR) { + if (fVisibleTextBuffer->GetChar(fCursor.y - firstVisible, fCursor.x, + character, attr) == A_CHAR) { int32 width; if (IS_WIDTH(attr)) width = 2; @@ -875,8 +919,8 @@ TermView::_DrawCursor() memcpy(buffer, character.bytes, bytes); buffer[bytes] = '\0'; - _DrawLinePart(fCursor.x * fFontWidth, (int32)_LineOffset(fCursor.y), - attr, buffer, width, selected, true, this); + _DrawLinePart(fCursor.x * fFontWidth, (int32)rect.top, attr, buffer, + width, selected, true, this); } else { if (selected) SetHighColor(fSelectBackColor); @@ -965,7 +1009,8 @@ TermView::Draw(BRect updateRect) // return; // } - BAutolock _(fTextBuffer); +// BAutolock _(fTextBuffer); + // debug_printf("TermView::Draw((%f, %f) - (%f, %f))\n", updateRect.left, // updateRect.top, updateRect.right, updateRect.bottom); // { @@ -982,11 +1027,12 @@ TermView::Draw(BRect updateRect) // } // } - _SynchronizeWithTextBuffer(&updateRect); +// _SynchronizeWithTextBuffer(&updateRect); int32 x1 = (int32)(updateRect.left) / fFontWidth; int32 x2 = (int32)(updateRect.right) / fFontWidth; + int32 firstVisible = _LineAt(0); int32 y1 = _LineAt(updateRect.top); int32 y2 = _LineAt(updateRect.bottom); @@ -999,7 +1045,8 @@ TermView::Draw(BRect updateRect) int32 k = x1; char buf[fTermColumns * 4 + 1]; - if (fTextBuffer->IsFullWidthChar(j, k)) +// if (fTextBuffer->IsFullWidthChar(j, k)) + if (fVisibleTextBuffer->IsFullWidthChar(j - firstVisible, k)) k--; if (k < 0) @@ -1009,9 +1056,12 @@ TermView::Draw(BRect updateRect) int32 lastColumn = x2; bool insideSelection = _CheckSelectedRegion(j, i, lastColumn); uint16 attr; - int32 count = fTextBuffer->GetString(j, i, lastColumn, buf, attr); -//debug_printf(" fTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n", -//j, i, lastColumn, count, (int)count, buf, insideSelection); +// int32 count = fTextBuffer->GetString(j, i, lastColumn, buf, attr); + int32 count = fVisibleTextBuffer->GetString(j - firstVisible, i, + lastColumn, buf, attr); + +//debug_printf(" fVisibleTextBuffer->GetString(%ld, %ld, %ld) -> (%ld, \"%.*s\"), selected: %d\n", +//j - firstVisible, i, lastColumn, count, (int)count, buf, insideSelection); if (count == 0) { BRect rect(fFontWidth * i, _LineOffset(j), @@ -1402,7 +1452,7 @@ TermView::MessageReceived(BMessage *msg) case MSG_TERMINAL_BUFFER_CHANGED: { BAutolock _(fTextBuffer); - _SynchronizeWithTextBuffer(NULL); + _SynchronizeWithTextBuffer(0, -1); break; } case MSG_SET_TERMNAL_TITLE: @@ -1445,6 +1495,12 @@ TermView::ScrollTo(BPoint where) if (diff == 0) return; + float bottom = Bounds().bottom; + int32 oldFirstLine = _LineAt(0); + int32 oldLastLine = _LineAt(bottom); + int32 newFirstLine = _LineAt(diff); + int32 newLastLine = _LineAt(bottom + diff); + fScrollOffset = where.y; // invalidate the current cursor position before scrolling @@ -1457,6 +1513,20 @@ TermView::ScrollTo(BPoint where) //sourceRect.left, sourceRect.top, sourceRect.right, sourceRect.bottom, //destRect.left, destRect.top, destRect.right, destRect.bottom); CopyBits(sourceRect, destRect); + + // sync visible text buffer with text buffer + if (newFirstLine != oldFirstLine || newLastLine != oldLastLine) { + if (newFirstLine != oldFirstLine) +{ +//debug_printf("fVisibleTextBuffer->ScrollBy(%ld)\n", newFirstLine - oldFirstLine); + fVisibleTextBuffer->ScrollBy(newFirstLine - oldFirstLine); +} + BAutolock _(fTextBuffer); + if (diff < 0) + _SynchronizeWithTextBuffer(newFirstLine, oldFirstLine - 1); + else + _SynchronizeWithTextBuffer(oldLastLine + 1, newLastLine); + } } @@ -1489,23 +1559,85 @@ TermView::_DoFileDrop(entry_ref &ref) /*! Text buffer must already be locked. */ void -TermView::_SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling) +TermView::_SynchronizeWithTextBuffer(int32 visibleDirtyTop, + int32 visibleDirtyBottom) { TerminalBufferDirtyInfo& info = fTextBuffer->DirtyInfo(); - -//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " -//"scrolled: %ld\n", info.dirtyTop, info.dirtyBottom, info.linesScrolled); - + int32 linesScrolled = info.linesScrolled; BRect bounds = Bounds(); int32 firstVisible = _LineAt(0); int32 lastVisible = _LineAt(bounds.bottom); int32 historySize = fTextBuffer->HistorySize(); - int32 linesScrolled = info.linesScrolled; + +//debug_printf("TermView::_SynchronizeWithTextBuffer(): dirty: %ld - %ld, " +//"scrolled: %ld, visible dirty: %ld - %ld\n", info.dirtyTop, info.dirtyBottom, +//info.linesScrolled, visibleDirtyTop, visibleDirtyBottom); + + bigtime_t now = system_time(); + bigtime_t timeElapsed = now - fLastSyncTime; + if (timeElapsed > 2 * kSyncUpdateGranularity) { + // last sync was ages ago + fLastSyncTime = now; + fScrolledSinceLastSync = linesScrolled; + } + + if (fSyncRunner == NULL) { + // We consider clocked syncing when more than a full screen height has + // been scrolled in less than a sync update period. Once we're + // actively considering it, the same condition will convince us to + // actually do it. + if (fScrolledSinceLastSync + linesScrolled <= fTermRows) { + // Condition doesn't hold yet. Reset if time is up, or otherwise + // keep counting. + if (timeElapsed > kSyncUpdateGranularity) { + fConsiderClockedSync = false; + fLastSyncTime = now; + fScrolledSinceLastSync = linesScrolled; + } else + fScrolledSinceLastSync += linesScrolled; + } else if (fConsiderClockedSync) { + // We are convinced -- create the sync runner. + fLastSyncTime = now; + fScrolledSinceLastSync = 0; + + BMessage message(MSG_TERMINAL_BUFFER_CHANGED); + fSyncRunner = new(std::nothrow) BMessageRunner(BMessenger(this), + &message, kSyncUpdateGranularity); + if (fSyncRunner != NULL && fSyncRunner->InitCheck() == B_OK) + return; + + delete fSyncRunner; + fSyncRunner = NULL; + } else { + // Looks interesting so far. Reset the counts and consider clocked + // syncing. + fConsiderClockedSync = true; + fLastSyncTime = now; + fScrolledSinceLastSync = 0; + } + } else if (timeElapsed < kSyncUpdateGranularity) { + // sync time not passed yet -- keep counting + fScrolledSinceLastSync += linesScrolled; + return; + } else if (fScrolledSinceLastSync + linesScrolled <= fTermRows) { + // time's up, but not enough happened + delete fSyncRunner; + fSyncRunner = NULL; + fLastSyncTime = now; + fScrolledSinceLastSync = linesScrolled; + } else { + // Things are still rolling, but the sync time's up. + fLastSyncTime = now; + fScrolledSinceLastSync = 0; + } bool doScroll = false; if (linesScrolled > 0) { _UpdateScrollBarRange(); + visibleDirtyTop -= linesScrolled; + visibleDirtyBottom -= linesScrolled; + if (firstVisible < 0) { firstVisible -= linesScrolled; lastVisible -= linesScrolled; @@ -1515,6 +1647,25 @@ TermView::_SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling) firstVisible = -historySize; doScroll = true; scrollOffset = -historySize * fFontHeight; + // We need to invalidate the lower linesScrolled lines of the + // visible text buffer, since those will be scrolled up and + // need to be replaced. We just use visibleDirty{Top,Bottom} + // for that purpose. Unless invoked from ScrollTo() (i.e. + // user-initiated scrolling) those are unused. In the unlikely + // case that the user is scrolling at the same time we may + // invalidate too many lines, since we have to extend the given + // region. + // Note that in the firstVisible == 0 case the new lines are + // already in the dirty region, so they will be updated anyway. + if (visibleDirtyTop <= visibleDirtyBottom) { + if (lastVisible < visibleDirtyTop) + visibleDirtyTop = lastVisible; + if (visibleDirtyBottom < lastVisible + linesScrolled) + visibleDirtyBottom = lastVisible + linesScrolled; + } else { + visibleDirtyTop = lastVisible + 1; + visibleDirtyBottom = lastVisible + linesScrolled; + } } else scrollOffset = fScrollOffset - linesScrolled * fFontHeight; @@ -1538,13 +1689,7 @@ TermView::_SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling) //destRect.left, destRect.top, destRect.right, destRect.bottom); CopyBits(sourceRect, destRect); - if (invalidateWhenScrolling != NULL) -{ - Invalidate(invalidateWhenScrolling->OffsetByCopy(0, -scrollBy)); -//BRect rect(invalidateWhenScrolling->OffsetByCopy(0, scrollBy)); -//debug_printf("Invalidate((%f, %f) - (%f, %f))\n", rect.left, rect.top, -//rect.right, rect.bottom); -} + fVisibleTextBuffer->ScrollBy(linesScrolled); } // move selection @@ -1577,6 +1722,14 @@ TermView::_SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling) } } + if (visibleDirtyTop <= visibleDirtyBottom) + info.ExtendDirtyRegion(visibleDirtyTop, visibleDirtyBottom); + + if (linesScrolled != 0 || info.IsDirtyRegionValid()) { + fVisibleTextBuffer->SynchronizeWith(fTextBuffer, firstVisible, + info.dirtyTop, info.dirtyBottom); + } + // invalidate cursor, if it changed TermPos cursor = fTextBuffer->Cursor(); if (fCursor != cursor) { @@ -1792,7 +1945,7 @@ TermView::_Select(TermPos start, TermPos end, bool inclusive, { BAutolock _(fTextBuffer); - _SynchronizeWithTextBuffer(NULL); + _SynchronizeWithTextBuffer(0, -1); if (end < start) std::swap(start, end); @@ -2008,7 +2161,7 @@ TermView::Find(const BString &str, bool forwardSearch, bool matchCase, bool matchWord) { BAutolock _(fTextBuffer); - _SynchronizeWithTextBuffer(NULL); + _SynchronizeWithTextBuffer(0, -1); TermPos start; if (_HasSelection()) { diff --git a/src/apps/terminal/TermView.h b/src/apps/terminal/TermView.h index 9317c3406b..4a89574e58 100644 --- a/src/apps/terminal/TermView.h +++ b/src/apps/terminal/TermView.h @@ -130,7 +130,8 @@ private: void _UpdateScrollBarRange(); void _DoFileDrop(entry_ref &ref); - void _SynchronizeWithTextBuffer(BRect* invalidateWhenScrolling); + void _SynchronizeWithTextBuffer(int32 visibleDirtyTop, + int32 visibleDirtyBottom); void _WritePTY(const char* text, int32 numBytes); @@ -201,8 +202,9 @@ private: int fEncoding; // Object pointer. - TerminalBuffer *fTextBuffer; - BScrollBar *fScrollBar; + TerminalBuffer *fTextBuffer; + BasicTerminalBuffer *fVisibleTextBuffer; + BScrollBar *fScrollBar; // Color and Attribute. rgb_color fTextForeColor, fTextBackColor; @@ -216,6 +218,12 @@ private: // buffer is created. float fAutoScrollSpeed; + // redraw management + bigtime_t fLastSyncTime; + int32 fScrolledSinceLastSync; + BMessageRunner* fSyncRunner; + bool fConsiderClockedSync; + // selection TermPos fSelStart; TermPos fSelEnd;