TileManager:

- set zoomMin/Max for current TileSource
- add zoom-level switch threshold
- add TileManager.getTile()
- remove releaseTiles()
- extract JobCompletedEvent from anonymous class
This commit is contained in:
Hannes Janetzek 2014-04-28 13:47:09 +02:00
parent 3fa121d31a
commit a8400dd48a
4 changed files with 142 additions and 91 deletions

View File

@ -1,5 +1,4 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2012, 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
@ -20,11 +19,16 @@ package org.oscim.layers.tile;
import static org.oscim.layers.tile.MapTile.State.LOADING;
import static org.oscim.layers.tile.MapTile.State.NONE;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A JobQueue keeps the list of pending jobs for a MapView and prioritizes them.
*/
public class JobQueue {
static final Logger log = LoggerFactory.getLogger(JobQueue.class);
private int mCurrentJob = 0;
private MapTile[] mJobs;
@ -51,7 +55,12 @@ public class JobQueue {
MapTile[] tiles = mJobs;
for (int i = mCurrentJob, n = mJobs.length; i < n; i++) {
tiles[i].state = NONE;
if (tiles[i].state == LOADING)
tiles[i].state = NONE;
else
log.debug("wrong tile in queue {} {}", tiles[i], tiles[i].state);
tiles[i] = null;
}
mCurrentJob = 0;

View File

@ -51,8 +51,8 @@ public class TileManager {
private final int mCacheLimit;
private int mCacheReduce;
private final int mMinZoom;
private final int mMaxZoom;
private int mMinZoom;
private int mMaxZoom;
private int[] mZoomTable;
@ -60,11 +60,12 @@ public class TileManager {
* limit number tiles with new data not uploaded to GL
* TODO this should depend on the number of tiles displayed
*/
private static final int MAX_TILES_IN_QUEUE = 40;
private static final int MAX_TILES_IN_QUEUE = 20;
/** cache limit threshold */
private static final int CACHE_THRESHOLD = 30;
private static final int CACHE_THRESHOLD = 25;
private static final int CACHE_CLEAR_THRESHOLD = 10;
private static final int QUEUE_CLEAR_THRESHOLD = 10;
private final Map mMap;
private final Viewport mViewport;
@ -81,49 +82,55 @@ public class TileManager {
/** counter for tiles with new data not yet loaded to GL */
private int mTilesForUpload;
/* new tile jobs for MapWorkers */
/** new tile jobs for MapWorkers */
private final ArrayList<MapTile> mJobs;
/* counter to check whether current TileSet has changed */
/** counter to check whether current TileSet has changed */
private int mUpdateSerial;
/* lock for TileSets while updating MapTile locks - still needed? */
/** lock for TileSets while updating MapTile locks - still needed? */
private final Object mTilelock = new Object();
private TileSet mCurrentTiles;
/* package */TileSet mNewTiles;
/* job queue filled in TileManager and polled by TileLoaders */
/** job queue filled in TileManager and polled by TileLoaders */
private final JobQueue jobQueue;
private final float[] mMapPlane = new float[8];
private final TileIndex<TileNode, MapTile> mIndex = new TileIndex<TileNode, MapTile>() {
private boolean mLoadParent = false;
private int mPrevZoomlevel;
@Override
public void removeItem(MapTile t) {
if (t.node == null) {
log.error("already removed {}", t);
return;
}
//private final double mLevelUpThreshold = 1.15;
//private final double mLevelDownThreshold = 1.95;
private final double mLevelUpThreshold = 1;
private final double mLevelDownThreshold = 2;
super.remove(t.node);
t.node.item = null;
}
private final TileIndex<TileNode, MapTile> mIndex =
new TileIndex<TileNode, MapTile>() {
@Override
public void removeItem(MapTile t) {
if (t.node == null)
throw new IllegalStateException("already removed: " + t);
@Override
public TileNode create() {
return new TileNode();
}
};
super.remove(t.node);
t.node.item = null;
}
public final EventDispatcher<Listener, MapTile> events = new EventDispatcher<Listener, MapTile>() {
@Override
public TileNode create() {
return new TileNode();
}
};
@Override
public void tell(Listener l, Event event, MapTile tile) {
l.onTileManagerEvent(event, tile);
}
};
public final EventDispatcher<Listener, MapTile> events =
new EventDispatcher<Listener, MapTile>() {
@Override
public void tell(Listener l, Event event, MapTile tile) {
l.onTileManagerEvent(event, tile);
}
};
public interface Listener extends EventListener {
void onTileManagerEvent(Event event, MapTile tile);
@ -149,7 +156,12 @@ public class TileManager {
public void setZoomTable(int[] zoomLevel) {
mZoomTable = zoomLevel;
}
public MapTile getTile(int x, int y, int z) {
synchronized (mTilelock) {
return mIndex.getTile(x, y, z);
}
}
public void init() {
@ -186,9 +198,10 @@ public class TileManager {
public boolean update(MapPosition pos) {
// FIXME cant expect init to be called otherwise
if (mNewTiles == null || mNewTiles.tiles.length == 0)
if (mNewTiles == null || mNewTiles.tiles.length == 0) {
mPrevZoomlevel = pos.zoomLevel;
init();
}
/* clear JobQueue and set tiles to state == NONE.
* one could also append new tiles and sort in JobQueue
* but this has the nice side-effect that MapWorkers dont
@ -208,7 +221,27 @@ public class TileManager {
int tileZoom = FastMath.clamp(pos.zoomLevel, mMinZoom, mMaxZoom);
if (mZoomTable != null) {
//mLoadParent = pos.getZoomScale() < 1.5;
/* greater 1 when zoomed in further than current zoomlevel */
double scaleDiv = pos.scale / (1 << tileZoom);
mLoadParent = false;
if (mZoomTable == null) {
/* positive when current zoomlevel is greater than previous */
int zoomDiff = tileZoom - mPrevZoomlevel;
if (zoomDiff == 1) {
/* dont switch zoomlevel if */
if (scaleDiv < mLevelUpThreshold)
tileZoom = mPrevZoomlevel;
} else if (zoomDiff == -1) {
mLoadParent = true;
/* dont switch zoomlevel if */
if (scaleDiv > mLevelDownThreshold)
tileZoom = mPrevZoomlevel;
}
} else {
int match = 0;
for (int z : mZoomTable) {
if (z <= tileZoom && z > match)
@ -219,11 +252,13 @@ public class TileManager {
tileZoom = match;
}
//log.debug("{}|{}|{}|{}", mLoadParent, mPrevZoomlevel, pos.zoomLevel, scaleDiv);
mPrevZoomlevel = tileZoom;
mViewport.getMapExtents(mMapPlane, Tile.SIZE / 2);
// scan visible tiles. callback function calls 'addTile'
// which updates mNewTiles
/* scan visible tiles. callback function calls 'addTile'
* which updates mNewTiles */
mNewTiles.cnt = 0;
mScanBox.scan(pos.x, pos.y, pos.scale, tileZoom, mMapPlane);
@ -255,7 +290,7 @@ public class TileManager {
/* unlock previous tiles */
mCurrentTiles.releaseTiles();
/* make new tiles current */
/* swap newTiles with currentTiles */
TileSet tmp = mCurrentTiles;
mCurrentTiles = mNewTiles;
mNewTiles = tmp;
@ -277,7 +312,6 @@ public class TileManager {
/* sets tiles to state == LOADING */
jobQueue.setJobs(jobs);
mJobs.clear();
if (mCacheReduce < mCacheLimit / 2) {
@ -294,13 +328,11 @@ public class TileManager {
if (remove > CACHE_THRESHOLD ||
mTilesForUpload > MAX_TILES_IN_QUEUE)
limitCache(pos, remove);
return true;
}
/** only used in setmapDatabase -- deprecate? */
public void clearJobs() {
jobQueue.clear();
}
@ -316,8 +348,9 @@ public class TileManager {
/**
* Retrive a TileSet of current tiles. Tiles remain locked in cache until
* the set is unlocked by either passing it again to this function or to
* releaseTiles. If passed TileSet is null it will be allocated.
* releaseTiles.
*
* @threadsafe
* @param tileSet
* to be updated
* @return true if TileSet has changed
@ -340,13 +373,6 @@ public class TileManager {
return true;
}
/**
* Unlock TileSet and clear all item references.
*/
public void releaseTiles(TileSet tileSet) {
tileSet.releaseTiles();
}
MapTile addTile(int x, int y, int zoomLevel) {
MapTile tile = mIndex.getTile(x, y, zoomLevel);
@ -359,14 +385,14 @@ public class TileManager {
mJobs.add(tile);
}
if ((zoomLevel > mMinZoom) && (mZoomTable == null)) {
if (mLoadParent && (zoomLevel > mMinZoom) && (mZoomTable == null)) {
/* prefetch parent */
MapTile p = tile.node.parent.item;
if (p == null) {
TileNode n = mIndex.add(x >> 1, y >> 1, zoomLevel - 1);
p = n.item = new MapTile(n, x >> 1, y >> 1, zoomLevel - 1);
addToCache(p);
// hack to not add tile twice to queue
/* hack to not add tile twice to queue */
p.state = LOADING;
mJobs.add(p);
} else if (!p.isActive()) {
@ -400,21 +426,19 @@ public class TileManager {
}
private void removeFromCache(MapTile t) {
if (t.state == NEW_DATA || t.state == READY)
if (t.state(NEW_DATA | READY))
events.fire(TILE_REMOVED, t);
synchronized (t) {
/* still belongs to TileLoader thread, defer clearing to
* jobCompleted() */
if (t.state != CANCEL)
t.clear();
if (dbg)
log.debug("remove from cache {} {} {}", t, t.state, t.isLocked());
// needed?
t.state = CANCEL;
//synchronized (t) {
/* When in CANCEL state tile belongs to TileLoader thread,
* defer clearing to jobCompleted() */
if (t.state(NEW_DATA | READY))
t.clear();
mIndex.removeItem(t);
}
mIndex.removeItem(t);
mTilesCount--;
}
@ -490,18 +514,15 @@ public class TileManager {
remove--;
}
remove = (newTileCnt - MAX_TILES_IN_QUEUE) + 10;
remove = (newTileCnt - MAX_TILES_IN_QUEUE) + QUEUE_CLEAR_THRESHOLD;
//int r = remove;
for (int i = size - 1; i >= 0 && remove > 0; i--) {
MapTile t = tiles[i];
if (t != null && t.state == NEW_DATA) {
if (!t.isLocked()) {
newTileCnt--;
removeFromCache(t);
tiles[i] = null;
remove--;
}
if (t != null && !t.isLocked() && t.state == NEW_DATA) {
newTileCnt--;
removeFromCache(t);
tiles[i] = null;
remove--;
}
}
@ -512,37 +533,53 @@ public class TileManager {
}
/**
* called by TileLoader thread when tile is loaded.
* Called by TileLoader thread when tile is loaded.
*
* @threadsafe
* @param tile
* Tile ready for upload in TileRenderLayer
*/
public void jobCompleted(final MapTile tile, final boolean success) {
mMap.post(new Runnable() {
public void jobCompleted(MapTile tile, boolean success) {
@Override
public void run() {
if (!success || tile.state == CANCEL) {
/* send TILE_LOADED event on main-loop */
mMap.post(new JobCompletedEvent(tile, success));
log.debug("loading {}: {}",
(success ? "canceled" : "failed"),
tile);
tile.clear();
return;
}
/* locked means the tile is visible or referenced by
* a tile that might be visible. */
if (tile.isLocked())
mMap.render();
}
class JobCompletedEvent implements Runnable {
final MapTile tile;
final boolean success;
public JobCompletedEvent(MapTile tile, boolean success) {
this.tile = tile;
this.success = success;
}
@Override
public void run() {
if (success && tile.state != CANCEL) {
tile.state = NEW_DATA;
events.fire(TILE_LOADED, tile);
mTilesForUpload += 1;
/* locked means the tile is visible or referenced by
* a tile that might be visible. */
if (tile.isLocked())
mMap.render();
mTilesForUpload++;
//log.debug("loading {}: ready", tile);
return;
}
});
log.debug("loading {}: {}", tile,
success ? "canceled"
: "failed");
tile.clear();
if (tile.state == LOADING)
mIndex.removeItem(tile);
}
}
private static void updateDistances(MapTile[] tiles, int size, MapPosition pos) {
@ -629,4 +666,9 @@ public class TileManager {
public MapTile getTile(int tileX, int tileY, byte zoomLevel) {
return mIndex.getTile(tileX, tileY, zoomLevel);
}
public void setZoomLevel(int zoomLevelMin, int zoomLevelMax) {
mMinZoom = zoomLevelMin;
mMaxZoom = zoomLevelMax;
}
}

View File

@ -81,7 +81,7 @@ public abstract class TileRenderer extends LayerRenderer {
public synchronized void update(GLViewport v) {
if (mAlpha == 0) {
mTileManager.releaseTiles(mDrawTiles);
mDrawTiles.releaseTiles();
return;
}

View File

@ -78,7 +78,7 @@ public class VectorTileLayer extends TileLayer {
public boolean setTileSource(TileSource tileSource) {
pauseLoaders(true);
mTileManager.clearJobs();
mTileManager.setZoomLevel(tileSource.getZoomLevelMin(), tileSource.getZoomLevelMax());
if (mTileSource != null) {
mTileSource.close();
mTileSource = null;