Bug 538669 - Improve idle crawler [r=vingtetun,froystig]

This commit is contained in:
Benjamin Stover 2010-01-14 13:35:08 -08:00
Родитель 5a8131dc7a
Коммит 9c0d0703dc
4 изменённых файлов: 142 добавлений и 241 удалений

Просмотреть файл

@ -435,19 +435,6 @@ BrowserView.prototype = {
return (this._renderMode == 0);
},
/**
* @param dx Guess delta to destination x coordinate
* @param dy Guess delta to destination y coordinate
*/
onBeforeVisibleMove: function onBeforeVisibleMove(dx, dy) {
let vs = this._browserViewportState;
let vr = this.getVisibleRect();
let destCR = BrowserView.Util.visibleRectToCriticalRect(vr.translate(dx, dy), vs);
this._tileManager.beginCriticalMove(destCR);
},
onAfterVisibleMove: function onAfterVisibleMove() {
let vs = this._browserViewportState;
let vr = this.getVisibleRect();
@ -457,7 +444,7 @@ BrowserView.prototype = {
let cr = BrowserView.Util.visibleRectToCriticalRect(vr, vs);
this._tileManager.endCriticalMove(cr, this.isRendering());
this._tileManager.criticalMove(cr, this.isRendering());
},
/**

Просмотреть файл

@ -131,12 +131,12 @@ function TileManager(appendTile, removeTile, browserView, cacheSize) {
this._idleTileCrawlerTimeout = 0;
/* object that keeps state on our current prefetch crawl */
this._crawler = null;
this._crawler = new TileManager.CrawlIterator(this._tileCache, new Rect(0, 0, 0, 0));
/* remember these values to reduce the recenterEvictionQueue cost */
this._ctr = new Point(0, 0);
/* remember if critical rect was changed so that we only do hard work one time */
this._lastCriticalRect = new Rect(0, 0, 0, 0);
/* If true, render dirty tiles outside of a viewport on timer. */
/* if true, fetch offscreen dirty tiles in the "background" */
this._prefetch = false;
/* create one Image of the checkerboard to be reused */
@ -162,11 +162,10 @@ TileManager.prototype = {
let jBound = tc.jBound = Math.ceil(viewportRect.bottom / kTileHeight) - 1;
if (criticalRect.isEmpty() || !criticalRect.equals(this._criticalRect)) {
this.beginCriticalMove(criticalRect);
this.endCriticalMove(criticalRect, !(dirtyAll || boundsSizeChanged));
this.criticalMove(criticalRect, !(dirtyAll || boundsSizeChanged));
} else {
// The critical rect hasn't changed, but there are possibly more tiles lounging about offscreen,
// waiting to be rendered. Make sure crawler knows about them.
// waiting to be rendered. Make sure crawler and eviction knows about them.
this.recenterCrawler();
}
@ -255,10 +254,10 @@ TileManager.prototype = {
let criticalIsDirty = false;
let criticalRect = this._criticalRect;
let tc = this._tileCache;
let crawler = this._crawler;
let rect;
for (let i = rects.length - 1; i >= 0; --i) {
let rect = rects[i];
rect = rects[i];
BEGIN_FOREACH_IN_RECT(rect, tc, tile)
tile.clear(rect);
@ -284,8 +283,7 @@ TileManager.prototype = {
if (!tile.boundRect.intersects(criticalRect)) {
// Dirty tile outside of viewport. Just remove and redraw later.
this._removeTileSafe(tile);
if (crawler)
crawler.enqueue(tile.i, tile.j);
crawler.enqueue(tile.i, tile.j);
outsideIsDirty = true;
} else {
criticalIsDirty = true;
@ -304,69 +302,30 @@ TileManager.prototype = {
criticalRectPaint: function criticalRectPaint() {
let cr = this._criticalRect;
let lastCr = this._lastCriticalRect;
//let start = Date.now();
if (!cr.isEmpty()) {
let ctr = cr.center().map(Math.round);
if (!this._ctr.equals(ctr)) {
this._ctr.set(ctr);
this.recenterEvictionQueue(ctr);
this.recenterCrawler();
}
//let start = Date.now();
this._renderAppendHoldRect(cr);
//dump(" render, append, hold: " + (Date.now() - start) + "\n");
}
//dump(" paint: " + (Date.now() - start) + "\n");
},
beginCriticalMove: function beginCriticalMove(destCriticalRect) {
if (!destCriticalRect.isEmpty()) {
if (!lastCr.isEmpty()) {
// This is the first paint since the last critical move.
let tc = this._tileCache;
BEGIN_FOREACH_IN_RECT(destCriticalRect, tc, tile)
if (!tile.isDirty())
this._appendTileSafe(tile);
BEGIN_FOREACH_IN_RECT(lastCr, tc, tile)
tc.releaseTile(tile);
END_FOREACH_IN_RECT
lastCr.setRect(0, 0, 0, 0);
this.recenterEvictionQueue(cr.center().map(Math.round));
this.recenterCrawler();
}
if (!cr.isEmpty())
this._renderAppendHoldRect(cr);
},
endCriticalMove: function endCriticalMove(destCriticalRect, doCriticalPaint) {
let tc = this._tileCache;
criticalMove: function criticalMove(destCriticalRect, doCriticalPaint) {
let cr = this._criticalRect;
//let start = Date.now();
if (!cr.isEmpty()) {
BEGIN_FOREACH_IN_RECT(cr, tc, tile)
tc.releaseTile(tile);
END_FOREACH_IN_RECT
}
//dump(" release: " + (Date.now() - start) + "\n");
//start = Date.now();
// XXX the conjunction with doCriticalPaint may cause tiles to disappear
// (be evicted) during a (relatively slow) move as no tiles will be "held"
// until a critical paint is requested. Also, while we have this
// && doCriticalPaint then we don't need this loop altogether, as
// criticalRectPaint will hold everything for us (called below)
//if (destCriticalRect && doCriticalPaint) {
// BEGIN_FOREACH_IN_RECT(destCriticalRect, tc, tile)
// tc.holdTile(tile);
// END_FOREACH_IN_RECT
//}
//dump(" hold: " + (Date.now() - start) + "\n");
let lastCr = this._lastCriticalRect;
if (lastCr.isEmpty() && !cr.equals(destCriticalRect))
lastCr.copyFrom(cr);
cr.copyFrom(destCriticalRect);
if (doCriticalPaint)
@ -374,15 +333,18 @@ TileManager.prototype = {
},
setPrefetch: function setPrefetch(prefetch) {
this._prefetch = prefetch;
if (prefetch)
this.restartPrefetchCrawl();
else
this.stopPrefetchCrawl();
if (prefetch != this._prefetch) {
this._prefetch = prefetch;
if (prefetch)
this.restartPrefetchCrawl();
else
this.stopPrefetchCrawl();
}
},
restartPrefetchCrawl: function restartPrefetchCrawl() {
if (this._prefetch && !this._idleTileCrawlerTimeout && this._crawler)
if (this._prefetch && !this._idleTileCrawlerTimeout)
this._idleTileCrawlerTimeout = setTimeout(this._idleTileCrawler, kTileCrawlComeAgain, this);
},
@ -406,7 +368,8 @@ TileManager.prototype = {
/** Crawler will recalculate the tiles it is supposed to fetch in the background. */
recenterCrawler: function recenterCrawler() {
let cr = this._criticalRect;
this._crawler = new TileManager.CrawlIterator(this._tileCache, cr.clone());
this._crawler.recenter(cr.clone());
this.restartPrefetchCrawl();
},
/**
@ -463,7 +426,7 @@ TileManager.prototype = {
}
},
_renderTile: function _renderTile(tile) {
_showTile: function _showTile(tile) {
if (tile.isDirty()) {
/*
let ctx = tile._canvas.getContext("2d");
@ -479,6 +442,7 @@ TileManager.prototype = {
*/
tile.render(this._browserView);
}
this._appendTileSafe(tile);
},
_appendTileSafe: function _appendTileSafe(tile) {
@ -498,29 +462,31 @@ TileManager.prototype = {
_renderAppendHoldRect: function _renderAppendHoldRect(rect) {
let tc = this._tileCache;
// XXX this can evict crawl tiles that might just be rerendered later. It would be nice if
// getTile understood which tiles had priority so that we don't waste time.
BEGIN_FORCREATE_IN_RECT(rect, tc, tile)
this._renderTile(tile);
this._appendTileSafe(tile);
this._showTile(tile);
this._tileCache.holdTile(tile);
END_FORCREATE_IN_RECT
},
_idleTileCrawler: function _idleTileCrawler(self) {
if (!self) self = this;
if (!self)
self = this;
let start = Date.now();
let comeAgain = true;
let tile;
while ((Date.now() - start) <= kTileCrawlTimeCap) {
let tile = self._crawler.next();
tile = self._crawler.next();
if (!tile) {
comeAgain = false;
break;
}
self._appendTileSafe(tile);
self._renderTile(tile);
self._showTile(tile);
}
if (comeAgain) {
@ -596,35 +562,6 @@ TileManager.TileCache.prototype = {
getCapacity: function getCapacity() { return this._capacity; },
setCapacity: function setCapacity(newCap, skipEvictionQueueSort) {
if (newCap < 0)
throw "Cannot set a negative tile cache capacity";
if (newCap == Infinity) {
this._capacity = Infinity;
return;
} else if (this._capacity == Infinity) {
// pretend we had a finite capacity all along and proceed normally
this._capacity = this._tilePool.length;
}
if (newCap < this._capacity) {
// This case is obnoxious. We're decreasing our capacity which means
// we may have to get rid of tiles. Note that "throwing out" means the
// cache won't keep them, and they'll get GC'ed as soon as all other
// refholders let go of their refs to the tile.
if (!skipEvictionQueueSort)
this.sortEvictionQueue();
let rem = this._tilePool.splice(newCap);
for (let k = 0, len = rem.length; k < len; ++k)
this._detachTile(rem[k].i, rem[k].j);
}
this._capacity = newCap;
},
inBounds: function inBounds(i, j) {
return (0 <= i && 0 <= j && i <= this.iBound && j <= this.jBound);
},
@ -720,9 +657,11 @@ TileManager.TileCache.prototype = {
let pool = this._tilePool;
let victim = null;
let tile;
for (; k >= 0; --k) {
if (pool[k].free && ( !evictionGuard || evictionGuard(pool[k]) )) {
victim = pool[k];
tile = pool[k];
if (!this.inBounds(tile.i, tile.j) || tile.free && ( !evictionGuard || evictionGuard(tile) )) {
victim = tile;
--k;
break;
}
@ -907,148 +846,130 @@ TileManager.Tile.prototype = {
* FIFO queue, and calls to next() simply dequeue elements, which must be added with
* enqueue().
*
* @param tileCache The TileCache over whose tiles this CrawlIterator will crawl
* @param startRect [optional] The rectangle that we grow in the first (rectangle
* expansion) iteration state.
* @param tc The TileCache over whose tiles this CrawlIterator will crawl
* @param rect The rectangle that we grow in the first (rectangle * expansion)
* iteration state. If empty, doesn't crawl.
*/
TileManager.CrawlIterator = function CrawlIterator(tileCache, startRect) {
this._tileCache = tileCache;
this._stepRect = startRect;
TileManager.CrawlIterator = function CrawlIterator(tc, rect) {
this._tileCache = tc;
// used to remember tiles that we've reused during this crawl
this._visited = {};
this.recenter(rect);
};
// filters the tiles we've already reused once from being considered victims
// for reuse when we ask the tile cache to create a new tile
let visited = this._visited;
this._notVisited = function(tile) { return !visited[tile.toString()]; };
TileManager.CrawlIterator.prototype = {
_generateCrawlQueue: function _generateCrawlQueue(rect) {
function add(i, j) {
if (tc.inBounds(i, j)) {
outOfBounds = false;
result.push([i, j]);
--index;
return true;
}
return false;
}
// a generator that generates tile indices corresponding to tiles intersecting
// the boundary of an expanding rectangle
this._crawlIndices = !startRect ? null : (function indicesGenerator(rect, tc) {
let outOfBounds = false;
let tc = this._tileCache;
let capacity = tc.getCapacity();
let result = [];
let index = capacity;
let outOfBounds;
let counter;
let starti = rect.left >> kTileExponentWidth;
let endi = rect.right >> kTileExponentWidth;
let startj = rect.top >> kTileExponentHeight;
let endj = rect.bottom >> kTileExponentHeight;
let i, j;
while (!outOfBounds) {
rect.left -= kTileWidth; // expand the rect
rect.right += kTileWidth;
rect.top -= kTileHeight;
rect.bottom += kTileHeight;
let starti = rect.left >> kTileExponentWidth;
let endi = rect.right >> kTileExponentWidth;
let startj = rect.top >> kTileExponentHeight;
let endj = rect.bottom >> kTileExponentHeight;
let i, j;
starti -= 1;
endi += 1;
startj -= 1;
endj += 1;
outOfBounds = true;
// top, bottom rect borders
for each (let y in [rect.top, rect.bottom]) {
j = y >> kTileExponentHeight;
for (i = starti; i <= endi; ++i) {
if (tc.inBounds(i, j)) {
outOfBounds = false;
yield [i, j];
}
for each (j in [startj, endj]) {
for (counter = 1, i = Math.floor((starti + endi) / 2); i >= starti && i <= endi;) {
if (add(i, j) && index == 0)
return result;
i += counter;
counter = -counter + (counter > 0 ? -1 : 1);
}
}
// left, right rect borders
for each (let x in [rect.left, rect.right]) {
i = x >> kTileExponentWidth;
for (j = startj; j <= endj; ++j) {
if (tc.inBounds(i, j)) {
outOfBounds = false;
yield [i, j];
}
for each (i in [starti, endi]) {
counter = 1;
for (counter = 1, j = Math.floor((startj + endj) / 2); j >= startj && j <= endj;) {
if (add(i, j) && index == 0)
return result;
j += counter;
counter = -counter + (counter > 0 ? -1 : 1);
}
}
}
})(this._stepRect, this._tileCache), // instantiate the generator
// after we finish the rectangle iteration state, we enter the FIFO queue state
this._queueState = !startRect;
this._queue = [];
// used to prevent tiles from being enqueued twice --- "patience, we'll get to
// it in a moment"
this._enqueued = {};
};
TileManager.CrawlIterator.prototype = {
__iterator__: function() {
while (true) {
let tile = this.next();
if (!tile) break;
yield tile;
}
return result;
},
becomeQueue: function becomeQueue() {
this._queueState = true;
},
recenter: function recenter(rect) {
// Queue should not be very big, so put first priorities last in order to pop quickly.
this._crawlQueue = rect.isEmpty() ? [] : this._generateCrawlQueue(rect).reverse();
unbecomeQueue: function unbecomeQueue() {
this._queueState = false;
// used to remember tiles that we've reused during this crawl
this._visited = {}
// filters the tiles we've already reused once from being considered victims
// for reuse when we ask the tile cache to create a new tile
let visited = this._visited;
this._notVisited = function(tile) { return !visited[tile.i + "," + tile.j]; };
// after we finish the rectangle iteration state, we enter the FILO queue state
// no need to remember old dirty tiles, if it's important we'll get to it anyways
this._queue = [];
// use a dictionary to prevent tiles from being enqueued twice --- "patience, we'll get to
// it in a moment"
this._enqueued = {};
},
next: function next() {
if (this._queueState)
return this.dequeue();
// Priority for next goes to the crawl queue, dirty tiles afterwards. Since dirty
// tile queue does not really have a necessary order, pop off the top.
let coords = this._crawlQueue.pop() || this.dequeue();
let tile = null;
if (this._crawlIndices) {
try {
let [i, j] = this._crawlIndices.next();
tile = this._tileCache.getTile(i, j, true, this._notVisited);
} catch (e) {
if (!(e instanceof StopIteration))
throw e;
if (coords) {
let [i, j] = coords;
// getTile will create a tile only if there are any left in our capacity that have not been
// visited already by the crawler.
tile = this._tileCache.getTile(i, j, true, this._notVisited);
if (tile) {
this._visited[this._strIndices(i, j)] = true;
} else {
tile = this.next();
}
}
if (tile) {
this._visited[tile.toString()] = true;
} else {
this.becomeQueue();
return this.next();
}
return tile;
},
dequeue: function dequeue() {
let tile = null;
do {
let idx = this._queue.shift();
if (!idx)
if (this._queue.length) {
let [i, j] = this._queue.pop();
this._enqueued[this._strIndices(i, j)] = false;
return [i, j];
} else {
return null;
delete this._enqueued[idx];
let [i, j] = this._unstrIndices(idx);
tile = this._tileCache.getTile(i, j, false);
} while (!tile);
return tile;
}
},
enqueue: function enqueue(i, j) {
let idx = this._strIndices(i, j);
if (!this._enqueued[idx]) {
this._queue.push(idx);
this._enqueued[idx] = true;
let index = this._strIndices(i, j);
let enqueued = this._enqueued;
if (!enqueued[index]) {
this._queue.push([i, j]);
enqueued[index] = true;
}
},
_strIndices: function _strIndices(i, j) {
return i + "," + j;
},
_unstrIndices: function _unstrIndices(str) {
return str.split(',');
}
};

Просмотреть файл

@ -1270,7 +1270,7 @@ var FormHelper = {
this._helperSpacer.hidden = true;
// give the form spacer area back to the content
let bv = Browser._browserView;
bv.onBeforeVisibleMove(0, 0);
Browser.forceChromeReflow();
Browser.contentScrollboxScroller.scrollBy(0, 0);
bv.onAfterVisibleMove();

Просмотреть файл

@ -170,7 +170,7 @@ function onDebugKeyPress(ev) {
const a = 65; // debug all critical tiles
const b = 66; // dump an ASCII graphic of the tile map
const c = 67; // set tilecache capacity
const c = 67;
const d = 68; // debug dump
const e = 69;
const f = 70; // free memory by clearing a tab.
@ -265,11 +265,6 @@ function onDebugKeyPress(ev) {
case l:
bv._tileManager.restartLazyCrawl(bv._tileManager._criticalRect);
break;
case c:
let cap = parseInt(window.prompt('new capacity'));
bv._tileManager._tileCache.setCapacity(cap);
break;
case b:
window.tileMapMode = true;
@ -1419,8 +1414,6 @@ Browser.MainDragger.prototype = {
let doffset = new Point(dx, dy);
let render = false;
this.bv.onBeforeVisibleMove(dx, dy);
// First calculate any panning to take sidebars out of view
let panOffset = this._panControlsAwayOffset(doffset);