package net.sharenav.sharenav.mapdata; /* * ShareNav - Copyright (c) 2007 Harald Mueller james22 at users dot sourceforge dot net See Copying */ import java.io.IOException; import java.util.Vector; import net.sharenav.sharenav.data.Configuration; import net.sharenav.sharenav.data.Legend; import net.sharenav.sharenav.graphics.ImageCollector; import net.sharenav.sharenav.tile.SingleTile; import net.sharenav.sharenav.tile.Tile; import net.sharenav.sharenav.ui.ShareNav; import net.sharenav.sharenav.ui.Trace; import net.sharenav.util.Logger; import de.enough.polish.util.Locale; public abstract class QueueReader implements Runnable { protected static final Logger logger = Logger.getInstance(QueueReader.class, Logger.ERROR); protected final Vector requestQueue = new Vector(); protected final Vector notificationQueue = new Vector(); protected final Vector livingQueue = new Vector(); private volatile boolean shut = false; private final Thread processorThread; public QueueReader(String name) { super(); processorThread = new Thread(this,name); processorThread.setPriority(Thread.MIN_PRIORITY + 1); processorThread.start(); } public abstract void readData(Tile tt, Object notifyReady) throws IOException; public synchronized void shutdown() { shut = true; } public synchronized void incUnusedCounter() { Tile tt; int loop; for (loop = 0; loop < livingQueue.size(); loop++) { tt = (Tile) livingQueue.elementAt(loop); if (tt.lastUse < 126) tt.lastUse++; } for (loop = 0; loop < requestQueue.size(); loop++) { tt = (Tile) requestQueue.elementAt(loop); if (tt.lastUse < 126) tt.lastUse++; } } public synchronized void add(Tile st, Object notifyReady) { st.lastUse = 0; requestQueue.addElement(st); notificationQueue.addElement(notifyReady); notify(); } public synchronized void dropCache() { Tile tt; int loop; cleanOldLivingTiles(0); // Should the request queue made empty here? for (loop = 0; loop < requestQueue.size(); loop++) { tt = (Tile) requestQueue.elementAt(loop); tt.cleanup(0); } } private void cleanupUnused() { int loop; Tile tt; if (ShareNav.getInstance().needsFreeingMemory()) { loop = cleanOldLivingTiles(3); for (loop = 0; loop < requestQueue.size(); loop++) { tt = (Tile) requestQueue.elementAt(loop); if (tt.cleanup(2)) { // logger.info("cleanup live " + tt.fileId); synchronized (this) { notificationQueue.removeElementAt(loop); requestQueue.removeElementAt(loop--); } } } } else { //#debug debug logger.debug("Not cleaning up caches, still enough memory left"); // it make not really sense to keep all tiles in memory // if there is enough so remove very old tile in every case: // very old is in this case not used for the last 120 paints cleanOldLivingTiles(120); } } /** * Removes tile requests for SingleTiles which contain data that is not rendered by ImageCollector because zoomed out too far, * also removes tile requests for SingleTiles that are offScreen for ImageCollector because zoomed in too far. * * This allows zooming out, panning around and zooming in without having to wait ages for obsolete tile requests. */ private void cleanupUnnecessarySingleTileRequests() { Tile tt; SingleTile st; int droppedCountZoomedOut = 0; int droppedCountZoomedIn = 0; Trace trace = Trace.getInstance(); for (int loop = 0; loop < requestQueue.size(); loop++) { tt = (Tile) requestQueue.elementAt(loop); if (tt instanceof SingleTile) { st = (SingleTile) tt; if ( (st.zl > ImageCollector.minTile || !trace.isTileRequiredByImageCollector(tt)) && tt.cleanup(1)) { if (st.zl > ImageCollector.minTile) { droppedCountZoomedOut++; } else { droppedCountZoomedIn++; } synchronized (this) { notificationQueue.removeElementAt(loop); requestQueue.removeElementAt(loop--); } } } } if ((droppedCountZoomedOut > 0 || droppedCountZoomedIn > 0) && Configuration.getCfgBitState(Configuration.CFGBIT_SHOW_TILE_REQUESTS_DROPPED)) { StringBuffer sb = new StringBuffer(); if (droppedCountZoomedOut > 0) { sb.append(droppedCountZoomedOut + " zl>" + ImageCollector.minTile); } if (droppedCountZoomedIn > 0) { if (sb.length() > 0) { sb.append(", "); } sb.append(droppedCountZoomedIn + " offScreen"); } sb.append(" tile requests dropped"); trace.receiveMessage(sb.toString()); } } private int cleanOldLivingTiles(int age) { int loop; Tile tt; for (loop = 0; loop < livingQueue.size(); loop++) { tt = (Tile) livingQueue.elementAt(loop); if (tt.cleanup(age)) { // logger.info("cleanup living " + tt.fileId); synchronized (this) { livingQueue.removeElementAt(loop--); } } } return loop; } public void run() { Tile tt; // logger.info("DataReader Thread start "); try { while (!shut) { //#debug debug logger.debug("DataReader Thread looped "); try { // logger.info("loop: " + livingQueue.size() + " / " + requestQueue.size()); // logger.info(toString()); cleanupUnused(); cleanupUnnecessarySingleTileRequests(); try { final Runtime runtime = Runtime.getRuntime(); if ((runtime.freeMemory() > 25000) || (runtime.totalMemory() < ShareNav.getInstance().getPhoneMaxMemory())) { if (requestQueue.size() > 0) { Object notifyReady; synchronized (this) { tt = (Tile) requestQueue.firstElement(); requestQueue.removeElementAt(0); notifyReady = notificationQueue.firstElement(); notificationQueue.removeElementAt(0); } readData(tt, notifyReady); synchronized (this) { livingQueue.addElement(tt); } } } else { //#debug info logger.info("Not much memory left, cleaning up and trying again"); Trace.getInstance().cleanup(); System.gc(); } } catch (final OutOfMemoryError oome) { logger.error(Locale.get("queuereader.OOMReadingTiles")/*Out of memory reading tiles, trying to recover*/); Trace.getInstance().dropCache(); } catch (final IOException e) { logger.exception(Locale.get("queuereader.FailedToReadTile")/*Failed to read tile*/, e); synchronized (this) { /* Start a fresh */ requestQueue.removeAllElements(); notificationQueue.removeAllElements(); } } synchronized (this) { if (requestQueue.size() == 0) { try { wait(10000); } catch (final InterruptedException e) { } } } } catch (final OutOfMemoryError oome) { logger.error(Locale.get("queuereader.OOMReadTilesNotRec")/*Out of memory while trying to read tiles. Not recovering*/); } catch (final RuntimeException e) { logger.exception(Locale.get("queuereader.ExceptionReadingTilesCont")/*Exception reading tiles, continuing never the less*/, e); } } } catch (final Exception e) { logger.fatal(Locale.get("queuereader.QueueReaderCrashed")/*QueueReader thread crashed unexpectedly with error */ + e.getMessage()); } // logger.info("DataReader Thread end "); } public int getLivingTilesCount() { return livingQueue.size(); } public int getRequestQueueSize() { return requestQueue.size(); } }