From 63a683ffc29e30fda06cb73a5dec5a2ed8d862f0 Mon Sep 17 00:00:00 2001 From: Mikko Pulkki <55925868+mpulkki-mapbox@users.noreply.github.com> Date: Wed, 18 Dec 2019 14:34:58 +0200 Subject: [PATCH] Change the type of tile id key to string (#8979) * Change tile id key type from number to string --- src/geo/transform.js | 4 +- src/render/painter.js | 2 +- src/source/source_cache.js | 129 +++++++++---- src/source/tile_cache.js | 8 +- src/source/tile_id.js | 22 +-- .../source/raster_dem_tile_source.test.js | 12 +- test/unit/source/source_cache.test.js | 170 +++++++++++++----- test/unit/source/tile_id.test.js | 18 +- test/unit/ui/map.test.js | 6 +- 9 files changed, 261 insertions(+), 110 deletions(-) diff --git a/src/geo/transform.js b/src/geo/transform.js index c92d68cf326..f80dc279277 100644 --- a/src/geo/transform.js +++ b/src/geo/transform.js @@ -50,8 +50,8 @@ class Transform { _maxPitch: number; _center: LngLat; _constraining: boolean; - _posMatrixCache: {[number]: Float32Array}; - _alignedPosMatrixCache: {[number]: Float32Array}; + _posMatrixCache: {[string]: Float32Array}; + _alignedPosMatrixCache: {[string]: Float32Array}; constructor(minZoom: ?number, maxZoom: ?number, minPitch: ?number, maxPitch: ?number, renderWorldCopies: boolean | void) { this.tileSize = 512; // constant diff --git a/src/render/painter.js b/src/render/painter.js index 30661433347..722c53fdc34 100644 --- a/src/render/painter.js +++ b/src/render/painter.js @@ -103,7 +103,7 @@ class Painter { viewportSegments: SegmentVector; quadTriangleIndexBuffer: IndexBuffer; tileBorderIndexBuffer: IndexBuffer; - _tileClippingMaskIDs: { [number]: number }; + _tileClippingMaskIDs: { [string]: number }; stencilClearMode: StencilMode; style: Style; options: PainterOptions; diff --git a/src/source/source_cache.js b/src/source/source_cache.js index 56a2d4839ec..4b06ad0ba2d 100644 --- a/src/source/source_cache.js +++ b/src/source/source_cache.js @@ -44,7 +44,7 @@ class SourceCache extends Evented { _source: Source; _sourceLoaded: boolean; _sourceErrored: boolean; - _tiles: {[any]: Tile}; + _tiles: {[string]: Tile}; _prevLng: number | void; _cache: TileCache; _timers: {[any]: TimeoutID}; @@ -52,11 +52,12 @@ class SourceCache extends Evented { _maxTileCacheSize: ?number; _paused: boolean; _shouldReloadOnResume: boolean; - _coveredTiles: {[any]: boolean}; + _coveredTiles: {[string]: boolean}; transform: Transform; - _isIdRenderable: (id: number, symbolLayer?: boolean) => boolean; + _isIdRenderable: (id: string, symbolLayer?: boolean) => boolean; used: boolean; _state: SourceFeatureState; + _loadedParentTiles: {[string]: ?Tile}; static maxUnderzooming: number; static maxOverzooming: number; @@ -93,6 +94,7 @@ class SourceCache extends Evented { this._timers = {}; this._cacheTimers = {}; this._maxTileCacheSize = null; + this._loadedParentTiles = {}; this._coveredTiles = {}; this._state = new SourceFeatureState(); @@ -179,25 +181,25 @@ class SourceCache extends Evented { /** * Return all tile ids ordered with z-order, and cast to numbers */ - getIds(): Array { - return Object.keys(this._tiles).map(Number).sort(compareKeyZoom); + getIds(): Array { + return (Object.values(this._tiles): any).map((tile: Tile) => tile.tileID).sort(compareTileId).map(id => id.key); } - getRenderableIds(symbolLayer?: boolean): Array { - const ids = []; + getRenderableIds(symbolLayer?: boolean): Array { + const renderables: Array = []; for (const id in this._tiles) { - if (this._isIdRenderable(+id, symbolLayer)) ids.push(+id); + if (this._isIdRenderable(id, symbolLayer)) renderables.push(this._tiles[id]); } if (symbolLayer) { - return ids.sort((a_, b_) => { - const a = this._tiles[a_].tileID; - const b = this._tiles[b_].tileID; + return renderables.sort((a_: Tile, b_: Tile) => { + const a = a_.tileID; + const b = b_.tileID; const rotatedA = (new Point(a.canonical.x, a.canonical.y))._rotate(this.transform.angle); const rotatedB = (new Point(b.canonical.x, b.canonical.y))._rotate(this.transform.angle); return a.overscaledZ - b.overscaledZ || rotatedB.y - rotatedA.y || rotatedB.x - rotatedA.x; - }); + }).map(tile => tile.tileID.key); } - return ids.sort(compareKeyZoom); + return renderables.map(tile => tile.tileID).sort(compareTileId).map(id => id.key); } hasRenderableParent(tileID: OverscaledTileID) { @@ -208,7 +210,7 @@ class SourceCache extends Evented { return false; } - _isIdRenderable(id: number, symbolLayer?: boolean) { + _isIdRenderable(id: string, symbolLayer?: boolean) { return this._tiles[id] && this._tiles[id].hasData() && !this._coveredTiles[id] && (symbolLayer || !this._tiles[id].holdingForFade()); } @@ -226,7 +228,7 @@ class SourceCache extends Evented { } } - _reloadTile(id: string | number, state: TileState) { + _reloadTile(id: string, state: TileState) { const tile = this._tiles[id]; // this potentially does not address all underlying @@ -245,7 +247,7 @@ class SourceCache extends Evented { this._loadTile(tile, this._tileLoaded.bind(this, tile, id, state)); } - _tileLoaded(tile: Tile, id: string | number, previousState: TileState, err: ?Error) { + _tileLoaded(tile: Tile, id: string, previousState: TileState, err: ?Error) { if (err) { tile.state = 'errored'; if ((err: any).status !== 404) this._source.fire(new ErrorEvent(err, {tile})); @@ -313,7 +315,7 @@ class SourceCache extends Evented { /** * Get a specific tile by id */ - getTileByID(id: string | number): Tile { + getTileByID(id: string): Tile { return this._tiles[id]; } @@ -367,19 +369,33 @@ class SourceCache extends Evented { * Find a loaded parent of the given tile (up to minCoveringZoom) */ findLoadedParent(tileID: OverscaledTileID, minCoveringZoom: number): ?Tile { + if (tileID.key in this._loadedParentTiles) { + const parent = this._loadedParentTiles[tileID.key]; + if (parent && parent.tileID.overscaledZ >= minCoveringZoom) { + return parent; + } else { + return null; + } + } for (let z = tileID.overscaledZ - 1; z >= minCoveringZoom; z--) { - const parentKey = tileID.calculateScaledKey(z, true); - const tile = this._tiles[parentKey]; - if (tile && tile.hasData()) { + const parentTileID = tileID.scaledTo(z); + const tile = this._getLoadedTile(parentTileID); + if (tile) { return tile; } - // TileCache ignores wrap in lookup. - const parentWrappedKey = tileID.calculateScaledKey(z, false); - const cachedTile = this._cache.getByKey(parentWrappedKey); - if (cachedTile) return cachedTile; } } + _getLoadedTile(tileID: OverscaledTileID): ?Tile { + const tile = this._tiles[tileID.key]; + if (tile && tile.hasData()) { + return tile; + } + // TileCache ignores wrap in lookup. + const cachedTile = this._cache.getByKey(tileID.wrapped().key); + return cachedTile; + } + /** * Resizes the tile cache based on the current viewport's size * or the maxTileCacheSize option passed during map creation @@ -423,7 +439,7 @@ class SourceCache extends Evented { this._prevLng = lng; if (wrapDelta) { - const tiles = {}; + const tiles: {[string]: Tile} = {}; for (const key in this._tiles) { const tile = this._tiles[key]; tile.tileID = tile.tileID.unwrapTo(tile.tileID.wrap + wrapDelta); @@ -489,12 +505,12 @@ class SourceCache extends Evented { const retain = this._updateRetainedTiles(idealTileIDs, zoom); if (isRasterType(this._source.type)) { - const parentsForFading = {}; + const parentsForFading: {[string]: OverscaledTileID} = {}; const fadingTiles = {}; const ids = Object.keys(retain); for (const id of ids) { const tileID = retain[id]; - assert(tileID.key === +id); + assert(tileID.key === id); const tile = this._tiles[id]; if (!tile || tile.fadeEndTime && tile.fadeEndTime <= browser.now()) continue; @@ -537,6 +553,9 @@ class SourceCache extends Evented { this._removeTile(tileID); } } + + // Construct a cache of loaded parents + this._updateLoadedParentTileCache(); } releaseSymbolFadeTiles() { @@ -548,8 +567,8 @@ class SourceCache extends Evented { } _updateRetainedTiles(idealTileIDs: Array, zoom: number): { [string]: OverscaledTileID} { - const retain = {}; - const checked: {[number]: boolean } = {}; + const retain: {[string]: OverscaledTileID} = {}; + const checked: {[string]: boolean } = {}; const minCoveringZoom = Math.max(zoom - SourceCache.maxOverzooming, this._source.minzoom); const maxCoveringZoom = Math.max(zoom + SourceCache.maxUnderzooming, this._source.minzoom); @@ -628,6 +647,43 @@ class SourceCache extends Evented { return retain; } + _updateLoadedParentTileCache() { + this._loadedParentTiles = {}; + + for (const tileKey in this._tiles) { + const path = []; + let parentTile: ?Tile; + let currentId = this._tiles[tileKey].tileID; + + // Find the closest loaded ancestor by traversing the tile tree towards the root and + // caching results along the way + while (currentId.overscaledZ > 0) { + + // Do we have a cached result from previous traversals? + if (currentId.key in this._loadedParentTiles) { + parentTile = this._loadedParentTiles[currentId.key]; + break; + } + + path.push(currentId.key); + + // Is the parent loaded? + const parentId = currentId.scaledTo(currentId.overscaledZ - 1); + parentTile = this._getLoadedTile(parentId); + if (parentTile) { + break; + } + + currentId = parentId; + } + + // Cache the result of this traversal to all newly visited tiles + for (const key of path) { + this._loadedParentTiles[key] = parentTile; + } + } + } + /** * Add a tile, given its coordinate, to the pyramid. * @private @@ -666,7 +722,7 @@ class SourceCache extends Evented { return tile; } - _setTileReloadTimer(id: string | number, tile: Tile) { + _setTileReloadTimer(id: string, tile: Tile) { if (id in this._timers) { clearTimeout(this._timers[id]); delete this._timers[id]; @@ -685,7 +741,7 @@ class SourceCache extends Evented { * Remove a tile, given its id, from the pyramid * @private */ - _removeTile(id: string | number) { + _removeTile(id: string) { const tile = this._tiles[id]; if (!tile) return; @@ -846,7 +902,7 @@ class SourceCache extends Evented { * Sets the set of keys that the tile depends on. This allows tiles to * be reloaded when their dependencies change. */ - setDependencies(tileKey: string | number, namespace: string, dependencies: Array) { + setDependencies(tileKey: string, namespace: string, dependencies: Array) { const tile = this._tiles[tileKey]; if (tile) { tile.setDependencies(namespace, dependencies); @@ -870,8 +926,13 @@ class SourceCache extends Evented { SourceCache.maxOverzooming = 10; SourceCache.maxUnderzooming = 3; -function compareKeyZoom(a, b) { - return ((a % 32) - (b % 32)) || (b - a); +function compareTileId(a: OverscaledTileID, b: OverscaledTileID): number { + // Different copies of the world are sorted based on their distance to the center. + // Wrap values are converted to unsigned distances by reserving odd number for copies + // with negative wrap and even numbers for copies with positive wrap. + const aWrap = Math.abs(a.wrap * 2) - +(a.wrap < 0); + const bWrap = Math.abs(b.wrap * 2) - +(b.wrap < 0); + return a.overscaledZ - b.overscaledZ || bWrap - aWrap || b.canonical.y - a.canonical.y || b.canonical.x - a.canonical.x; } function isRasterType(type) { diff --git a/src/source/tile_cache.js b/src/source/tile_cache.js index 1bf01cc0948..8d13bb5f53d 100644 --- a/src/source/tile_cache.js +++ b/src/source/tile_cache.js @@ -12,8 +12,8 @@ import type Tile from './tile'; */ class TileCache { max: number; - data: {[key: number | string]: Array<{ value: Tile, timeout: ?TimeoutID}>}; - order: Array; + data: {[key: string]: Array<{ value: Tile, timeout: ?TimeoutID}>}; + order: Array; onRemove: (element: Tile) => void; /** * @param {number} max number of permitted values @@ -110,7 +110,7 @@ class TileCache { /* * Get and remove the value with the specified key. */ - _getAndRemoveByKey(key: number): ?Tile { + _getAndRemoveByKey(key: string): ?Tile { const data = this.data[key].shift(); if (data.timeout) clearTimeout(data.timeout); @@ -125,7 +125,7 @@ class TileCache { /* * Get the value with the specified (wrapped tile) key. */ - getByKey(key: number): ?Tile { + getByKey(key: string): ?Tile { const data = this.data[key]; return data ? data[0].value : null; } diff --git a/src/source/tile_id.js b/src/source/tile_id.js index 52ff8f71169..244209cee0b 100644 --- a/src/source/tile_id.js +++ b/src/source/tile_id.js @@ -12,7 +12,7 @@ export class CanonicalTileID { z: number; x: number; y: number; - key: number; + key: string; constructor(z: number, x: number, y: number) { assert(z >= 0 && z <= 25); @@ -21,7 +21,7 @@ export class CanonicalTileID { this.z = z; this.x = x; this.y = y; - this.key = calculateKey(0, z, x, y); + this.key = calculateKey(0, z, z, x, y); } equals(id: CanonicalTileID) { @@ -57,12 +57,12 @@ export class CanonicalTileID { export class UnwrappedTileID { wrap: number; canonical: CanonicalTileID; - key: number; + key: string; constructor(wrap: number, canonical: CanonicalTileID) { this.wrap = wrap; this.canonical = canonical; - this.key = calculateKey(wrap, canonical.z, canonical.x, canonical.y); + this.key = calculateKey(wrap, canonical.z, canonical.z, canonical.x, canonical.y); } } @@ -70,7 +70,7 @@ export class OverscaledTileID { overscaledZ: number; wrap: number; canonical: CanonicalTileID; - key: number; + key: string; posMatrix: Float32Array; constructor(overscaledZ: number, wrap: number, z: number, x: number, y: number) { @@ -78,7 +78,7 @@ export class OverscaledTileID { this.overscaledZ = overscaledZ; this.wrap = wrap; this.canonical = new CanonicalTileID(z, +x, +y); - this.key = calculateKey(wrap, overscaledZ, x, y); + this.key = calculateKey(wrap, overscaledZ, z, x, y); } equals(id: OverscaledTileID) { @@ -100,13 +100,13 @@ export class OverscaledTileID { * when withWrap == true, implements the same as this.scaledTo(z).key, * when withWrap == false, implements the same as this.scaledTo(z).wrapped().key. */ - calculateScaledKey(targetZ: number, withWrap: boolean) { + calculateScaledKey(targetZ: number, withWrap: boolean): string { assert(targetZ <= this.overscaledZ); const zDifference = this.canonical.z - targetZ; if (targetZ > this.canonical.z) { - return calculateKey(this.wrap * +withWrap, targetZ, this.canonical.x, this.canonical.y); + return calculateKey(this.wrap * +withWrap, targetZ, this.canonical.z, this.canonical.x, this.canonical.y); } else { - return calculateKey(this.wrap * +withWrap, targetZ, this.canonical.x >> zDifference, this.canonical.y >> zDifference); + return calculateKey(this.wrap * +withWrap, targetZ, targetZ, this.canonical.x >> zDifference, this.canonical.y >> zDifference); } } @@ -179,11 +179,11 @@ export class OverscaledTileID { } } -function calculateKey(wrap: number, z: number, x: number, y: number) { +function calculateKey(wrap: number, overscaledZ: number, z: number, x: number, y: number): string { wrap *= 2; if (wrap < 0) wrap = wrap * -1 - 1; const dim = 1 << z; - return ((dim * dim * wrap + dim * y + x) * 32) + z; + return (dim * dim * wrap + dim * y + x).toString(36) + z.toString(36) + overscaledZ.toString(36); } function getQuadkey(z, x, y) { diff --git a/test/unit/source/raster_dem_tile_source.test.js b/test/unit/source/raster_dem_tile_source.test.js index 0f2ee7617e6..f7fef0d6d5e 100644 --- a/test/unit/source/raster_dem_tile_source.test.js +++ b/test/unit/source/raster_dem_tile_source.test.js @@ -98,11 +98,11 @@ test('RasterTileSource', (t) => { source.loadTile(tile, () => {}); t.deepEqual(Object.keys(tile.neighboringTiles), [ + new OverscaledTileID(10, 0, 10, 4, 5).key, + new OverscaledTileID(10, 0, 10, 6, 5).key, new OverscaledTileID(10, 0, 10, 4, 4).key, new OverscaledTileID(10, 0, 10, 5, 4).key, new OverscaledTileID(10, 0, 10, 6, 4).key, - new OverscaledTileID(10, 0, 10, 4, 5).key, - new OverscaledTileID(10, 0, 10, 6, 5).key, new OverscaledTileID(10, 0, 10, 4, 6).key, new OverscaledTileID(10, 0, 10, 5, 6).key, new OverscaledTileID(10, 0, 10, 6, 6).key @@ -134,13 +134,13 @@ test('RasterTileSource', (t) => { source.loadTile(tile, () => {}); t.deepEqual(Object.keys(tile.neighboringTiles), [ - new OverscaledTileID(5, 0, 5, 30, 4).key, - new OverscaledTileID(5, 0, 5, 31, 4).key, - new OverscaledTileID(5, 0, 5, 30, 5).key, new OverscaledTileID(5, 0, 5, 30, 6).key, new OverscaledTileID(5, 0, 5, 31, 6).key, - new OverscaledTileID(5, 1, 5, 0, 4).key, + new OverscaledTileID(5, 0, 5, 30, 5).key, new OverscaledTileID(5, 1, 5, 0, 5).key, + new OverscaledTileID(5, 0, 5, 30, 4).key, + new OverscaledTileID(5, 0, 5, 31, 4).key, + new OverscaledTileID(5, 1, 5, 0, 4).key, new OverscaledTileID(5, 1, 5, 0, 6).key ]); t.end(); diff --git a/test/unit/source/source_cache.test.js b/test/unit/source/source_cache.test.js index b8eb33b4cc6..2d8fc7790b0 100644 --- a/test/unit/source/source_cache.test.js +++ b/test/unit/source/source_cache.test.js @@ -229,6 +229,31 @@ test('SourceCache#addTile', (t) => { t.end(); }); + t.test('should load tiles with identical overscaled Z but different canonical Z', (t) => { + const sourceCache = createSourceCache(); + + const tileIDs = [ + new OverscaledTileID(1, 0, 0, 0, 0), + new OverscaledTileID(1, 0, 1, 0, 0), + new OverscaledTileID(1, 0, 1, 1, 0), + new OverscaledTileID(1, 0, 1, 0, 1), + new OverscaledTileID(1, 0, 1, 1, 1) + ]; + + for (let i = 0; i < tileIDs.length; i++) + sourceCache._addTile(tileIDs[i]); + + for (let i = 0; i < tileIDs.length; i++) { + const id = tileIDs[i]; + const key = id.key; + + t.ok(sourceCache._tiles[key]); + t.deepEqual(sourceCache._tiles[key].tileID, id); + } + + t.end(); + }); + t.end(); }); @@ -754,20 +779,20 @@ test('SourceCache#update', (t) => { if (e.sourceDataType === 'metadata') { sourceCache.update(transform); t.deepEqual(sourceCache.getRenderableIds(), [ - new OverscaledTileID(16, 0, 16, 8192, 8192).key, - new OverscaledTileID(16, 0, 16, 8191, 8192).key, - new OverscaledTileID(16, 0, 16, 8192, 8191).key, - new OverscaledTileID(16, 0, 16, 8191, 8191).key + new OverscaledTileID(16, 0, 14, 8192, 8192).key, + new OverscaledTileID(16, 0, 14, 8191, 8192).key, + new OverscaledTileID(16, 0, 14, 8192, 8191).key, + new OverscaledTileID(16, 0, 14, 8191, 8191).key ]); transform.zoom = 15; sourceCache.update(transform); t.deepEqual(sourceCache.getRenderableIds(), [ - new OverscaledTileID(16, 0, 16, 8192, 8192).key, - new OverscaledTileID(16, 0, 16, 8191, 8192).key, - new OverscaledTileID(16, 0, 16, 8192, 8191).key, - new OverscaledTileID(16, 0, 16, 8191, 8191).key + new OverscaledTileID(16, 0, 14, 8192, 8192).key, + new OverscaledTileID(16, 0, 14, 8191, 8192).key, + new OverscaledTileID(16, 0, 14, 8192, 8191).key, + new OverscaledTileID(16, 0, 14, 8191, 8191).key ]); t.end(); } @@ -852,14 +877,14 @@ test('SourceCache#_updateRetainedTiles', (t) => { } const retained = sourceCache._updateRetainedTiles([idealTile], 3); - t.deepEqual(Object.keys(retained), [ + t.deepEqual(Object.keys(retained).sort(), [ // parents are requested because ideal ideal tile is not completely covered by // loaded child tiles new OverscaledTileID(0, 0, 0, 0, 0), - new OverscaledTileID(1, 0, 1, 0, 0), new OverscaledTileID(2, 0, 2, 0, 1), + new OverscaledTileID(1, 0, 1, 0, 0), idealTile - ].concat(loadedChildren).map(t => String(t.key))); + ].concat(loadedChildren).map(t => t.key).sort()); t.end(); }); @@ -887,12 +912,12 @@ test('SourceCache#_updateRetainedTiles', (t) => { // retained tiles include all ideal tiles and any parents that were loaded to cover // non-existant tiles t.deepEqual(retained, { - // parent - '0': new OverscaledTileID(0, 0, 0, 0, 0), - // 1/0/1 - '65': new OverscaledTileID(1, 0, 1, 0, 1), + // 1/0/1 + '211': new OverscaledTileID(1, 0, 1, 0, 1), // 1/1/1 - '97': new OverscaledTileID(1, 0, 1, 1, 1) + '311': new OverscaledTileID(1, 0, 1, 1, 1), + // parent + '000': new OverscaledTileID(0, 0, 0, 0, 0) }); addTileSpy.restore(); getTileSpy.restore(); @@ -945,10 +970,11 @@ test('SourceCache#_updateRetainedTiles', (t) => { } }); const idealTile = new OverscaledTileID(1, 0, 1, 0, 1); + const parentTile = new OverscaledTileID(0, 0, 0, 0, 0); sourceCache._tiles[idealTile.key] = new Tile(idealTile); sourceCache._tiles[idealTile.key].state = 'loading'; - sourceCache._tiles['0'] = new Tile(new OverscaledTileID(0, 0, 0, 0, 0)); - sourceCache._tiles['0'].state = 'loaded'; + sourceCache._tiles[parentTile.key] = new Tile(parentTile); + sourceCache._tiles[parentTile.key].state = 'loaded'; const addTileSpy = t.spy(sourceCache, '_addTile'); const getTileSpy = t.spy(sourceCache, 'getTile'); @@ -962,9 +988,9 @@ test('SourceCache#_updateRetainedTiles', (t) => { t.deepEqual(retained, { // parent of ideal tile 0/0/0 - '0' : new OverscaledTileID(0, 0, 0, 0, 0), + '000' : new OverscaledTileID(0, 0, 0, 0, 0), // ideal tile id 1/0/1 - '65' : new OverscaledTileID(1, 0, 1, 0, 1) + '211' : new OverscaledTileID(1, 0, 1, 0, 1) }, 'retain ideal and parent tile when ideal tiles aren\'t loaded'); addTileSpy.resetHistory(); @@ -977,7 +1003,7 @@ test('SourceCache#_updateRetainedTiles', (t) => { t.ok(getTileSpy.notCalled); t.deepEqual(retainedLoaded, { // only ideal tile retained - '65' : new OverscaledTileID(1, 0, 1, 0, 1) + '211' : new OverscaledTileID(1, 0, 1, 0, 1) }, 'only retain ideal tiles when they\'re all loaded'); addTileSpy.restore(); @@ -1034,24 +1060,24 @@ test('SourceCache#_updateRetainedTiles', (t) => { t.deepEqual(retained, { // parent of ideal tile (0, 0, 0) (only partially covered by loaded child // tiles, so we still need to load the parent) - '0' : new OverscaledTileID(0, 0, 0, 0, 0), + '000' : new OverscaledTileID(0, 0, 0, 0, 0), // ideal tile id (1, 0, 0) - '1' : new OverscaledTileID(1, 0, 1, 0, 0), + '011' : new OverscaledTileID(1, 0, 1, 0, 0), // loaded child tile (2, 0, 0) - '2': new OverscaledTileID(2, 0, 2, 0, 0) + '022': new OverscaledTileID(2, 0, 2, 0, 0) }, 'retains children and parent when ideal tile is partially covered by a loaded child tile'); getTileSpy.restore(); // remove child tile and check that it only uses parent tile - delete sourceCache._tiles['2']; + delete sourceCache._tiles['022']; retained = sourceCache._updateRetainedTiles([idealTile], 1); t.deepEqual(retained, { // parent of ideal tile (0, 0, 0) (only partially covered by loaded child // tiles, so we still need to load the parent) - '0' : new OverscaledTileID(0, 0, 0, 0, 0), + '000' : new OverscaledTileID(0, 0, 0, 0, 0), // ideal tile id (1, 0, 0) - '1' : new OverscaledTileID(1, 0, 1, 0, 0) + '011' : new OverscaledTileID(1, 0, 1, 0, 0) }, 'only retains parent tile if no child tiles are loaded'); t.end(); @@ -1079,7 +1105,7 @@ test('SourceCache#_updateRetainedTiles', (t) => { t.deepEqual(retained, { // ideal tile id (2, 0, 0) - '2' : new OverscaledTileID(2, 0, 2, 0, 0) + '022' : new OverscaledTileID(2, 0, 2, 0, 0) }, 'doesn\'t retain parent tiles below minzoom'); getTileSpy.restore(); @@ -1109,7 +1135,7 @@ test('SourceCache#_updateRetainedTiles', (t) => { t.deepEqual(retained, { // ideal tile id (2, 0, 0) - '2' : new OverscaledTileID(2, 0, 2, 0, 0) + '022' : new OverscaledTileID(2, 0, 2, 0, 0) }, 'doesn\'t retain child tiles above maxzoom'); getTileSpy.restore(); @@ -1178,10 +1204,10 @@ test('SourceCache#_updateRetainedTiles', (t) => { const retained = sourceCache._updateRetainedTiles(idealTiles, 8); t.deepEqual(Object.keys(retained), [ - new OverscaledTileID(7, 0, 7, 0, 0).key, - new OverscaledTileID(8, 0, 7, 0, 0).key, new OverscaledTileID(7, 0, 7, 1, 0).key, - new OverscaledTileID(8, 0, 7, 1, 0).key + new OverscaledTileID(8, 0, 7, 1, 0).key, + new OverscaledTileID(8, 0, 7, 0, 0).key, + new OverscaledTileID(7, 0, 7, 0, 0).key ]); t.end(); @@ -1277,12 +1303,12 @@ test('SourceCache#tilesIn', (t) => { tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); - t.equal(tiles[0].tile.tileID.key, 1); + t.equal(tiles[0].tile.tileID.key, "011"); t.equal(tiles[0].tile.tileSize, 512); t.equal(tiles[0].scale, 1); t.deepEqual(round(tiles[0].queryGeometry), [{x: 4096, y: 4050}, {x:12288, y: 8146}]); - t.equal(tiles[1].tile.tileID.key, 33); + t.equal(tiles[1].tile.tileID.key, "111"); t.equal(tiles[1].tile.tileSize, 512); t.equal(tiles[1].scale, 1); t.deepEqual(round(tiles[1].queryGeometry), [{x: -4096, y: 4050}, {x: 4096, y: 8146}]); @@ -1315,10 +1341,10 @@ test('SourceCache#tilesIn', (t) => { sourceCache.update(transform); t.deepEqual(sourceCache.getIds(), [ - new OverscaledTileID(2, 0, 2, 1, 1).key, - new OverscaledTileID(2, 0, 2, 0, 1).key, - new OverscaledTileID(2, 0, 2, 1, 0).key, - new OverscaledTileID(2, 0, 2, 0, 0).key + new OverscaledTileID(2, 0, 1, 1, 1).key, + new OverscaledTileID(2, 0, 1, 0, 1).key, + new OverscaledTileID(2, 0, 1, 1, 0).key, + new OverscaledTileID(2, 0, 1, 0, 0).key ]); const tiles = sourceCache.tilesIn([ @@ -1329,12 +1355,12 @@ test('SourceCache#tilesIn', (t) => { tiles.sort((a, b) => { return a.tile.tileID.canonical.x - b.tile.tileID.canonical.x; }); tiles.forEach((result) => { delete result.tile.uid; }); - t.equal(tiles[0].tile.tileID.key, 2); + t.equal(tiles[0].tile.tileID.key, "012"); t.equal(tiles[0].tile.tileSize, 1024); t.equal(tiles[0].scale, 1); t.deepEqual(round(tiles[0].queryGeometry), [{x: 4096, y: 4050}, {x:12288, y: 8146}]); - t.equal(tiles[1].tile.tileID.key, 34); + t.equal(tiles[1].tile.tileID.key, "112"); t.equal(tiles[1].tile.tileSize, 1024); t.equal(tiles[1].scale, 1); t.deepEqual(round(tiles[1].queryGeometry), [{x: -4096, y: 4050}, {x: 4096, y: 8146}]); @@ -1472,6 +1498,70 @@ test('SourceCache#findLoadedParent', (t) => { t.end(); }); + t.test('Search cache for loaded parent tiles', (t) => { + const sourceCache = createSourceCache({}); + sourceCache.onAdd(); + const tr = new Transform(); + tr.width = 512; + tr.height = 512; + sourceCache.updateCacheSize(tr); + + const mockTile = id => { + const tile = { + tileID: id, + hasData() { return true; } + }; + sourceCache._tiles[id.key] = tile; + }; + + const tiles = [ + new OverscaledTileID(0, 0, 0, 0, 0), + new OverscaledTileID(1, 0, 1, 1, 0), + new OverscaledTileID(2, 0, 2, 0, 0), + new OverscaledTileID(2, 0, 2, 1, 0), + new OverscaledTileID(2, 0, 2, 2, 0), + new OverscaledTileID(2, 0, 2, 1, 2) + ]; + + tiles.forEach(t => mockTile(t)); + sourceCache._updateLoadedParentTileCache(); + + // Loaded tiles excluding the root should be in the cache + t.equal(sourceCache.findLoadedParent(tiles[0], 0), undefined); + t.equal(sourceCache.findLoadedParent(tiles[1], 0).tileID, tiles[0]); + t.equal(sourceCache.findLoadedParent(tiles[2], 0).tileID, tiles[0]); + t.equal(sourceCache.findLoadedParent(tiles[3], 0).tileID, tiles[0]); + t.equal(sourceCache.findLoadedParent(tiles[4], 0).tileID, tiles[1]); + t.equal(sourceCache.findLoadedParent(tiles[5], 0).tileID, tiles[0]); + + t.equal(tiles[0].key in sourceCache._loadedParentTiles, false); + t.equal(tiles[1].key in sourceCache._loadedParentTiles, true); + t.equal(tiles[2].key in sourceCache._loadedParentTiles, true); + t.equal(tiles[3].key in sourceCache._loadedParentTiles, true); + t.equal(tiles[4].key in sourceCache._loadedParentTiles, true); + t.equal(tiles[5].key in sourceCache._loadedParentTiles, true); + + // Arbitray tiles should not in the cache + const notLoadedTiles = [ + new OverscaledTileID(2, 1, 2, 0, 0), + new OverscaledTileID(2, 0, 2, 3, 0), + new OverscaledTileID(2, 0, 2, 3, 3), + new OverscaledTileID(3, 0, 3, 2, 1) + ]; + + t.equal(sourceCache.findLoadedParent(notLoadedTiles[0], 0), undefined); + t.equal(sourceCache.findLoadedParent(notLoadedTiles[1], 0).tileID, tiles[1]); + t.equal(sourceCache.findLoadedParent(notLoadedTiles[2], 0).tileID, tiles[0]); + t.equal(sourceCache.findLoadedParent(notLoadedTiles[3], 0).tileID, tiles[3]); + + t.equal(notLoadedTiles[0].key in sourceCache._loadedParentTiles, false); + t.equal(notLoadedTiles[1].key in sourceCache._loadedParentTiles, false); + t.equal(notLoadedTiles[2].key in sourceCache._loadedParentTiles, false); + t.equal(notLoadedTiles[3].key in sourceCache._loadedParentTiles, false); + + t.end(); + }); + t.end(); }); diff --git a/test/unit/source/tile_id.test.js b/test/unit/source/tile_id.test.js index 10e276964af..09255aa5fb5 100644 --- a/test/unit/source/tile_id.test.js +++ b/test/unit/source/tile_id.test.js @@ -23,10 +23,10 @@ test('CanonicalTileID', (t) => { }); t.test('.key', (t) => { - t.deepEqual(new CanonicalTileID(0, 0, 0).key, 0); - t.deepEqual(new CanonicalTileID(1, 0, 0).key, 1); - t.deepEqual(new CanonicalTileID(1, 1, 0).key, 33); - t.deepEqual(new CanonicalTileID(1, 1, 1).key, 97); + t.deepEqual(new CanonicalTileID(0, 0, 0).key, "000"); + t.deepEqual(new CanonicalTileID(1, 0, 0).key, "011"); + t.deepEqual(new CanonicalTileID(1, 1, 0).key, "111"); + t.deepEqual(new CanonicalTileID(1, 1, 1).key, "311"); t.end(); }); @@ -77,11 +77,11 @@ test('OverscaledTileID', (t) => { }); t.test('.key', (t) => { - t.deepEqual(new OverscaledTileID(0, 0, 0, 0, 0).key, 0); - t.deepEqual(new OverscaledTileID(1, 0, 1, 0, 0).key, 1); - t.deepEqual(new OverscaledTileID(1, 0, 1, 1, 0).key, 33); - t.deepEqual(new OverscaledTileID(1, 0, 1, 1, 1).key, 97); - t.deepEqual(new OverscaledTileID(1, -1, 1, 1, 1).key, 225); + t.deepEqual(new OverscaledTileID(0, 0, 0, 0, 0).key, "000"); + t.deepEqual(new OverscaledTileID(1, 0, 1, 0, 0).key, "011"); + t.deepEqual(new OverscaledTileID(1, 0, 1, 1, 0).key, "111"); + t.deepEqual(new OverscaledTileID(1, 0, 1, 1, 1).key, "311"); + t.deepEqual(new OverscaledTileID(1, -1, 1, 1, 1).key, "711"); t.end(); }); diff --git a/test/unit/ui/map.test.js b/test/unit/ui/map.test.js index 47da135e998..059291b980d 100755 --- a/test/unit/ui/map.test.js +++ b/test/unit/ui/map.test.js @@ -322,11 +322,11 @@ test('Map', (t) => { const map = createMap(t, {style}); t.equal(map.areTilesLoaded(), true, 'returns true if there are no sources on the map'); map.on('load', () => { - + const fakeTileId = new OverscaledTileID(0, 0, 0, 0, 0); map.addSource('geojson', createStyleSource()); - map.style.sourceCaches.geojson._tiles.fakeTile = new Tile(new OverscaledTileID(0, 0, 0, 0, 0)); + map.style.sourceCaches.geojson._tiles[fakeTileId.key] = new Tile(fakeTileId); t.equal(map.areTilesLoaded(), false, 'returns false if tiles are loading'); - map.style.sourceCaches.geojson._tiles.fakeTile.state = 'loaded'; + map.style.sourceCaches.geojson._tiles[fakeTileId.key].state = 'loaded'; t.equal(map.areTilesLoaded(), true, 'returns true if tiles are loaded'); t.end(); });