/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.eas.client.controls.geopane.cache;
import com.eas.client.controls.geopane.TilesBoundaries;
import com.eas.concurrent.PlatypusThreadFactory;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.geotools.map.MapContent;
import org.geotools.renderer.GTRenderer;
import org.geotools.renderer.lite.StreamingRenderer;
/**
*
* @author mg
*/
public class AsyncMapTilesCache extends MapTilesCache {
public class AsyncRenderingTask extends RenderingTask {
protected Thread executingThread;
private boolean rendered = false;
public AsyncRenderingTask(Point aTilePoint) throws NoninvertibleTransformException {
super(aTilePoint);
}
@Override
public void run() {
mapContextLock.readLock().lock();
try {
executingThread = Thread.currentThread();
if (!isStopped() && isActual()) {
try {
super.run();
setRendered(true);
} finally {
offeredTasks.remove(tilePoint);
// fireRenderingTaskCompleted() must be called strictly after offeredTasks.remove() !
fireRenderingTaskCompleted(this);
}
} else {
offeredTasks.remove(tilePoint);
}
} finally {
mapContextLock.readLock().unlock();
}
}
@Override
protected boolean isActual() {
return getActualArea().contains(tilePoint);
}
@Override
protected GTRenderer archieveRenderer() {
return new StreamingRenderer();
}
public void stop() throws InterruptedException {
assert taskRenderer != null;
taskRenderer.stopRendering();
setStopped(true);
}
/**
* @return the rendered
*/
public synchronized boolean isRendered() {
return rendered;
}
/**
* @param aRendered the rendered to set
*/
public synchronized void setRendered(boolean aRendered) {
rendered = aRendered;
}
}
protected Map<Point, AsyncRenderingTask> offeredTasks = new ConcurrentHashMap<>();
protected ExecutorService executor = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), Runtime.getRuntime().availableProcessors(),
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(), new PlatypusThreadFactory());
protected Set<RenderingTaskListener> listeners = new HashSet<>();
protected ReadWriteLock mapContextLock;
public AsyncMapTilesCache(int aCacheSize, MapContent aDisplayContext, ReadWriteLock aMapContextLock, AffineTransform aTransform) {
super(aCacheSize, aDisplayContext, aTransform);
mapContextLock = aMapContextLock;
}
public AsyncMapTilesCache(MapContent aDisplayContext, ReadWriteLock aMapContextLock, AffineTransform aTransform) {
super(aDisplayContext, aTransform);
mapContextLock = aMapContextLock;
}
@Override
protected Image renderTile(Point ptKey) {
try {
AsyncRenderingTask task = new AsyncRenderingTask(ptKey);
assert !executor.isShutdown();
offeredTasks.put(ptKey, task);
executor.execute(task);
return null;
} catch (NoninvertibleTransformException ex) {
Logger.getLogger(AsyncMapTilesCache.class.getName()).log(Level.SEVERE, null, ex);
return null;
}
}
public void shutdown() {
try2StopSomeTasks();
wait4AllTasksCompleted();
executor.shutdown();
super.clear();
}
@Override
public void clear() {
// all tasks are invalid and cache entries to.
// hint some tasks to quikly complete their's work.
try2StopSomeTasks();
// wait while ALL tasks complete (hinted to shutdown and all others)
wait4AllTasksCompleted();
// clear the cache
super.clear();
}
@Override
public synchronized Image get(Point ptKey) {
if (offeredTasks.containsKey(ptKey)) {
// may be one of the working rendering tasks alreadey have put results in the cache.
return cache.get(ptKey);
}
// There is no task rendering ptKey tile, and so let's relay on the super get/offer mechanism
return super.get(ptKey);
}
@Override
public void setActualArea(TilesBoundaries aActualArea) {
// let's hint invalid, but already offered tasks.
try2StopSomeUnactualTasks(aActualArea);
super.setActualArea(aActualArea);
}
/**
* Makes to executing and queued tasks a hint to shutdown any work and remove thereselfs from
* offeredTasks collection.
* This is only hint because of multithreaded environment. Not all the tasks will undestand it.
*/
protected void try2StopSomeTasks() {
try2StopSomeUnactualTasks(TilesBoundaries.EMPTY);
}
protected void try2StopSomeUnactualTasks(TilesBoundaries aActualArea) {
for (Point taskPoint : offeredTasks.keySet()) {
if (!aActualArea.contains(taskPoint)) {
AsyncRenderingTask task = offeredTasks.get(taskPoint);
if (task != null && task.isRendered()) {
try {
task.stop();
} catch (InterruptedException ex) {
Logger.getLogger(AsyncMapTilesCache.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
protected void wait4AllTasksCompleted() {
// wait while all tasks complete execution
while (!offeredTasks.isEmpty()) {
try {
assert !executor.isShutdown();
Thread.sleep(4);
} catch (InterruptedException ex) {
Logger.getLogger(AsyncMapTilesCache.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
@Override
public void scaleChanged() {
// clear the cache.
clear();
}
public synchronized void addRenderingTaskListener(RenderingTaskListener l) {
listeners.add(l);
}
public synchronized void removeRenderingTaskListener(RenderingTaskListener l) {
listeners.remove(l);
}
/**
* Fires an event that rendering task had completed
* WARNING!!! This method is called from executor service thread and it
* have to use all appropriate synchronization.
*/
protected synchronized void fireRenderingTaskCompleted(AsyncRenderingTask aTask) {
for (RenderingTaskListener l : listeners) {
l.taskCompleted(aTask);
}
}
}