From 2de65767658794647fdc619e6bb6aee4e3ae44a3 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 14:51:17 +0100 Subject: [PATCH 1/6] fix PausableThread: set 'mPausing = false' on thread after pause, not on call to proceed() as the thread may already have been stopped in which case awaitPausing will loop forever -- short: dont use thread utility classes you havent written yourself :) --- vtm/src/org/oscim/utils/PausableThread.java | 41 +++++++++++++++------ 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/vtm/src/org/oscim/utils/PausableThread.java b/vtm/src/org/oscim/utils/PausableThread.java index 19a75d7a..cb4641b7 100644 --- a/vtm/src/org/oscim/utils/PausableThread.java +++ b/vtm/src/org/oscim/utils/PausableThread.java @@ -17,12 +17,18 @@ */ package org.oscim.utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * An abstract base class for threads which support pausing and resuming. */ public abstract class PausableThread extends Thread { + private final static Logger log = LoggerFactory.getLogger(PausableThread.class); + private final static boolean DEBUG = false; + private boolean mPausing = true; - private boolean mShouldPause; + private boolean mShouldPause = false; /** * Causes the current thread to wait until this thread is pausing. @@ -30,8 +36,10 @@ public abstract class PausableThread extends Thread { public final void awaitPausing() { synchronized (this) { while (!isInterrupted() && !isPausing()) { + if (DEBUG) + log.debug("await {}", getThreadName()); try { - wait(100); + wait(10); } catch (InterruptedException e) { // restore the interrupted status Thread.currentThread().interrupt(); @@ -42,17 +50,13 @@ public abstract class PausableThread extends Thread { @Override public void interrupt() { + if (DEBUG) + log.debug("interrupt {}", getThreadName()); + // first acquire the monitor which is used to call wait() synchronized (this) { super.interrupt(); } - - //try { - // this.join(10000); - //} catch (InterruptedException e) { - // // restore the interrupted status - // Thread.currentThread().interrupt(); - //} } /** @@ -72,14 +76,16 @@ public abstract class PausableThread extends Thread { } } + public final synchronized boolean isCanceled() { + return mShouldPause; + } + /** * The paused thread should continue with its work. */ public final synchronized void proceed() { if (mShouldPause) { mShouldPause = false; - mPausing = false; - afterPause(); notify(); } } @@ -102,6 +108,14 @@ public abstract class PausableThread extends Thread { interrupt(); } } + + if (mPausing) { + mPausing = false; + afterPause(); + } + + if (DEBUG) + log.debug("resume {}", getThreadName()); } if (isInterrupted()) { @@ -116,6 +130,11 @@ public abstract class PausableThread extends Thread { } } + if (DEBUG) + log.debug("finish {}", getThreadName()); + + mPausing = true; + afterRun(); } From 348e63b4fb97fa233b1830fa9f6410bb6fc64438 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 15:15:07 +0100 Subject: [PATCH 2/6] synchronize clearing TileLayer on its TileRenderer instead of global MapRenderer.drawlock --- vtm/src/org/oscim/layers/tile/TileLayer.java | 42 ++++++++-------- vtm/src/org/oscim/renderer/MapRenderer.java | 13 +---- vtm/src/org/oscim/tiling/TileLoader.java | 3 +- vtm/src/org/oscim/tiling/TileManager.java | 52 ++++++-------------- vtm/src/org/oscim/tiling/TileRenderer.java | 9 ++-- 5 files changed, 46 insertions(+), 73 deletions(-) diff --git a/vtm/src/org/oscim/layers/tile/TileLayer.java b/vtm/src/org/oscim/layers/tile/TileLayer.java index 45245b9f..304183af 100644 --- a/vtm/src/org/oscim/layers/tile/TileLayer.java +++ b/vtm/src/org/oscim/layers/tile/TileLayer.java @@ -24,9 +24,12 @@ import org.oscim.map.Map; import org.oscim.tiling.TileLoader; import org.oscim.tiling.TileManager; import org.oscim.tiling.TileRenderer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; public abstract class TileLayer extends Layer implements Map.UpdateListener { - //static final Logger log = LoggerFactory.getLogger(TileLayer.class); + static final Logger log = LoggerFactory.getLogger(TileLayer.class); + private final static int MAX_ZOOMLEVEL = 17; private final static int MIN_ZOOMLEVEL = 2; private final static int CACHE_LIMIT = 250; @@ -37,8 +40,6 @@ public abstract class TileLayer extends Layer implements M protected final int mNumTileLoader = 4; protected final ArrayList mTileLoader; - protected boolean mInitial = true; - public TileLayer(Map map) { this(map, MIN_ZOOMLEVEL, MAX_ZOOMLEVEL, CACHE_LIMIT); } @@ -71,10 +72,13 @@ public abstract class TileLayer extends Layer implements M @Override public void onMapUpdate(MapPosition mapPosition, boolean changed, boolean clear) { - if (clear || mInitial) { - mRenderLayer.clearTiles(); - mTileManager.init(mInitial); - mInitial = false; + if (clear) { + // sync with TileRenderer + synchronized (mRenderLayer) { + mRenderLayer.clearTiles(); + mTileManager.init(); + } + changed = true; } @@ -87,16 +91,13 @@ public abstract class TileLayer extends Layer implements M for (T loader : mTileLoader) { loader.pause(); loader.interrupt(); + try { + loader.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } loader.cleanup(); - - //try { - // tileWorker.join(10000); - //} catch (InterruptedException e) { - // // restore the interrupted status - // Thread.currentThread().interrupt(); - //} } - mTileManager.destroy(); } void notifyLoaders() { @@ -110,11 +111,12 @@ public abstract class TileLayer extends Layer implements M if (!loader.isPausing()) loader.pause(); } - if (wait) { - for (T loader : mTileLoader) { - if (!loader.isPausing()) - loader.awaitPausing(); - } + if (!wait) + return; + + for (T loader : mTileLoader) { + if (!loader.isPausing()) + loader.awaitPausing(); } } diff --git a/vtm/src/org/oscim/renderer/MapRenderer.java b/vtm/src/org/oscim/renderer/MapRenderer.java index ab41d0eb..6e7ba379 100644 --- a/vtm/src/org/oscim/renderer/MapRenderer.java +++ b/vtm/src/org/oscim/renderer/MapRenderer.java @@ -90,10 +90,6 @@ public class MapRenderer { private static volatile boolean mUpdateColor = false; - // drawlock to synchronize Main- and GL-Thread - // static ReentrantLock tilelock = new ReentrantLock(); - public static Object drawlock = new Object(); - public static long frametime; // Do not use the same buffer to upload data within a frame twice @@ -210,13 +206,8 @@ public class MapRenderer { } public void onDrawFrame() { - - // prevent main thread recreating all tiles (updateMap) - // while rendering is going on. - synchronized (drawlock) { - frametime = System.currentTimeMillis(); - draw(); - } + frametime = System.currentTimeMillis(); + draw(); mBufferPool.releaseBuffers(); } diff --git a/vtm/src/org/oscim/tiling/TileLoader.java b/vtm/src/org/oscim/tiling/TileLoader.java index 302be92c..c4febc83 100644 --- a/vtm/src/org/oscim/tiling/TileLoader.java +++ b/vtm/src/org/oscim/tiling/TileLoader.java @@ -42,6 +42,7 @@ public abstract class TileLoader extends PausableThread { @Override protected void doWork() { + MapTile tile = mTileManager.getTileJob(); if (tile == null) @@ -53,7 +54,7 @@ public abstract class TileLoader extends PausableThread { success = executeJob(tile); } catch (Exception e) { e.printStackTrace(); - return; + success = false; } if (isInterrupted()) diff --git a/vtm/src/org/oscim/tiling/TileManager.java b/vtm/src/org/oscim/tiling/TileManager.java index 12a7da91..29e452a6 100644 --- a/vtm/src/org/oscim/tiling/TileManager.java +++ b/vtm/src/org/oscim/tiling/TileManager.java @@ -132,11 +132,6 @@ public class TileManager { mUpdateSerial = 0; } - public void destroy() { - // there might be some leaks in here - // ... free static pools - } - private int[] mZoomTable; public void setZoomTable(int[] zoomLevel) { @@ -144,40 +139,23 @@ public class TileManager { } - public void init(boolean first) { + public void init() { + // pass VBOs and VertexItems back to pools + for (int i = 0; i < mTilesSize; i++) + clearTile(mTiles[i]); - // sync with GLRender thread - // ... and labeling thread? - synchronized (MapRenderer.drawlock) { + // clear references to cached MapTiles + Arrays.fill(mTiles, null); + mTilesSize = 0; + mTilesCount = 0; - if (!first) { - // pass VBOs and VertexItems back to pools - for (int i = 0; i < mTilesSize; i++) - clearTile(mTiles[i]); - } + // set up TileSet large enough to hold current tiles + int num = Math.max(mMap.getWidth(), mMap.getHeight()); + int size = Tile.SIZE >> 1; + int numTiles = (num * num) / (size * size) * 4; - // FIXME any of this still needed? - // mInitialized is set when surface changed - // and VBOs might be lost - // VertexPool.init(); - // clear cache index - // QuadTree.init(); - - // clear references to cached MapTiles - Arrays.fill(mTiles, null); - mTilesSize = 0; - mTilesCount = 0; - - // set up TileSet large enough to hold current tiles - int num = Math.max(mMap.getWidth(), mMap.getHeight()); - int size = Tile.SIZE >> 1; - int numTiles = (num * num) / (size * size) * 4; - - mNewTiles = new TileSet(numTiles); - mCurrentTiles = new TileSet(numTiles); - log.debug("max tiles: " + numTiles); - - } + mNewTiles = new TileSet(numTiles); + mCurrentTiles = new TileSet(numTiles); } /** @@ -187,7 +165,7 @@ public class TileManager { * @param pos * current MapPosition */ - public synchronized boolean update(MapPosition pos) { + public boolean update(MapPosition pos) { // 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 diff --git a/vtm/src/org/oscim/tiling/TileRenderer.java b/vtm/src/org/oscim/tiling/TileRenderer.java index bbf1bad2..1d3f269c 100644 --- a/vtm/src/org/oscim/tiling/TileRenderer.java +++ b/vtm/src/org/oscim/tiling/TileRenderer.java @@ -64,8 +64,11 @@ public class TileRenderer extends LayerRenderer { mAlpha = alpha; } + /** + * synced with clearTiles + */ @Override - protected void update(MapPosition pos, boolean positionChanged, Matrices m) { + protected synchronized void update(MapPosition pos, boolean positionChanged, Matrices m) { if (mAlpha == 0) { mTileManager.releaseTiles(mDrawTiles); @@ -106,9 +109,7 @@ public class TileRenderer extends LayerRenderer { public void clearTiles() { // Clear all references to MapTiles as all current // tiles will also be removed from TileManager. - synchronized (MapRenderer.drawlock) { - mDrawTiles = new TileSet(); - } + mDrawTiles = new TileSet(); } /** compile tile layer data and upload to VBOs */ From 0710e5776e8c5ece38341626764c8e043d91e27d Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 16:09:39 +0100 Subject: [PATCH 3/6] fix deadlock on AndroidMap<>Viewport: - on GL-Thread: MapRenderer.draw()->MapAnimator->update()->AndroidMap.updateMap [sync: Viewport [sync: AndroidMap ]] - on Mainloop: AndroidMap.redrawMapInternal()->updateLayers()->Viewport.getMapPosition() [sync:AndroidMap [sync:Viewport]] ... no need to sync on AndroidMap --- .../src/org/oscim/android/AndroidMap.java | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/vtm-android/src/org/oscim/android/AndroidMap.java b/vtm-android/src/org/oscim/android/AndroidMap.java index e8d70966..e29d2a52 100644 --- a/vtm-android/src/org/oscim/android/AndroidMap.java +++ b/vtm-android/src/org/oscim/android/AndroidMap.java @@ -23,16 +23,10 @@ import android.widget.RelativeLayout.LayoutParams; public class AndroidMap extends Map { private final MapView mMapView; - private boolean mWaitRedraw; final GLView mGLView; - boolean mPausing = false; - private final Runnable mRedrawRequest = new Runnable() { - @Override - public void run() { - redrawMapInternal(false); - } - }; + private volatile boolean mWaitRedraw; + private volatile boolean mPausing; public AndroidMap(MapView mapView) { super(); @@ -45,8 +39,6 @@ public class AndroidMap extends Map { android.view.ViewGroup.LayoutParams.MATCH_PARENT); mapView.addView(mGLView, params); - - //mGestureDetector = } @Override @@ -60,9 +52,9 @@ public class AndroidMap extends Map { } @Override - public synchronized void updateMap(boolean requestRender) { - if (requestRender && !mClearMap && !mPausing) // && mInitialized) - mGLView.requestRender(); + public void updateMap(boolean redraw) { + //if (redraw && !mClearMap && !mPausing) + // mGLView.requestRender(); if (!mWaitRedraw) { mWaitRedraw = true; @@ -71,26 +63,29 @@ public class AndroidMap extends Map { } @Override - public synchronized void render() { + public void render() { + if (mPausing) + return; + if (mClearMap) updateMap(false); else mGLView.requestRender(); } - synchronized void redrawMapInternal(boolean forceRedraw) { - boolean clear = mClearMap; - mWaitRedraw = false; + private final Runnable mRedrawRequest = new Runnable() { + @Override + public void run() { + redrawMapInternal(); + } + }; - if (forceRedraw && !clear) - mGLView.requestRender(); + void redrawMapInternal() { + mWaitRedraw = false; updateLayers(); - if (clear) { - mGLView.requestRender(); - mClearMap = false; - } + mGLView.requestRender(); } @Override @@ -103,9 +98,7 @@ public class AndroidMap extends Map { return mMapView.postDelayed(action, delay); } - - public synchronized void pause(boolean pause) { - mPausing = pause; - } - + public void pause(boolean pause) { + mPausing = pause; + } } From ec8821e557c17e617f75bf212f1c3cb7818e17fe Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 17:10:27 +0100 Subject: [PATCH 4/6] cancel TileLoader job when thread shouldPause - handle Exceptions in PbfTileDataSource, cleanup in finally{} --- .../layers/tile/vector/VectorTileLoader.java | 26 ++++++--- .../oscim/tiling/source/common/LwHttp.java | 45 +++++++++------ .../source/common/PbfTileDataSource.java | 55 +++++++------------ 3 files changed, 65 insertions(+), 61 deletions(-) diff --git a/vtm/src/org/oscim/layers/tile/vector/VectorTileLoader.java b/vtm/src/org/oscim/layers/tile/vector/VectorTileLoader.java index 79456dd9..6c9e4b40 100644 --- a/vtm/src/org/oscim/layers/tile/vector/VectorTileLoader.java +++ b/vtm/src/org/oscim/layers/tile/vector/VectorTileLoader.java @@ -16,6 +16,8 @@ */ package org.oscim.layers.tile.vector; +import java.util.concurrent.CancellationException; + import org.oscim.core.GeometryBuffer.GeometryType; import org.oscim.core.MapElement; import org.oscim.core.MercatorProjection; @@ -131,14 +133,18 @@ public class VectorTileLoader extends TileLoader implements IRenderTheme.Callbac mTile = tile; mTile.layers = new ElementLayers(); - - // query database, which calls 'process' callback - QueryResult result = mTileDataSource.executeQuery(mTile, this); - - mTile = null; - - clearState(); - + QueryResult result = null; + try { + // query database, which calls 'process' callback + result = mTileDataSource.executeQuery(mTile, this); + } catch (CancellationException e) { + log.debug("canceled {}", mTile); + } catch (Exception e) { + log.debug("{}", e); + } finally { + mTile = null; + clearState(); + } return (result == QueryResult.SUCCESS); } @@ -205,8 +211,12 @@ public class VectorTileLoader extends TileLoader implements IRenderTheme.Callbac @Override public void process(MapElement element) { + clearState(); + if (isCanceled()) + throw new CancellationException(); + mElement = element; if (element.type == GeometryType.POINT) { diff --git a/vtm/src/org/oscim/tiling/source/common/LwHttp.java b/vtm/src/org/oscim/tiling/source/common/LwHttp.java index 415f0441..206f8aa2 100644 --- a/vtm/src/org/oscim/tiling/source/common/LwHttp.java +++ b/vtm/src/org/oscim/tiling/source/common/LwHttp.java @@ -56,6 +56,7 @@ public class LwHttp { private Socket mSocket; private OutputStream mCommandStream; private InputStream mResponseStream; + private OutputStream mCacheOutputStream; private long mLastRequest = 0; private SocketAddress mSockAddr; @@ -106,40 +107,34 @@ public class LwHttp { } static class Buffer extends BufferedInputStream { - final OutputStream mCache; + final OutputStream mCacheOutputstream; public Buffer(InputStream is, OutputStream cache) { super(is, 4096); - mCache = cache; + mCacheOutputstream = cache; } @Override - public synchronized int read() throws IOException { + public int read() throws IOException { int data = super.read(); if (data >= 0) - mCache.write(data); + mCacheOutputstream.write(data); return data; } @Override - public synchronized int read(byte[] buffer, int offset, int byteCount) + public int read(byte[] buffer, int offset, int byteCount) throws IOException { int len = super.read(buffer, offset, byteCount); if (len >= 0) - mCache.write(buffer, offset, len); + mCacheOutputstream.write(buffer, offset, len); return len; } } - OutputStream mCacheOutputStream; - - public void setOutputStream(OutputStream outputStream) { - mCacheOutputStream = outputStream; - } - public void close() { if (mSocket != null) { try { @@ -177,6 +172,10 @@ public class LwHttp { while (end < read && (buf[end] != '\n')) end++; + if (end == BUFFER_SIZE) { + return null; + } + if (buf[end] != '\n') continue; @@ -209,7 +208,7 @@ public class LwHttp { if (!ok) { String line = new String(buf, pos, end - pos - 1); - log.debug(">" + line + "< "); + log.debug("> {} <", line); } pos += (end - pos) + 1; @@ -255,12 +254,14 @@ public class LwHttp { mMaxReq = RESPONSE_EXPECTED_LIVES; // log.debug("create connection"); } else { - // FIXME not sure if this is correct way to drain socket int avail = mResponseStream.available(); if (avail > 0) { - log.debug("Consume left-over bytes: " + avail); - while ((avail = mResponseStream.available()) > 0) - mResponseStream.read(buffer); + log.debug("left-over bytes: " + avail); + close(); + lwHttpConnect(); + // FIXME not sure if this is correct way to drain socket + //while ((avail = mResponseStream.available()) > 0) + // mResponseStream.read(buffer); } } @@ -353,8 +354,16 @@ public class LwHttp { return true; } - public void requestCompleted() { + public void setOutputStream(OutputStream outputStream) { + mCacheOutputStream = outputStream; + } + + public void requestCompleted(boolean keepConnection) { mLastRequest = System.nanoTime(); + mCacheOutputStream = null; + + if (!keepConnection) + close(); } public int getContentLength() { diff --git a/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java b/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java index 1d016a55..44bf74c8 100644 --- a/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java +++ b/vtm/src/org/oscim/tiling/source/common/PbfTileDataSource.java @@ -30,10 +30,6 @@ import org.oscim.utils.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** - * - * - */ public abstract class PbfTileDataSource implements ITileDataSource { static final Logger log = LoggerFactory.getLogger(PbfTileDataSource.class); @@ -48,66 +44,55 @@ public abstract class PbfTileDataSource implements ITileDataSource { @Override public QueryResult executeQuery(MapTile tile, ITileDataSink sink) { - boolean success = true; ITileCache.TileWriter cacheWriter = null; if (mTileCache != null) { ITileCache.TileReader c = mTileCache.getTile(tile); if (c == null) { - // create new cache entry cacheWriter = mTileCache.writeTile(tile); - mConn.setOutputStream(cacheWriter.getOutputStream()); } else { + InputStream is = c.getInputStream(); try { - InputStream is = c.getInputStream(); if (mTileDecoder.decode(tile, sink, is, c.getBytes())) { - IOUtils.closeQuietly(is); return QueryResult.SUCCESS; } } catch (IOException e) { e.printStackTrace(); + } finally { + IOUtils.closeQuietly(is); } - log.debug(tile + " Cache read failed"); } } + boolean success = false; try { + if (cacheWriter != null) + mConn.setOutputStream(cacheWriter.getOutputStream()); + InputStream is; if (!mConn.sendRequest(tile)) { - log.debug(tile + " Request failed"); - success = false; - } else if ((is = mConn.readHeader()) != null) { + log.debug("{} Request failed", tile); + } else if ((is = mConn.readHeader()) == null) { + log.debug("{} Network Error", tile); + } else { int bytes = mConn.getContentLength(); success = mTileDecoder.decode(tile, sink, is, bytes); - if (!success) - log.debug(tile + " Decoding failed"); - } else { - log.debug(tile + " Network Error"); - success = false; } } catch (SocketException e) { - log.debug(tile + " Socket exception: " + e.getMessage()); - success = false; + log.debug("{} Socket exception: {}", tile, e.getMessage()); } catch (SocketTimeoutException e) { - log.debug(tile + " Socket Timeout"); - success = false; + log.debug("{} Socket Timeout", tile); } catch (UnknownHostException e) { - log.debug(tile + " No Network"); - success = false; - } catch (Exception e) { + log.debug("{} No Network", tile); + } catch (IOException e) { e.printStackTrace(); - success = false; + } finally { + mConn.requestCompleted(success); + + if (cacheWriter != null) + cacheWriter.complete(success); } - - mConn.requestCompleted(); - - if (cacheWriter != null) - cacheWriter.complete(success); - - if (success) - mConn.close(); - return success ? QueryResult.SUCCESS : QueryResult.FAILED; } From e76b0a374064c0a1095dc813a0d1b48fbd47ca28 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 22:01:51 +0100 Subject: [PATCH 5/6] refactor TextRenderer, sync MapTile in Label-Thread --- .../tile/vector/labeling/LabelLayer.java | 28 +- .../tile/vector/labeling/TextRenderer.java | 403 ++++++++---------- .../org/oscim/utils/async/ContinuousTask.java | 91 ++++ 3 files changed, 284 insertions(+), 238 deletions(-) create mode 100644 vtm/src/org/oscim/utils/async/ContinuousTask.java diff --git a/vtm/src/org/oscim/layers/tile/vector/labeling/LabelLayer.java b/vtm/src/org/oscim/layers/tile/vector/labeling/LabelLayer.java index eb3ecaac..db6b189e 100644 --- a/vtm/src/org/oscim/layers/tile/vector/labeling/LabelLayer.java +++ b/vtm/src/org/oscim/layers/tile/vector/labeling/LabelLayer.java @@ -28,7 +28,7 @@ public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateLi static final Logger log = LoggerFactory.getLogger(LabelLayer.class); private final TextRenderer mTextRenderer; - private int multi; + //private int multi; public LabelLayer(Map map, TileRenderer tileRenderLayer) { super(map); @@ -49,19 +49,19 @@ public class LabelLayer extends Layer implements Map.InputListener, Map.UpdateLi @Override public void onMotionEvent(MotionEvent e) { - int action = e.getAction() & MotionEvent.ACTION_MASK; - if (action == MotionEvent.ACTION_POINTER_DOWN) { - multi++; - mTextRenderer.hold(true); - } else if (action == MotionEvent.ACTION_POINTER_UP) { - multi--; - if (multi == 0) - mTextRenderer.hold(false); - } else if (action == MotionEvent.ACTION_CANCEL) { - multi = 0; - log.debug("cancel " + multi); - mTextRenderer.hold(false); - } + // int action = e.getAction() & MotionEvent.ACTION_MASK; + // if (action == MotionEvent.ACTION_POINTER_DOWN) { + // multi++; + // mTextRenderer.hold(true); + // } else if (action == MotionEvent.ACTION_POINTER_UP) { + // multi--; + // if (multi == 0) + // mTextRenderer.hold(false); + // } else if (action == MotionEvent.ACTION_CANCEL) { + // multi = 0; + // log.debug("cancel " + multi); + // mTextRenderer.hold(false); + // } } @Override diff --git a/vtm/src/org/oscim/layers/tile/vector/labeling/TextRenderer.java b/vtm/src/org/oscim/layers/tile/vector/labeling/TextRenderer.java index a80383d2..4e4278f5 100644 --- a/vtm/src/org/oscim/layers/tile/vector/labeling/TextRenderer.java +++ b/vtm/src/org/oscim/layers/tile/vector/labeling/TextRenderer.java @@ -51,6 +51,7 @@ import org.oscim.tiling.TileRenderer; import org.oscim.tiling.TileSet; import org.oscim.utils.FastMath; import org.oscim.utils.OBB2D; +import org.oscim.utils.async.ContinuousTask; import org.oscim.utils.pool.Pool; class TextRenderer extends ElementRenderer { @@ -143,6 +144,8 @@ class TextRenderer extends ElementRenderer { //mActiveTiles = new HashMap(); mRelabelCnt = 0; + + mLabelTask = new LabelTask(map); } // remove Label l from mLabels and return l.next @@ -274,6 +277,108 @@ class TextRenderer extends ElementRenderer { return dx; } + private Label updateWayLabels(MapTile t, Label l, float dx, float dy, double scale, + ElementLayers dbg) { + + for (TextItem ti = t.labels; ti != null; ti = ti.next) { + if (ti.text.caption) + continue; + + // acquire a TextItem to add to TextLayer + if (l == null) + l = getLabel(); + + // check if path at current scale is long enough for text + if (dbg == null && ti.width > ti.length * scale) + continue; + + l.clone(ti); + l.move(ti, dx, dy, (float) scale); + + // set line endpoints relative to view to be able to + // check intersections with label from other tiles + float w = (ti.x2 - ti.x1) / 2f; + float h = (ti.y2 - ti.y1) / 2f; + l.bbox = null; + l.x1 = l.x - w; + l.y1 = l.y - h; + l.x2 = l.x + w; + l.y2 = l.y + h; + + if (!wayIsVisible(l)) + continue; + + byte overlaps = -1; + + if (l.bbox == null) + l.bbox = new OBB2D(l.x, l.y, l.x1, l.y1, + l.width + MIN_WAY_DIST, + l.text.fontHeight + MIN_WAY_DIST); + else + l.bbox.set(l.x, l.y, l.x1, l.y1, + l.width + MIN_WAY_DIST, + l.text.fontHeight + MIN_WAY_DIST); + + if (dbg == null || ti.width < ti.length * scale) + overlaps = checkOverlap(l); + + if (dbg != null) + Debug.addDebugBox(dbg, l, ti, overlaps, false, (float) scale); + + if (overlaps == 0) { + addLabel(l); + l.item = TextItem.copy(ti); + l.tile = t; + l.active = mRelabelCnt; + l = null; + } + } + return l; + } + + private Label updateNodeLabels(MapTile t, Label l, float dx, float dy, double scale, float cos, + float sin) { + O: for (TextItem ti = t.labels; ti != null; ti = ti.next) { + if (!ti.text.caption) + continue; + + // acquire a TextItem to add to TextLayer + if (l == null) + l = getLabel(); + + l.clone(ti); + l.move(ti, dx, dy, (float) scale); + if (!nodeIsVisible(l)) + continue; + + if (l.bbox == null) + l.bbox = new OBB2D(); + + l.bbox.setNormalized(l.x, l.y, cos, -sin, + l.width + MIN_CAPTION_DIST, + l.text.fontHeight + MIN_CAPTION_DIST, + l.text.dy); + + for (Label lp = mLabels; lp != null;) { + if (l.bbox.overlaps(lp.bbox)) { + if (l.text.priority < lp.text.priority) { + lp = removeLabel(lp); + continue; + } + continue O; + } + lp = (Label) lp.next; + } + + addLabel(l); + l.item = TextItem.copy(ti); + l.tile = t; + l.active = mRelabelCnt; + l = null; + } + return l; + } + boolean updateLabels() { // nextLayer is not loaded yet if (mNextLayer.ready) @@ -400,118 +505,30 @@ class TextRenderer extends ElementRenderer { /* add way labels */ for (int i = 0, n = mTileSet.cnt; i < n; i++) { MapTile t = tiles[i]; - if (!t.state(MapTile.STATE_READY)) - continue; - - float dx = (float) (t.tileX * Tile.SIZE - tileX); - float dy = (float) (t.tileY * Tile.SIZE - tileY); - dx = flipLongitude(dx, maxx); - - for (TextItem ti = t.labels; ti != null; ti = ti.next) { - - if (ti.text.caption) + synchronized (t) { + if (!t.state(MapTile.STATE_READY)) continue; - // acquire a TextItem to add to TextLayer - if (l == null) - l = getLabel(); + float dx = (float) (t.tileX * Tile.SIZE - tileX); + float dy = (float) (t.tileY * Tile.SIZE - tileY); + dx = flipLongitude(dx, maxx); - // check if path at current scale is long enough for text - if (dbg == null && ti.width > ti.length * scale) - continue; - - l.clone(ti); - l.move(ti, dx, dy, (float) scale); - - // set line endpoints relative to view to be able to - // check intersections with label from other tiles - float w = (ti.x2 - ti.x1) / 2f; - float h = (ti.y2 - ti.y1) / 2f; - l.bbox = null; - l.x1 = l.x - w; - l.y1 = l.y - h; - l.x2 = l.x + w; - l.y2 = l.y + h; - - if (!wayIsVisible(l)) - continue; - - byte overlaps = -1; - - if (l.bbox == null) - l.bbox = new OBB2D(l.x, l.y, l.x1, l.y1, - l.width + MIN_WAY_DIST, - l.text.fontHeight + MIN_WAY_DIST); - else - l.bbox.set(l.x, l.y, l.x1, l.y1, - l.width + MIN_WAY_DIST, - l.text.fontHeight + MIN_WAY_DIST); - - if (dbg == null || ti.width < ti.length * scale) - overlaps = checkOverlap(l); - - if (dbg != null) - Debug.addDebugBox(dbg, l, ti, overlaps, false, (float) scale); - - if (overlaps == 0) { - addLabel(l); - l.item = TextItem.copy(ti); - l.tile = t; - l.active = mRelabelCnt; - l = null; - } + l = updateWayLabels(t, l, dx, dy, scale, dbg); } } /* add caption */ for (int i = 0, n = mTileSet.cnt; i < n; i++) { MapTile t = tiles[i]; - if (!t.state(MapTile.STATE_READY)) - continue; - - float dx = (float) (t.tileX * Tile.SIZE - tileX); - float dy = (float) (t.tileY * Tile.SIZE - tileY); - dx = flipLongitude(dx, maxx); - - O: for (TextItem ti = t.labels; ti != null; ti = ti.next) { - if (!ti.text.caption) + synchronized (t) { + if (!t.state(MapTile.STATE_READY)) continue; - // acquire a TextItem to add to TextLayer - if (l == null) - l = getLabel(); + float dx = (float) (t.tileX * Tile.SIZE - tileX); + float dy = (float) (t.tileY * Tile.SIZE - tileY); + dx = flipLongitude(dx, maxx); - l.clone(ti); - l.move(ti, dx, dy, (float) scale); - if (!nodeIsVisible(l)) - continue; - - //l.setAxisAlignedBBox(); - - if (l.bbox == null) - l.bbox = new OBB2D(); - - l.bbox.setNormalized(l.x, l.y, cos, -sin, - l.width + MIN_CAPTION_DIST, - l.text.fontHeight + MIN_CAPTION_DIST, - l.text.dy); - - for (Label lp = mLabels; lp != null;) { - if (l.bbox.overlaps(lp.bbox)) { - if (l.text.priority < lp.text.priority) { - lp = removeLabel(lp); - continue; - } - continue O; - } - lp = (Label) lp.next; - } - - addLabel(l); - l.item = TextItem.copy(ti); - l.tile = t; - l.active = mRelabelCnt; - l = null; + l = updateNodeLabels(t, l, dx, dy, scale, cos, sin); } } @@ -541,25 +558,27 @@ class TextRenderer extends ElementRenderer { for (int i = 0, n = mTileSet.cnt; i < n; i++) { MapTile t = tiles[i]; - if (!t.state(MapTile.STATE_READY)) - continue; - - float dx = (float) (t.tileX * Tile.SIZE - tileX); - float dy = (float) (t.tileY * Tile.SIZE - tileY); - dx = flipLongitude(dx, maxx); - - for (SymbolItem ti = t.symbols; ti != null; ti = ti.next) { - if (ti.texRegion == null) + synchronized (t) { + if (!t.state(MapTile.STATE_READY)) continue; - SymbolItem s = SymbolItem.pool.get(); + float dx = (float) (t.tileX * Tile.SIZE - tileX); + float dy = (float) (t.tileY * Tile.SIZE - tileY); + dx = flipLongitude(dx, maxx); - s.texRegion = ti.texRegion; - s.x = (float) ((dx + ti.x) * scale); - s.y = (float) ((dy + ti.y) * scale); - s.billboard = true; + for (SymbolItem ti = t.symbols; ti != null; ti = ti.next) { + if (ti.texRegion == null) + continue; - sl.addSymbol(s); + SymbolItem s = SymbolItem.pool.get(); + + s.texRegion = ti.texRegion; + s.x = (float) ((dx + ti.x) * scale); + s.y = (float) ((dy + ti.y) * scale); + s.billboard = true; + + sl.addSymbol(s); + } } } @@ -588,11 +607,6 @@ class TextRenderer extends ElementRenderer { public synchronized void update(MapPosition pos, boolean changed, Matrices matrices) { - //if (System.currentTimeMillis() - lastDraw > 1000){ - // updateLabels(); - // lastDraw = System.currentTimeMillis(); - // - //} if (mNextLayer.ready) { // exchange current with next layers TextureLayers tmp = mCurLayer; @@ -615,127 +629,68 @@ class TextRenderer extends ElementRenderer { compile(); } - if (mRequestClear) - cleanup(); - - //if (!mHolding) - postLabelTask(); - } - - /* private */LabelTask mLabelTask; - /* private */long mLastRun; - /* private */boolean mRequestRun; - /* private */boolean mRequestClear; - /* private */boolean mRelabel; - - class LabelTask implements Runnable { - private boolean isCancelled; - - @Override - public void run() { - boolean labelsChanged = false; - if (isCancelled) { - return; - } - long now = System.currentTimeMillis(); - //log.debug("relabel after " + (now - mLastRun)); - mLastRun = now; - - labelsChanged = updateLabels(); - - if (!isCancelled && labelsChanged) - mMap.render(); - - mLabelTask = null; - mRequestRun = false; - isCancelled = false; - - if (mRelabel) { - mRelabel = false; - postLabelTask(); - } - } - - public void cancel() { - isCancelled = true; - } - } - - /* private */void cleanup() { - mLabels = (Label) mPool.releaseAll(mLabels); - mTileSet.releaseTiles(); - mLabelTask = null; - mRequestClear = false; - } - - private final Runnable mLabelUpdate = new Runnable() { - @Override - public void run() { - if (mLabelTask == null) { - mLabelTask = new LabelTask(); - mMap.addTask(mLabelTask); - } - } - }; - - /* private */void postLabelTask() { - synchronized (mLabelUpdate) { - if (mRequestRun) { - mRelabel = true; - } else { - mRequestRun = true; - long delay = (mLastRun + MAX_RELABEL_DELAY) - System.currentTimeMillis(); - //log.debug("relabel in: " + delay); - mMap.postDelayed(mLabelUpdate, Math.max(delay, 0)); - } - } + mLabelTask.submit((mLastRun + MAX_RELABEL_DELAY) - System.currentTimeMillis()); } @Override public synchronized void render(MapPosition pos, Matrices m) { - - layers.vbo.bind(); GLState.test(false, false); float scale = (float) (mMapPosition.scale / pos.scale); setMatrix(pos, m, true); - if (layers.baseLayers != null) { - for (RenderElement l = layers.baseLayers; l != null;) { - if (l.type == RenderElement.POLYGON) { - l = PolygonLayer.Renderer.draw(pos, l, m, true, 1, false); - } else { - float div = scale * (float) (pos.scale / (1 << pos.zoomLevel)); - l = LineLayer.Renderer.draw(layers, l, pos, m, div, 0); + synchronized (layers) { + layers.vbo.bind(); + if (layers.baseLayers != null) { + for (RenderElement l = layers.baseLayers; l != null;) { + if (l.type == RenderElement.POLYGON) { + l = PolygonLayer.Renderer.draw(pos, l, m, true, 1, false); + } else { + float div = scale * (float) (pos.scale / (1 << pos.zoomLevel)); + l = LineLayer.Renderer.draw(layers, l, pos, m, div, 0); + } } } + + setMatrix(pos, m, false); + + for (RenderElement l = layers.textureLayers; l != null;) + l = TextureLayer.Renderer.draw(l, scale, m); } - - setMatrix(pos, m, false); - - for (RenderElement l = layers.textureLayers; l != null;) - l = TextureLayer.Renderer.draw(l, scale, m); } - //private boolean mHolding; + final class LabelTask extends ContinuousTask { - /** - * @param enable layer updates - */ - public synchronized void hold(boolean enable) { - // mHolding = enable; - // if (!enable) - // runLabelTask(); + public LabelTask(Map map) { + super(map, 10); + } + + @Override + public void doWork() { + + if (updateLabels()) + mMap.render(); + + mLastRun = System.currentTimeMillis(); + } + + @Override + public void cleanup() { + clearLabelsInternal(); + } } - public synchronized void clearLabels() { - if (mRequestRun) { - mRequestClear = true; - //mRelabel = true; - } else { - cleanup(); - //postLabelTask(); - } + private final LabelTask mLabelTask; + + /* private */long mLastRun; + + /* private */void clearLabelsInternal() { + mLabels = (Label) mPool.releaseAll(mLabels); + mTileSet.releaseTiles(); + } + + public void clearLabels() { + mLabelTask.cancel(); } } diff --git a/vtm/src/org/oscim/utils/async/ContinuousTask.java b/vtm/src/org/oscim/utils/async/ContinuousTask.java new file mode 100644 index 00000000..a8584b53 --- /dev/null +++ b/vtm/src/org/oscim/utils/async/ContinuousTask.java @@ -0,0 +1,91 @@ +package org.oscim.utils.async; + +import org.oscim.map.Map; + +public abstract class ContinuousTask implements Runnable { + private final Map mMap; + + protected boolean mRunning; + protected boolean mWait; + protected boolean mCancel; + protected boolean mDelayed; + + protected long mMinDelay; + + public ContinuousTask(Map map, long minDelay) { + mMap = map; + mMinDelay = minDelay; + } + + @Override + public void run() { + + synchronized (this) { + //System.out.println("run " + mRunning + " " + // + mCancel + " " + mDelayed + " " + mWait); + + if (mCancel) { + mCancel = false; + mRunning = false; + mDelayed = false; + mWait = false; + cleanup(); + return; + } + if (mDelayed) { + // entered on main-loop + mDelayed = false; + mWait = false; + // unset running temporarily + mRunning = false; + submit(0); + return; + } + } + + doWork(); + + synchronized (this) { + mRunning = false; + + if (mCancel) + cleanup(); + else if (mWait) + submit(mMinDelay); + + mCancel = false; + mWait = false; + } + } + + public abstract void doWork(); + + public abstract void cleanup(); + + public synchronized void submit(long delay) { + //System.out.println("submit " + mRunning + " " + mCancel + " " + delay); + + if (mRunning) { + mWait = true; + return; + } + mRunning = true; + if (delay <= 0) { + mMap.addTask(this); + return; + } + + mDelayed = true; + mMap.postDelayed(this, delay); + + } + + public synchronized void cancel() { + if (mRunning) { + mCancel = true; + return; + } + + cleanup(); + } +} From 25c95da80cd208106c4ab068d2aaffb819f80118 Mon Sep 17 00:00:00 2001 From: Hannes Janetzek Date: Sun, 19 Jan 2014 17:15:42 +0100 Subject: [PATCH 6/6] set MapTile.STATE_CANCEL when tile is cleared in TileManager.jobCompleted clear tile when tile state is CANCEL --- vtm/src/org/oscim/tiling/MapTile.java | 5 +++-- vtm/src/org/oscim/tiling/TileManager.java | 19 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vtm/src/org/oscim/tiling/MapTile.java b/vtm/src/org/oscim/tiling/MapTile.java index 1a811dcf..316309f9 100644 --- a/vtm/src/org/oscim/tiling/MapTile.java +++ b/vtm/src/org/oscim/tiling/MapTile.java @@ -61,6 +61,8 @@ public class MapTile extends Tile { */ public final static byte STATE_ERROR = 1 << 3; + public final static byte STATE_CANCEL = 1 << 4; + /** * absolute tile coordinates: tileX,Y / Math.pow(2, zoomLevel) */ @@ -89,7 +91,7 @@ public class MapTile extends Tile { } /** - * Tile is in view region. Set by GLRenderer. + * Tile is in view region. Set by TileRenderer. */ public boolean isVisible; @@ -100,7 +102,6 @@ public class MapTile extends Tile { /** * to avoid drawing a tile twice per frame - * FIXME what if multiple layers use the same tile? */ int lastDraw = 0; diff --git a/vtm/src/org/oscim/tiling/TileManager.java b/vtm/src/org/oscim/tiling/TileManager.java index 29e452a6..ec559420 100644 --- a/vtm/src/org/oscim/tiling/TileManager.java +++ b/vtm/src/org/oscim/tiling/TileManager.java @@ -17,9 +17,9 @@ package org.oscim.tiling; +import static org.oscim.tiling.MapTile.STATE_CANCEL; import static org.oscim.tiling.MapTile.STATE_LOADING; import static org.oscim.tiling.MapTile.STATE_NEW_DATA; -import static org.oscim.tiling.MapTile.STATE_NONE; import java.util.ArrayList; import java.util.Arrays; @@ -29,7 +29,6 @@ import org.oscim.core.Tile; import org.oscim.map.Map; import org.oscim.map.Viewport; import org.oscim.renderer.BufferObject; -import org.oscim.renderer.MapRenderer; import org.oscim.utils.FastMath; import org.oscim.utils.ScanBox; import org.oscim.utils.quadtree.QuadTree; @@ -383,13 +382,14 @@ public class TileManager { if (t == null) return; - t.clear(); - - mIndex.remove(t); - - // QuadTree.remove(t); - t.state = STATE_NONE; + synchronized (t) { + // still belongs to TileLoader thread + if (t.state != STATE_LOADING) + t.clear(); + t.state = STATE_CANCEL; + mIndex.remove(t); + } mTilesCount--; } @@ -527,8 +527,7 @@ public class TileManager { * @return caller does not care */ public void jobCompleted(MapTile tile, boolean success) { - - if (!success) { + if (!success || tile.state == STATE_CANCEL) { tile.clear(); return; }