From b121967cfd6899ee815a38cedd23f831a384741d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaakko=20Kera=CC=88nen?= Date: Tue, 21 Oct 2014 19:27:56 +0300 Subject: [PATCH] Performance|libappfw|LogWidget: Improved/faster log entry processing One of the major performance bottlenecks was that entries were being processed even when the log was not really visible. Applied other rules for avoiding excessive work, for instance pausing the incoming messages for a while to allow the scrolling to catch up. Todo for later: There is still room for improvement; at no point should the log remain blank for significant periods of time... Also should rethink strategic points when the clear the entire atlas and/or trigger a (cheap) defragmentation. The pausing should also be unnecessary if one could "look ahead" and see that the entire set of visible entries will not be needed any more, just clear everything before starting an update. Currently the individual allocations are released one by one. --- doomsday/libappfw/src/widgets/logwidget.cpp | 114 ++++++++++++++------ 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/doomsday/libappfw/src/widgets/logwidget.cpp b/doomsday/libappfw/src/widgets/logwidget.cpp index 83204ded78..49c85a1d66 100644 --- a/doomsday/libappfw/src/widgets/logwidget.cpp +++ b/doomsday/libappfw/src/widgets/logwidget.cpp @@ -57,6 +57,8 @@ public Font::RichFormat::IStyle */ class CacheEntry { + bool _needWrap { true }; + int _wrapWidth { 0 }; int _height; ///< Current height of the entry, in pixels. int _oldHeight; ///< Previous height, before calling updateVisibility(). @@ -92,10 +94,11 @@ public Font::RichFormat::IStyle return drawable.isReady(); } - void wrap(String const &richText, int width) + void setupWrap(String const &richText, int width) { - drawable.setLineWrapWidth(width); drawable.setText(richText); + _needWrap = true; + _wrapWidth = width; } void rewrap(int width) @@ -103,8 +106,10 @@ public Font::RichFormat::IStyle drawable.setLineWrapWidth(width); } - /// Returns the possible delta in the height of the entry. - /// Does not block even though a long wrapping task is in progress. + /** + * Returns the possible delta in the height of the entry. + * Does not block even though a long wrapping task is in progress. + */ int update() { int const old = _height; @@ -116,6 +121,15 @@ public Font::RichFormat::IStyle return 0; } + void beginWrap() + { + if(_needWrap) + { + drawable.setLineWrapWidth(_wrapWidth); + _needWrap = false; + } + } + /** * Updates the entry's visibility: which lines might be visible to the user * and thus need to be allocated on an atlas and ready to draw. @@ -127,6 +141,9 @@ public Font::RichFormat::IStyle */ int updateVisibility(int yBottom, Rangei const &visiblePixels) { + // If the wrapping hasn't been started yet for this item, do so now. + beginWrap(); + int heightDelta = 0; // Remember the height we had prior to any updating. @@ -243,6 +260,7 @@ public Font::RichFormat::IStyle void remove(int pos, int n = 1) { DENG2_GUARD(this); + DENG2_ASSERT(pos + n <= _next); MemoryLogSink::remove(pos, n); _next -= n; } @@ -262,15 +280,34 @@ public Font::RichFormat::IStyle { DENG2_GUARD(_wrappedEntries); if(_wrappedEntries.isEmpty()) return 0; + //if(_wrappedEntries.first()->drawable.isBeingWrapped()) return 0; return _wrappedEntries.takeFirst(); } + /** + * Pauses the sink so that it doesn't produce cached entries any more. + * This will allow the widget to catch up. + */ + void setPaused(bool pause) + { + if(_paused != pause) + { + _paused = pause; + if(!_paused) beginWorkOnNext(); + } + } + + bool isPaused() const + { + return _paused; + } + /** * Schedules wrapping tasks for all incoming entries. */ void beginWorkOnNext() { - if(!d->formatter) return; // Must have a formatter. + if(isPaused() || !d->formatter) return; // Must have a formatter. DENG2_GUARD(this); @@ -280,10 +317,14 @@ public Font::RichFormat::IStyle String const styled = d->formatter->logEntryToTextLines(ent).at(0); CacheEntry *cached = new CacheEntry(*d->font, *d, *d->entryAtlas); - cached->wrap(styled, _width); + cached->setupWrap(styled, _width); - DENG2_GUARD(_wrappedEntries); - _wrappedEntries << cached; + // The cached entry will be passed to the widget when it's ready to + // receive new ones. + { + DENG2_GUARD(_wrappedEntries); + _wrappedEntries << cached; + } _next++; } @@ -295,6 +336,7 @@ public Font::RichFormat::IStyle int _next; TaskPool _pool; int _width; + bool _paused { false }; struct WrappedEntries : public QList, public Lockable {}; WrappedEntries _wrappedEntries; ///< New entries possibly created in background threads. @@ -369,24 +411,14 @@ public Font::RichFormat::IStyle clearCache(); } - void cancelRewraps() - { - // Cancel all wraps. - /// @todo TextDrawable does not support cancelling. - } - void clearCache() { - cancelRewraps(); - entryAtlas->clear(); - cache.clear(); + cache.clear(); // Ongoing text wrapping cancelled automatically. } void updateStyle() { - // TODO -- stop wrapping tasks in the sink - Style const &st = style(); font = &self.font(); @@ -555,7 +587,7 @@ public Font::RichFormat::IStyle { if(visibleRange < 0) return; - int len = de::max(10, visibleRange.size()); + int len = de::max(10, visibleRange.size()/2); // Excess entries before the visible range. int excess = visibleRange.start - len; @@ -593,18 +625,15 @@ public Font::RichFormat::IStyle { DENG2_ASSERT_IN_MAIN_THREAD(); - // We must lock the sink during this so no new entries are added. - DENG2_GUARD(sink); - // Remove oldest excess entries. - int num = sink.entryCount() - sink.maxEntries(); + int num = cache.size() - sink.maxEntries(); if(num > 0) { // There is one sink entry and one cached entry for each log entry. sink.remove(0, num); for(int i = 0; i < num; ++i) { - self.modifyContentHeight(-cache[0]->height()); + self.modifyContentHeight(-cache.first()->height()); delete cache.takeFirst(); } } @@ -646,13 +675,20 @@ public Font::RichFormat::IStyle int initialYBottom = contentSize.y + self.scrollPositionY().valuei(); contentOffsetForDrawing = std::ceil(contentOffset.value()); - Rangei const visiblePixelRange = extendPixelRangeWithPadding( + Rangei visiblePixelRange = extendPixelRangeWithPadding( Rangei(-contentOffsetForDrawing, contentSize.y - contentOffsetForDrawing)); + if(!isVisible()) + { + // The widget is hidden, so there's no point in loading anything into the atlas. + visiblePixelRange = Rangei(); // Nothing to be seen. + } for(int attempt = 0; attempt < 2; ++attempt) { if(entryAtlasFull) { + // Hopefully releasing some entries will make it possible to fit the + // new entries. releaseAllNonVisibleEntries(); entryAtlasFull = false; } @@ -688,9 +724,6 @@ public Font::RichFormat::IStyle if(entry->isReady() && yBottom + contentOffsetForDrawing <= contentSize.y) { - //gotReady = true; - - // Rasterize and allocate if needed. entry->make(verts, yBottom); // Update the visible range. @@ -703,6 +736,12 @@ public Font::RichFormat::IStyle if(entryAtlasLayoutChanged || entryAtlasFull) { + if(entryAtlasFull) + { + // We're full at the moment so let's delay adding any new + // entries for a while. + sink.setPaused(true); + } goto nextAttempt; } } @@ -731,6 +770,20 @@ public Font::RichFormat::IStyle emit self.contentHeightIncreased(heightDelta); } } + + // We don't need to keep all entries ready for drawing immediately. + releaseExcessComposedEntries(); + + if(contentOffset.done()) + { + sink.setPaused(false); + } + } + + bool isVisible() const + { + Rectanglei vp = self.viewport(); + return vp.height() > 0 && vp.right() >= 0; } void draw() @@ -770,9 +823,6 @@ public Font::RichFormat::IStyle GLState::pop(); } - - // We don't need to keep all entries ready for drawing immediately. - releaseExcessComposedEntries(); } };