2009-07-17 17:14:49 -07:00
|
|
|
// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
|
|
|
|
/*
|
|
|
|
* ***** BEGIN LICENSE BLOCK *****
|
|
|
|
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
|
|
|
|
*
|
|
|
|
* The contents of this file are subject to the Mozilla Public License Version
|
|
|
|
* 1.1 (the "License"); you may not use this file except in compliance with
|
|
|
|
* the License. You may obtain a copy of the License at
|
|
|
|
* http://www.mozilla.org/MPL/
|
|
|
|
*
|
|
|
|
* Software distributed under the License is distributed on an "AS IS" basis,
|
|
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
|
|
|
|
* for the specific language governing rights and limitations under the
|
|
|
|
* License.
|
|
|
|
*
|
|
|
|
* The Original Code is Mozilla Mobile Browser.
|
|
|
|
*
|
|
|
|
* The Initial Developer of the Original Code is
|
|
|
|
* Mozilla Corporation.
|
|
|
|
* Portions created by the Initial Developer are Copyright (C) 2009
|
|
|
|
* the Initial Developer. All Rights Reserved.
|
|
|
|
*
|
|
|
|
* Contributor(s):
|
|
|
|
* Roy Frostig <rfrostig@mozilla.com>
|
|
|
|
* Stuart Parmenter <stuart@mozilla.com>
|
|
|
|
*
|
|
|
|
* Alternatively, the contents of this file may be used under the terms of
|
|
|
|
* either the GNU General Public License Version 2 or later (the "GPL"), or
|
|
|
|
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
|
|
|
|
* in which case the provisions of the GPL or the LGPL are applicable instead
|
|
|
|
* of those above. If you wish to allow use of your version of this file only
|
|
|
|
* under the terms of either the GPL or the LGPL, and not to allow others to
|
|
|
|
* use your version of this file under the terms of the MPL, indicate your
|
|
|
|
* decision by deleting the provisions above and replace them with the notice
|
|
|
|
* and other provisions required by the GPL or the LGPL. If you do not delete
|
|
|
|
* the provisions above, a recipient may use your version of this file under
|
|
|
|
* the terms of any one of the MPL, the GPL or the LGPL.
|
|
|
|
*
|
|
|
|
* ***** END LICENSE BLOCK ***** */
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
#define BEGIN_FOREACH_IN_RECT(rect, tilecache, tile) \
|
2009-07-20 18:32:52 -07:00
|
|
|
{ \
|
2009-07-30 17:31:09 -07:00
|
|
|
let __starti = (rect).left >> kTileExponentWidth; \
|
|
|
|
let __endi = (rect).right >> kTileExponentWidth; \
|
|
|
|
let __startj = (rect).top >> kTileExponentHeight; \
|
|
|
|
let __endj = (rect).bottom >> kTileExponentHeight; \
|
2009-07-20 18:32:52 -07:00
|
|
|
\
|
|
|
|
let tile = null; \
|
2009-07-30 17:31:09 -07:00
|
|
|
let __i, __j; \
|
|
|
|
for (__j = __startj; __j <= __endj; ++__j) { \
|
|
|
|
for (__i = __starti; __i <= __endi; ++__i) { \
|
|
|
|
tile = (tilecache).getTile(__i, __j, false, null); \
|
|
|
|
if (tile) {
|
|
|
|
|
|
|
|
#define END_FOREACH_IN_RECT \
|
2009-07-20 18:32:52 -07:00
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
}
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
#define BEGIN_FORCREATE_IN_RECT(rect, tilecache, tile) \
|
2009-07-20 18:32:52 -07:00
|
|
|
{ \
|
2009-07-30 17:31:09 -07:00
|
|
|
let __starti = (rect).left >> kTileExponentWidth; \
|
|
|
|
let __endi = (rect).right >> kTileExponentWidth; \
|
|
|
|
let __startj = (rect).top >> kTileExponentHeight; \
|
|
|
|
let __endj = (rect).bottom >> kTileExponentHeight; \
|
2009-07-20 18:32:52 -07:00
|
|
|
\
|
|
|
|
let tile = null; \
|
2009-07-30 17:31:09 -07:00
|
|
|
let __i, __j; \
|
|
|
|
for (__j = __startj; __j <= __endj; ++__j) { \
|
|
|
|
for (__i = __starti; __i <= __endi; ++__i) { \
|
|
|
|
tile = (tilecache).getTile(__i, __j, true, null); \
|
2009-07-20 18:32:52 -07:00
|
|
|
if (tile) { \
|
2009-07-30 17:31:09 -07:00
|
|
|
|
|
|
|
#define END_FORCREATE_IN_RECT \
|
2009-07-20 18:32:52 -07:00
|
|
|
} \
|
|
|
|
} \
|
|
|
|
} \
|
|
|
|
}
|
2009-07-29 12:51:24 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
#define FOREACH_IN_RECT(rect, tilecache, fn, thisObj) \
|
|
|
|
BEGIN_FOREACH_IN_RECT(rect, tilecache, tile) \
|
|
|
|
(fn).call((thisObj), tile); \
|
|
|
|
END_FOREACH_IN_RECT
|
|
|
|
|
|
|
|
#define FORCREATE_IN_RECT(rect, tilecache, fn, thisObj) \
|
|
|
|
BEGIN_FORCREATE_IN_RECT(rect, tilecache, tile) \
|
|
|
|
(fn).call((thisObj), tile); \
|
|
|
|
END_FORCREATE_IN_RECT
|
|
|
|
|
2009-07-20 18:32:52 -07:00
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
const kXHTMLNamespaceURI = "http://www.w3.org/1999/xhtml";
|
|
|
|
|
|
|
|
// base-2 exponent for width, height of a single tile.
|
2009-10-05 20:14:57 -07:00
|
|
|
const kTileExponentWidth = 9;
|
|
|
|
const kTileExponentHeight = 9;
|
|
|
|
const kTileWidth = Math.pow(2, kTileExponentWidth); // 2^9 = 512
|
|
|
|
const kTileHeight = Math.pow(2, kTileExponentHeight); // 2^9 = 512
|
2009-10-09 20:03:14 -07:00
|
|
|
const kTileCrawlTimeCap = 100; // millis
|
|
|
|
const kTileCrawlComeAgain = 0; // millis
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* The Tile Manager!
|
|
|
|
*
|
|
|
|
* @param appendTile The function the tile manager should call in order to
|
|
|
|
* "display" a tile (e.g. append it to the DOM). The argument to this
|
|
|
|
* function is a TileManager.Tile object.
|
|
|
|
* @param removeTile The function the tile manager should call in order to
|
|
|
|
* "undisplay" a tile (e.g. remove it from the DOM). The argument to this
|
|
|
|
* function is a TileManager.Tile object.
|
|
|
|
* @param fakeWidth The width of the widest possible visible rectangle, e.g.
|
|
|
|
* the width of the screen. This is used in setting the zoomLevel.
|
|
|
|
*/
|
2009-10-19 13:22:17 -07:00
|
|
|
function TileManager(appendTile, removeTile, browserView, cacheSize) {
|
2009-07-31 15:32:48 -07:00
|
|
|
/* backref to the BrowserView object that owns us */
|
2009-07-17 17:14:49 -07:00
|
|
|
this._browserView = browserView;
|
|
|
|
|
2009-07-31 15:32:48 -07:00
|
|
|
/* callbacks to append / remove a tile to / from the parent */
|
2009-07-17 17:14:49 -07:00
|
|
|
this._appendTile = appendTile;
|
|
|
|
this._removeTile = removeTile;
|
|
|
|
|
2009-07-31 15:32:48 -07:00
|
|
|
/* tile cache holds tile objects and pools them under a given capacity */
|
2009-07-17 17:14:49 -07:00
|
|
|
let self = this;
|
|
|
|
this._tileCache = new TileManager.TileCache(function(tile) { self._removeTileSafe(tile); },
|
2009-10-19 13:22:17 -07:00
|
|
|
-1, -1, cacheSize);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-31 15:32:48 -07:00
|
|
|
/* Rectangle within the viewport that is visible to the user. It is "critical"
|
|
|
|
* in the sense that it must be rendered as soon as it becomes dirty, and tiles
|
|
|
|
* within this rectangle should not be evicted for use elsewhere. */
|
2009-10-20 19:16:06 -07:00
|
|
|
this._criticalRect = new Rect(0, 0, 0, 0);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-31 15:32:48 -07:00
|
|
|
/* timeout of the non-visible-tiles-crawler to cache renders from the browser */
|
2009-07-17 17:14:49 -07:00
|
|
|
this._idleTileCrawlerTimeout = 0;
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
/* object that keeps state on our current prefetch crawl */
|
2009-07-17 17:14:49 -07:00
|
|
|
this._crawler = null;
|
2009-10-09 20:03:14 -07:00
|
|
|
|
|
|
|
/* remember these values to reduce the recenterEvictionQueue cost */
|
2009-10-15 12:50:32 -07:00
|
|
|
this._ctr = new Point(0, 0);
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
TileManager.prototype = {
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/**
|
2009-08-06 18:44:20 -07:00
|
|
|
* Entry point by which the BrowserView informs of changes to the viewport or
|
|
|
|
* critical rect.
|
2009-07-30 17:31:09 -07:00
|
|
|
*/
|
2009-07-17 17:14:49 -07:00
|
|
|
viewportChangeHandler: function viewportChangeHandler(viewportRect,
|
|
|
|
criticalRect,
|
|
|
|
boundsSizeChanged,
|
|
|
|
dirtyAll) {
|
|
|
|
let tc = this._tileCache;
|
|
|
|
|
|
|
|
tc.iBound = Math.ceil(viewportRect.right / kTileWidth);
|
|
|
|
tc.jBound = Math.ceil(viewportRect.bottom / kTileHeight);
|
|
|
|
|
2009-10-20 19:16:06 -07:00
|
|
|
if (criticalRect.isEmpty() || !criticalRect.equals(this._criticalRect)) {
|
2009-07-17 17:14:49 -07:00
|
|
|
this.beginCriticalMove(criticalRect);
|
|
|
|
this.endCriticalMove(criticalRect, !boundsSizeChanged);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (boundsSizeChanged) {
|
|
|
|
// TODO fastpath if !dirtyAll
|
|
|
|
this.dirtyRects([viewportRect.clone()], true);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
dirtyRects: function dirtyRects(rects, doCriticalRender) {
|
|
|
|
let criticalIsDirty = false;
|
|
|
|
let criticalRect = this._criticalRect;
|
2009-07-30 17:31:09 -07:00
|
|
|
let tc = this._tileCache;
|
|
|
|
let crawler = this._crawler;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
for (let i = 0, len = rects.length; i < len; ++i) {
|
|
|
|
let rect = rects[i];
|
|
|
|
|
|
|
|
BEGIN_FOREACH_IN_RECT(rect, tc, tile)
|
|
|
|
|
2009-10-20 19:16:06 -07:00
|
|
|
if (!tile.boundRect.intersects(criticalRect))
|
2009-07-30 17:31:09 -07:00
|
|
|
this._removeTileSafe(tile);
|
2009-07-30 18:17:15 -07:00
|
|
|
else
|
|
|
|
criticalIsDirty = true;
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-10-05 20:14:57 -07:00
|
|
|
let intersection = tile.boundRect.intersect(rects[i]);
|
2009-10-20 19:16:06 -07:00
|
|
|
tile.markDirty(intersection);
|
2009-07-30 17:31:09 -07:00
|
|
|
|
|
|
|
if (crawler)
|
|
|
|
crawler.enqueue(tile.i, tile.j);
|
|
|
|
|
|
|
|
END_FOREACH_IN_RECT
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (criticalIsDirty && doCriticalRender)
|
|
|
|
this.criticalRectPaint();
|
|
|
|
},
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
criticalRectPaint: function criticalRectPaint() {
|
2009-07-17 17:14:49 -07:00
|
|
|
let cr = this._criticalRect;
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
//let start = Date.now();
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-10-20 19:16:06 -07:00
|
|
|
if (!cr.isEmpty()) {
|
2009-10-15 12:50:32 -07:00
|
|
|
let ctr = cr.center().map(Math.round);
|
2009-10-09 20:03:14 -07:00
|
|
|
|
2009-10-15 12:50:32 -07:00
|
|
|
if (!this._ctr.equals(ctr)) {
|
|
|
|
this._ctr.set(ctr);
|
|
|
|
this.recenterEvictionQueue(ctr);
|
2009-10-09 20:03:14 -07:00
|
|
|
}
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
//let start = Date.now();
|
2009-10-09 20:03:14 -07:00
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
this._renderAppendHoldRect(cr);
|
2009-10-09 20:03:14 -07:00
|
|
|
|
2009-08-05 13:28:43 -07:00
|
|
|
//dump(" render, append, hold: " + (Date.now() - start) + "\n");
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-08-05 13:28:43 -07:00
|
|
|
//dump(" paint: " + (Date.now() - start) + "\n");
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
2009-07-29 12:51:24 -07:00
|
|
|
beginCriticalMove: function beginCriticalMove(destCriticalRect) {
|
|
|
|
if (destCriticalRect) {
|
|
|
|
let tc = this._tileCache;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
BEGIN_FOREACH_IN_RECT(destCriticalRect, tc, tile)
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
if (!tile.isDirty())
|
|
|
|
this._appendTileSafe(tile);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
END_FOREACH_IN_RECT
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
endCriticalMove: function endCriticalMove(destCriticalRect, doCriticalPaint) {
|
|
|
|
let tc = this._tileCache;
|
|
|
|
let cr = this._criticalRect;
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
//let start = Date.now();
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-10-20 19:16:06 -07:00
|
|
|
if (!cr.isEmpty()) {
|
2009-07-30 17:31:09 -07:00
|
|
|
BEGIN_FOREACH_IN_RECT(cr, tc, tile)
|
|
|
|
tc.releaseTile(tile);
|
|
|
|
END_FOREACH_IN_RECT
|
2009-07-29 12:51:24 -07:00
|
|
|
}
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-08-05 13:28:43 -07:00
|
|
|
//dump(" release: " + (Date.now() - start) + "\n");
|
2009-08-06 18:44:20 -07:00
|
|
|
//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
|
|
|
|
//}
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-08-05 13:28:43 -07:00
|
|
|
//dump(" hold: " + (Date.now() - start) + "\n");
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-10-20 19:16:06 -07:00
|
|
|
cr.copyFrom(destCriticalRect);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
if (doCriticalPaint)
|
|
|
|
this.criticalRectPaint();
|
|
|
|
},
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
restartPrefetchCrawl: function restartPrefetchCrawl(startRectOrQueue) {
|
|
|
|
if (startRectOrQueue instanceof Array) {
|
2009-07-17 17:14:49 -07:00
|
|
|
this._crawler = new TileManager.CrawlIterator(this._tileCache);
|
|
|
|
|
|
|
|
if (startRectOrQueue) {
|
2009-10-09 20:03:14 -07:00
|
|
|
for (let k = 0, len = startRectOrQueue.length; k < len; ++k)
|
2009-07-17 17:14:49 -07:00
|
|
|
this._crawler.enqueue(startRectOrQueue[k].i, startRectOrQueue[k].j);
|
|
|
|
}
|
|
|
|
} else {
|
2009-10-09 20:03:14 -07:00
|
|
|
let cr = this._criticalRect;
|
|
|
|
this._crawler = new TileManager.CrawlIterator(this._tileCache,
|
|
|
|
startRectOrQueue || (cr ? cr.clone() : null));
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!this._idleTileCrawlerTimeout)
|
2009-10-09 20:03:14 -07:00
|
|
|
this._idleTileCrawlerTimeout = setTimeout(this._idleTileCrawler, kTileCrawlComeAgain, this);
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
stopPrefetchCrawl: function stopPrefetchCrawl(skipRecenter) {
|
2009-08-06 18:44:20 -07:00
|
|
|
if (this._idleTileCrawlerTimeout)
|
|
|
|
clearTimeout(this._idleTileCrawlerTimeout);
|
|
|
|
delete this._idleTileCrawlerTimeout;
|
2009-07-17 17:14:49 -07:00
|
|
|
this._crawler = null;
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
if (!skipRecenter) {
|
|
|
|
let cr = this._criticalRect;
|
2009-10-20 19:16:06 -07:00
|
|
|
if (!cr.isEmpty()) {
|
2009-10-15 12:50:32 -07:00
|
|
|
let ctr = cr.center().map(Math.round);
|
|
|
|
this.recenterEvictionQueue(ctr);
|
2009-10-09 20:03:14 -07:00
|
|
|
}
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-10-15 12:50:32 -07:00
|
|
|
recenterEvictionQueue: function recenterEvictionQueue(ctr) {
|
2009-10-09 20:03:14 -07:00
|
|
|
if (this._crawler) {
|
|
|
|
this.restartPrefetchCrawl();
|
|
|
|
}
|
|
|
|
|
2009-10-15 12:50:32 -07:00
|
|
|
let ctri = ctr.x >> kTileExponentWidth;
|
|
|
|
let ctrj = ctr.y >> kTileExponentHeight;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
this._tileCache.sortEvictionQueue(function evictFarTiles(a, b) {
|
2009-07-17 17:14:49 -07:00
|
|
|
let dista = Math.max(Math.abs(a.i - ctri), Math.abs(a.j - ctrj));
|
|
|
|
let distb = Math.max(Math.abs(b.i - ctri), Math.abs(b.j - ctrj));
|
|
|
|
return dista - distb;
|
2009-07-30 17:31:09 -07:00
|
|
|
});
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
/**
|
|
|
|
* Render a rect to the canvas under the given scale. We attempt to avoid a
|
|
|
|
* drawWindow() by copying the image (via drawImage()) from cached tiles, we
|
|
|
|
* may have. If we find that we're missing a necessary tile, we fall back on
|
|
|
|
* drawWindow() directly to the destination canvas.
|
|
|
|
*/
|
2009-08-22 11:06:51 -07:00
|
|
|
renderRectToCanvas: function renderRectToCanvas(srcRect, destCanvas, scalex, scaley) {
|
|
|
|
let tc = this._tileCache;
|
|
|
|
let ctx = destCanvas.getContext("2d");
|
|
|
|
|
|
|
|
let completed = (function breakableLoop() {
|
|
|
|
BEGIN_FOREACH_IN_RECT(srcRect, tc, tile)
|
|
|
|
|
|
|
|
if (tile.isDirty())
|
|
|
|
return false;
|
|
|
|
|
|
|
|
let dx = Math.round(scalex * (tile.boundRect.left - srcRect.left));
|
|
|
|
let dy = Math.round(scaley * (tile.boundRect.top - srcRect.top));
|
|
|
|
|
|
|
|
ctx.drawImage(tile._canvas, dx, dy,
|
|
|
|
Math.round(scalex * kTileWidth),
|
|
|
|
Math.round(scaley * kTileHeight));
|
|
|
|
|
|
|
|
END_FOREACH_IN_RECT
|
|
|
|
|
|
|
|
return true;
|
|
|
|
})();
|
|
|
|
|
|
|
|
if (!completed) {
|
|
|
|
let bv = this._browserView;
|
|
|
|
|
|
|
|
bv.viewportToBrowserRect(srcRect);
|
|
|
|
|
|
|
|
ctx.save();
|
|
|
|
|
|
|
|
bv.browserToViewportCanvasContext(ctx);
|
|
|
|
ctx.scale(scalex, scaley);
|
|
|
|
ctx.drawWindow(bv._contentWindow,
|
|
|
|
0, 0, srcRect.width, srcRect.height,
|
|
|
|
"white",
|
|
|
|
(ctx.DRAWWINDOW_DO_NOT_FLUSH | ctx.DRAWWINDOW_DRAW_CARET));
|
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
_renderTile: function _renderTile(tile) {
|
2009-10-05 20:14:57 -07:00
|
|
|
if (tile.isDirty()) {
|
|
|
|
/*
|
|
|
|
let ctx = tile._canvas.getContext("2d");
|
|
|
|
ctx.save();
|
|
|
|
ctx.fillStyle = "rgba(0,255,0,.5)";
|
|
|
|
ctx.translate(-tile.boundRect.left, -tile.boundRect.top);
|
|
|
|
ctx.fillRect(tile._dirtyTileCanvasRect.left, tile._dirtyTileCanvasRect.top,
|
|
|
|
tile._dirtyTileCanvasRect.width, tile._dirtyTileCanvasRect.height);
|
|
|
|
ctx.restore();
|
|
|
|
window.setTimeout(function(bv) {
|
|
|
|
tile.render(bv);
|
|
|
|
}, 1000, this._browserView);
|
|
|
|
*/
|
2009-07-17 17:14:49 -07:00
|
|
|
tile.render(this._browserView);
|
2009-10-05 20:14:57 -07:00
|
|
|
}
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_appendTileSafe: function _appendTileSafe(tile) {
|
2009-07-30 17:31:09 -07:00
|
|
|
if (!tile.appended) {
|
2009-07-17 17:14:49 -07:00
|
|
|
this._appendTile(tile);
|
2009-07-30 17:31:09 -07:00
|
|
|
tile.appended = true;
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-07-31 15:32:48 -07:00
|
|
|
_removeTileSafe: function _removeTileSafe(tile) {
|
2009-07-30 17:31:09 -07:00
|
|
|
if (tile.appended) {
|
2009-07-17 17:14:49 -07:00
|
|
|
this._removeTile(tile);
|
2009-07-30 17:31:09 -07:00
|
|
|
tile.appended = false;
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
_renderAppendHoldRect: function _renderAppendHoldRect(rect) {
|
|
|
|
let tc = this._tileCache;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
BEGIN_FORCREATE_IN_RECT(rect, tc, tile)
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
if (tile.isDirty())
|
|
|
|
this._renderTile(tile);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
this._appendTileSafe(tile);
|
|
|
|
this._tileCache.holdTile(tile);
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
END_FORCREATE_IN_RECT
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
_idleTileCrawler: function _idleTileCrawler(self) {
|
|
|
|
if (!self) self = this;
|
2009-07-30 17:31:09 -07:00
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
let start = Date.now();
|
|
|
|
let comeAgain = true;
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
while ((Date.now() - start) <= kTileCrawlTimeCap) {
|
2009-07-17 17:14:49 -07:00
|
|
|
let tile = self._crawler.next();
|
|
|
|
if (!tile) {
|
|
|
|
comeAgain = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (tile.isDirty()) {
|
|
|
|
self._renderTile(tile);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (comeAgain) {
|
2009-10-09 20:03:14 -07:00
|
|
|
self._idleTileCrawlerTimeout = setTimeout(self._idleTileCrawler, kTileCrawlComeAgain, self);
|
2009-07-17 17:14:49 -07:00
|
|
|
} else {
|
2009-10-09 20:03:14 -07:00
|
|
|
self.stopPrefetchCrawl();
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The tile cache used by the tile manager to hold and index all
|
|
|
|
* tiles. Also responsible for pooling tiles and maintaining the
|
|
|
|
* number of tiles under given capacity.
|
|
|
|
*
|
|
|
|
* @param onBeforeTileDetach callback set by the TileManager to call before
|
|
|
|
* we must "detach" a tile from a tileholder due to needing it elsewhere or
|
|
|
|
* having to discard it on capacity decrease
|
|
|
|
* @param capacity the initial capacity of the tile cache, i.e. the max number
|
|
|
|
* of tiles the cache can have allocated
|
|
|
|
*/
|
|
|
|
TileManager.TileCache = function TileCache(onBeforeTileDetach, iBound, jBound, capacity) {
|
|
|
|
if (arguments.length <= 3 || capacity < 0)
|
|
|
|
capacity = Infinity;
|
|
|
|
|
|
|
|
// We track all pooled tiles in a 2D array (row, column) ordered as
|
|
|
|
// they "appear on screen". The array is a grid that functions for
|
|
|
|
// storage of the tiles and as a lookup map. Each array entry is
|
|
|
|
// a reference to the tile occupying that space ("tileholder"). Entries
|
|
|
|
// are not unique, so a tile could be referenced by multiple array entries,
|
|
|
|
// i.e. a tile could "span" many tile placeholders (e.g. if we merge
|
|
|
|
// neighbouring tiles).
|
2009-07-30 17:31:09 -07:00
|
|
|
this._tileMap = [];
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
// holds the same tiles that _tileMap holds, but as contiguous array
|
2009-07-17 17:14:49 -07:00
|
|
|
// elements, for pooling tiles for reuse under finite capacity
|
2009-07-30 17:31:09 -07:00
|
|
|
this._tilePool = [];
|
|
|
|
this._pos = -1;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
this._capacity = capacity;
|
|
|
|
|
|
|
|
this._onBeforeTileDetach = onBeforeTileDetach;
|
|
|
|
|
|
|
|
this.iBound = iBound;
|
|
|
|
this.jBound = jBound;
|
|
|
|
};
|
|
|
|
|
|
|
|
TileManager.TileCache.prototype = {
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
get size() { return this._tilePool.length; },
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/**
|
|
|
|
* The default tile comparison function used to order the tile pool such that
|
|
|
|
* tiles most eligible for eviction have higher index.
|
|
|
|
*
|
|
|
|
* Unless reset, this is a comparison function that will compare free tiles
|
|
|
|
* as greater than all non-free tiles. Useful, for instance, to shrink
|
|
|
|
* the tile pool when capacity is lowered, as we want to remove all tiles
|
|
|
|
* at the new cap and beyond, favoring removal of free tiles first.
|
|
|
|
*/
|
|
|
|
evictionCmp: function evictionCmp(a, b) {
|
2009-07-17 17:14:49 -07:00
|
|
|
if (a.free == b.free) return (a.j == b.j) ? b.i - a.i : b.j - a.j;
|
|
|
|
return (a.free) ? 1 : -1;
|
|
|
|
},
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
lookup: function lookup(i, j) {
|
|
|
|
let tile = null;
|
|
|
|
if (this._tileMap[i])
|
|
|
|
tile = this._tileMap[i][j] || null;
|
|
|
|
|
|
|
|
return tile;
|
2009-07-30 17:31:09 -07:00
|
|
|
},
|
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
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
|
2009-07-30 17:31:09 -07:00
|
|
|
// 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.
|
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
if (!skipEvictionQueueSort)
|
|
|
|
this.sortEvictionQueue();
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
let rem = this._tilePool.splice(newCap);
|
|
|
|
for (let k = 0, len = rem.length; k < len; ++k)
|
2009-10-05 20:14:57 -07:00
|
|
|
this._detachTile(rem[k].i, rem[k].j);
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
this._capacity = newCap;
|
|
|
|
},
|
|
|
|
|
|
|
|
inBounds: function inBounds(i, j) {
|
2009-10-05 20:14:57 -07:00
|
|
|
return (0 <= i && 0 <= j && i <= this.iBound && j <= this.jBound);
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
sortEvictionQueue: function sortEvictionQueue(cmp) {
|
2009-07-30 17:31:09 -07:00
|
|
|
let pool = this._tilePool;
|
|
|
|
|
|
|
|
pool.sort(cmp ? cmp : this.evictionCmp);
|
|
|
|
this._pos = pool.length - 1;
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get a tile by its indices
|
|
|
|
*
|
|
|
|
* @param i Column
|
|
|
|
* @param j Row
|
|
|
|
* @param create Flag true if the tile should be created in case there is no
|
|
|
|
* tile at (i, j)
|
|
|
|
* @param reuseCondition Boolean-valued function to restrict conditions under
|
|
|
|
* which an old tile may be reused for creating this one. This can happen if
|
|
|
|
* the cache has reached its capacity and must reuse existing tiles in order to
|
|
|
|
* create this one. The function is given a Tile object as its argument and
|
|
|
|
* returns true if the tile is OK for reuse. This argument has no effect if the
|
|
|
|
* create argument is false.
|
|
|
|
*/
|
|
|
|
getTile: function getTile(i, j, create, evictionGuard) {
|
|
|
|
if (!this.inBounds(i, j))
|
|
|
|
return null;
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
let tile = this.lookup(i, j);
|
|
|
|
if (!tile && create) {
|
2009-07-17 17:14:49 -07:00
|
|
|
tile = this._createTile(i, j, evictionGuard);
|
|
|
|
if (tile) tile.markDirty();
|
|
|
|
}
|
|
|
|
|
|
|
|
return tile;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up (possibly creating) a tile from its viewport coordinates.
|
|
|
|
*
|
|
|
|
* @param x
|
|
|
|
* @param y
|
|
|
|
* @param create Flag true if the tile should be created in case it doesn't
|
|
|
|
* already exist at the tileholder corresponding to (x, y)
|
|
|
|
*/
|
|
|
|
tileFromPoint: function tileFromPoint(x, y, create) {
|
|
|
|
let i = x >> kTileExponentWidth;
|
|
|
|
let j = y >> kTileExponentHeight;
|
|
|
|
|
|
|
|
return this.getTile(i, j, create);
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2009-07-30 17:31:09 -07:00
|
|
|
* Hold a tile (i.e. mark it non-free).
|
2009-07-17 17:14:49 -07:00
|
|
|
*/
|
|
|
|
holdTile: function holdTile(tile) {
|
2009-07-30 17:31:09 -07:00
|
|
|
if (tile) tile.free = false;
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
2009-07-30 17:31:09 -07:00
|
|
|
* Release a tile (i.e. mark it free).
|
2009-07-17 17:14:49 -07:00
|
|
|
*/
|
|
|
|
releaseTile: function releaseTile(tile) {
|
2009-07-30 17:31:09 -07:00
|
|
|
if (tile) tile.free = true;
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
_detachTile: function _detachTile(i, j) {
|
2009-08-06 18:44:20 -07:00
|
|
|
let tile = this.lookup(i, j);
|
|
|
|
if (tile) {
|
2009-07-30 17:31:09 -07:00
|
|
|
if (this._onBeforeTileDetach)
|
|
|
|
this._onBeforeTileDetach(tile);
|
|
|
|
|
|
|
|
this.releaseTile(tile);
|
|
|
|
delete this._tileMap[i][j];
|
|
|
|
}
|
|
|
|
return tile;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Pluck tile from its current tileMap position and drop it in position
|
|
|
|
* given by (i, j).
|
|
|
|
*/
|
|
|
|
_reassignTile: function _reassignTile(tile, i, j) {
|
|
|
|
this._detachTile(tile.i, tile.j); // detach
|
|
|
|
tile.init(i, j); // re-init
|
|
|
|
this._tileMap[i][j] = tile; // attach
|
|
|
|
return tile;
|
|
|
|
},
|
|
|
|
|
|
|
|
_evictTile: function _evictTile(evictionGuard) {
|
|
|
|
let k = this._pos;
|
|
|
|
let pool = this._tilePool;
|
|
|
|
let victim = null;
|
|
|
|
|
|
|
|
for (; k >= 0; --k) {
|
|
|
|
if (pool[k].free && ( !evictionGuard || evictionGuard(pool[k]) )) {
|
|
|
|
victim = pool[k];
|
2009-07-31 15:32:48 -07:00
|
|
|
--k;
|
2009-07-30 17:31:09 -07:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._pos = k;
|
|
|
|
|
|
|
|
return victim;
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
_createTile: function _createTile(i, j, evictionGuard) {
|
|
|
|
if (!this._tileMap[i])
|
|
|
|
this._tileMap[i] = [];
|
|
|
|
|
|
|
|
let tile = null;
|
|
|
|
if (this._tilePool.length < this._capacity) { // either capacity is
|
|
|
|
tile = new TileManager.Tile(i, j); // infinite, or we have
|
|
|
|
this._tileMap[i][j] = tile; // room to allocate more
|
|
|
|
this._tilePool.push(tile);
|
|
|
|
} else {
|
|
|
|
tile = this._evictTile(evictionGuard);
|
|
|
|
if (tile)
|
|
|
|
this._reassignTile(tile, i, j);
|
|
|
|
}
|
|
|
|
|
|
|
|
return tile;
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/**
|
|
|
|
* A tile is a little object with an <html:canvas> DOM element that it used for
|
|
|
|
* caching renders from drawWindow().
|
|
|
|
*
|
|
|
|
* Supports the dirtying of only a subrectangle of the bounding rectangle, as
|
|
|
|
* well as just dirtying the entire tile.
|
|
|
|
*/
|
2009-07-17 17:14:49 -07:00
|
|
|
TileManager.Tile = function Tile(i, j) {
|
|
|
|
this._canvas = document.createElementNS(kXHTMLNamespaceURI, "canvas");
|
|
|
|
this._canvas.setAttribute("width", String(kTileWidth));
|
|
|
|
this._canvas.setAttribute("height", String(kTileHeight));
|
|
|
|
this._canvas.setAttribute("moz-opaque", "true");
|
|
|
|
|
|
|
|
this.init(i, j); // defines more properties, cf below
|
|
|
|
};
|
|
|
|
|
|
|
|
TileManager.Tile.prototype = {
|
|
|
|
init: function init(i, j) {
|
|
|
|
if (!this.boundRect)
|
2009-10-15 12:50:32 -07:00
|
|
|
this.boundRect = new Rect(i * kTileWidth, j * kTileHeight, kTileWidth, kTileHeight);
|
2009-07-17 17:14:49 -07:00
|
|
|
else
|
|
|
|
this.boundRect.setRect(i * kTileWidth, j * kTileHeight, kTileWidth, kTileHeight);
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/* indices of this tile in the tile cache's tile grid map */
|
|
|
|
this.i = i; // row
|
|
|
|
this.j = j; // column
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/* flag used by TileManager to avoid re-appending tiles that have already
|
|
|
|
* been appended */
|
|
|
|
this.appended = false;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/* flag used by the TileCache to mark tiles as ineligible for eviction,
|
|
|
|
* usually because they are fixed in some critical position */
|
|
|
|
this.free = true;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/* flags true if we need to repaint our own local canvas */
|
|
|
|
this._dirtyTileCanvas = false;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/* keep a dirty rectangle (i.e. only part of the tile is dirty) */
|
2009-10-20 19:16:06 -07:00
|
|
|
this._dirtyTileCanvasRect = new Rect(0, 0, 0, 0);
|
2009-07-17 17:14:49 -07:00
|
|
|
},
|
|
|
|
|
|
|
|
get x() { return this.boundRect.left; },
|
|
|
|
get y() { return this.boundRect.top; },
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/**
|
|
|
|
* Get the <html:canvas> DOM element with this tile's cached paint.
|
|
|
|
*/
|
2009-07-17 17:14:49 -07:00
|
|
|
getContentImage: function getContentImage() { return this._canvas; },
|
|
|
|
|
|
|
|
isDirty: function isDirty() { return this._dirtyTileCanvas; },
|
|
|
|
|
|
|
|
/**
|
|
|
|
* This will mark dirty at least everything in dirtyRect (which must be
|
|
|
|
* specified in canvas coordinates). If dirtyRect is not given then
|
2009-08-06 18:44:20 -07:00
|
|
|
* the entire tile is marked dirty (i.e. the whole tile needs to be rendered
|
|
|
|
* on next render).
|
2009-07-17 17:14:49 -07:00
|
|
|
*/
|
2009-08-06 18:44:20 -07:00
|
|
|
markDirty: function markDirty(dirtyRect) {
|
2009-07-17 17:14:49 -07:00
|
|
|
if (!dirtyRect) {
|
2009-10-20 19:16:06 -07:00
|
|
|
this._dirtyTileCanvasRect.copyFrom(this.boundRect);
|
2009-10-05 20:14:57 -07:00
|
|
|
} else {
|
2009-10-20 19:16:06 -07:00
|
|
|
this._dirtyTileCanvasRect.expandToContain(dirtyRect.intersect(this.boundRect)).expandToIntegers();
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
// XXX if, after the above, the dirty rectangle takes up a large enough
|
|
|
|
// portion of the boundRect, we should probably just mark the entire tile
|
|
|
|
// dirty and fastpath for future calls.
|
2009-10-20 19:16:06 -07:00
|
|
|
if (!this._dirtyTileCanvasRect.isEmpty())
|
2009-07-17 17:14:49 -07:00
|
|
|
this._dirtyTileCanvas = true;
|
|
|
|
},
|
|
|
|
|
2009-08-06 18:44:20 -07:00
|
|
|
unmarkDirty: function unmarkDirty() {
|
2009-10-20 19:16:06 -07:00
|
|
|
this._dirtyTileCanvasRect.setRect(0, 0, 0, 0);
|
2009-08-06 18:44:20 -07:00
|
|
|
this._dirtyTileCanvas = false;
|
|
|
|
},
|
|
|
|
|
2009-07-17 17:14:49 -07:00
|
|
|
/**
|
2009-07-30 17:31:09 -07:00
|
|
|
* Actually draw the browser content into the dirty region of this tile. This
|
|
|
|
* requires us to actually draw with nsIDOMCanvasRenderingContext2D::
|
|
|
|
* drawWindow(), which we expect to be a heavy operation.
|
2009-07-17 17:14:49 -07:00
|
|
|
*
|
|
|
|
* You likely want to check if the tile isDirty() before asking it
|
|
|
|
* to render, as this will cause the entire tile to re-render in the
|
|
|
|
* case that it is not dirty.
|
|
|
|
*/
|
|
|
|
render: function render(browserView) {
|
|
|
|
if (!this.isDirty())
|
|
|
|
this.markDirty();
|
|
|
|
|
|
|
|
let rect = this._dirtyTileCanvasRect;
|
|
|
|
let x = rect.left - this.boundRect.left;
|
|
|
|
let y = rect.top - this.boundRect.top;
|
|
|
|
|
|
|
|
browserView.viewportToBrowserRect(rect);
|
|
|
|
|
|
|
|
let ctx = this._canvas.getContext("2d");
|
|
|
|
ctx.save();
|
|
|
|
|
|
|
|
ctx.translate(x, y);
|
2009-10-05 20:14:57 -07:00
|
|
|
browserView.browserToViewportCanvasContext(ctx);
|
|
|
|
ctx.drawWindow(browserView._contentWindow,
|
2009-07-17 17:14:49 -07:00
|
|
|
rect.left, rect.top,
|
2009-07-17 20:05:07 -07:00
|
|
|
rect.right - rect.left, rect.bottom - rect.top,
|
2009-07-18 01:16:04 -07:00
|
|
|
"white",
|
2009-09-21 14:46:10 -07:00
|
|
|
(ctx.DRAWWINDOW_DRAW_CARET));
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
ctx.restore();
|
|
|
|
|
|
|
|
this.unmarkDirty();
|
|
|
|
},
|
|
|
|
|
2009-07-30 17:31:09 -07:00
|
|
|
/**
|
|
|
|
* Standard toString prints "Tile(<row>, <column>)", but if `more' flags true
|
|
|
|
* then some extra information is printed.
|
|
|
|
*/
|
2009-07-17 17:14:49 -07:00
|
|
|
toString: function toString(more) {
|
|
|
|
if (more) {
|
2009-07-30 17:31:09 -07:00
|
|
|
return 'Tile(' + this.i + ', '
|
|
|
|
+ this.j + ', '
|
|
|
|
+ 'dirty=' + this.isDirty() + ', '
|
|
|
|
+ 'boundRect=' + this.boundRect.toString() + ')';
|
2009-07-17 17:14:49 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
return 'Tile(' + this.i + ', ' + this.j + ')';
|
2009-07-30 17:31:09 -07:00
|
|
|
}
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A CrawlIterator is in charge of creating and returning subsequent tiles "crawled"
|
2009-07-30 17:31:09 -07:00
|
|
|
* over as we render tiles lazily.
|
2009-07-17 17:14:49 -07:00
|
|
|
*
|
|
|
|
* Currently the CrawlIterator is built to expand a rectangle iteratively and return
|
|
|
|
* subsequent tiles that intersect the boundary of the rectangle. Each expansion of
|
|
|
|
* the rectangle is one unit of tile dimensions in each direction. This is repeated
|
|
|
|
* until all tiles from elsewhere have been reused (assuming the cache has finite
|
|
|
|
* capacity) in this crawl, so that we don't start reusing tiles from the beginning
|
|
|
|
* of our crawl. Afterward, the CrawlIterator enters a state where it operates as a
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
TileManager.CrawlIterator = function CrawlIterator(tileCache, startRect) {
|
|
|
|
this._tileCache = tileCache;
|
|
|
|
this._stepRect = startRect;
|
|
|
|
|
|
|
|
// 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.toString()]; };
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
while (!outOfBounds) {
|
2009-10-09 20:03:14 -07:00
|
|
|
rect.left -= kTileWidth; // expand the rect
|
2009-07-17 17:14:49 -07:00
|
|
|
rect.right += kTileWidth;
|
|
|
|
rect.top -= kTileHeight;
|
|
|
|
rect.bottom += kTileHeight;
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
let starti = rect.left >> kTileExponentWidth;
|
|
|
|
let endi = rect.right >> kTileExponentWidth;
|
|
|
|
let startj = rect.top >> kTileExponentHeight;
|
|
|
|
let endj = rect.bottom >> kTileExponentHeight;
|
|
|
|
let i, j;
|
2009-07-17 17:14:49 -07:00
|
|
|
|
|
|
|
outOfBounds = true;
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
// top, bottom rect borders
|
2009-07-17 17:14:49 -07:00
|
|
|
for each (let y in [rect.top, rect.bottom]) {
|
2009-10-09 20:03:14 -07:00
|
|
|
j = y >> kTileExponentHeight;
|
|
|
|
|
|
|
|
for (i = starti; i <= endi; ++i) {
|
2009-07-17 17:14:49 -07:00
|
|
|
if (tc.inBounds(i, j)) {
|
|
|
|
outOfBounds = false;
|
|
|
|
yield [i, j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-10-09 20:03:14 -07:00
|
|
|
// left, right rect borders
|
2009-07-17 17:14:49 -07:00
|
|
|
for each (let x in [rect.left, rect.right]) {
|
2009-10-09 20:03:14 -07:00
|
|
|
i = x >> kTileExponentWidth;
|
|
|
|
|
|
|
|
for (j = startj; j <= endj; ++j) {
|
2009-07-17 17:14:49 -07:00
|
|
|
if (tc.inBounds(i, j)) {
|
|
|
|
outOfBounds = false;
|
|
|
|
yield [i, j];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})(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;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
becomeQueue: function becomeQueue() {
|
|
|
|
this._queueState = true;
|
|
|
|
},
|
|
|
|
|
|
|
|
unbecomeQueue: function unbecomeQueue() {
|
|
|
|
this._queueState = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
next: function next() {
|
|
|
|
if (this._queueState)
|
|
|
|
return 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 (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)
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
_strIndices: function _strIndices(i, j) {
|
|
|
|
return i + "," + j;
|
|
|
|
},
|
|
|
|
|
|
|
|
_unstrIndices: function _unstrIndices(str) {
|
|
|
|
return str.split(',');
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|