Skip to content

Commit

Permalink
Performance|libappfw|LogWidget: Improved/faster log entry processing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
skyjake committed Oct 21, 2014
1 parent ee85daf commit b121967
Showing 1 changed file with 82 additions and 32 deletions.
114 changes: 82 additions & 32 deletions doomsday/libappfw/src/widgets/logwidget.cpp
Expand Up @@ -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().

Expand Down Expand Up @@ -92,19 +94,22 @@ 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)
{
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;
Expand All @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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;
}
Expand All @@ -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);

Expand All @@ -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++;
}
Expand All @@ -295,6 +336,7 @@ public Font::RichFormat::IStyle
int _next;
TaskPool _pool;
int _width;
bool _paused { false };

struct WrappedEntries : public QList<CacheEntry *>, public Lockable {};
WrappedEntries _wrappedEntries; ///< New entries possibly created in background threads.
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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.
Expand All @@ -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;
}
}
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -770,9 +823,6 @@ public Font::RichFormat::IStyle

GLState::pop();
}

// We don't need to keep all entries ready for drawing immediately.
releaseExcessComposedEntries();
}
};

Expand Down

0 comments on commit b121967

Please sign in to comment.