Skip to content

Commit

Permalink
Change the type of tile id key to string (mapbox#8979)
Browse files Browse the repository at this point in the history
* Change tile id key type from number to string
  • Loading branch information
mpulkki-mapbox authored and mike-unearth committed Mar 18, 2020
1 parent 42fee37 commit 220ff94
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 110 deletions.
4 changes: 2 additions & 2 deletions src/geo/transform.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/render/painter.js
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ class Painter {
viewportSegments: SegmentVector;
quadTriangleIndexBuffer: IndexBuffer;
tileBorderIndexBuffer: IndexBuffer;
_tileClippingMaskIDs: { [number]: number };
_tileClippingMaskIDs: { [string]: number };
stencilClearMode: StencilMode;
style: Style;
options: PainterOptions;
Expand Down
129 changes: 95 additions & 34 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,20 @@ class SourceCache extends Evented {
_source: Source;
_sourceLoaded: boolean;
_sourceErrored: boolean;
_tiles: {[any]: Tile};
_tiles: {[string]: Tile};
_prevLng: number | void;
_cache: TileCache;
_timers: {[any]: TimeoutID};
_cacheTimers: {[any]: TimeoutID};
_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;
Expand Down Expand Up @@ -93,6 +94,7 @@ class SourceCache extends Evented {
this._timers = {};
this._cacheTimers = {};
this._maxTileCacheSize = null;
this._loadedParentTiles = {};

this._coveredTiles = {};
this._state = new SourceFeatureState();
Expand Down Expand Up @@ -179,25 +181,25 @@ class SourceCache extends Evented {
/**
* Return all tile ids ordered with z-order, and cast to numbers
*/
getIds(): Array<number> {
return Object.keys(this._tiles).map(Number).sort(compareKeyZoom);
getIds(): Array<string> {
return (Object.values(this._tiles): any).map((tile: Tile) => tile.tileID).sort(compareTileId).map(id => id.key);
}

getRenderableIds(symbolLayer?: boolean): Array<number> {
const ids = [];
getRenderableIds(symbolLayer?: boolean): Array<string> {
const renderables: Array<Tile> = [];
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) {
Expand All @@ -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());
}
Expand All @@ -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
Expand All @@ -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}));
Expand Down Expand Up @@ -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];
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -537,6 +553,9 @@ class SourceCache extends Evented {
this._removeTile(tileID);
}
}

// Construct a cache of loaded parents
this._updateLoadedParentTileCache();
}

releaseSymbolFadeTiles() {
Expand All @@ -548,8 +567,8 @@ class SourceCache extends Evented {
}

_updateRetainedTiles(idealTileIDs: Array<OverscaledTileID>, 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);

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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];
Expand All @@ -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;
Expand Down Expand Up @@ -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<string>) {
setDependencies(tileKey: string, namespace: string, dependencies: Array<string>) {
const tile = this._tiles[tileKey];
if (tile) {
tile.setDependencies(namespace, dependencies);
Expand All @@ -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) {
Expand Down
8 changes: 4 additions & 4 deletions src/source/tile_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import type Tile from './tile';
*/
class TileCache {
max: number;
data: {[key: number | string]: Array<{ value: Tile, timeout: ?TimeoutID}>};
order: Array<number>;
data: {[key: string]: Array<{ value: Tile, timeout: ?TimeoutID}>};
order: Array<string>;
onRemove: (element: Tile) => void;
/**
* @param {number} max number of permitted values
Expand Down Expand Up @@ -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);

Expand All @@ -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;
}
Expand Down
Loading

0 comments on commit 220ff94

Please sign in to comment.