/**
* Copyright 2010-2013 Jingjing Li.
*
* This file is part of jplot2d.
*
* jplot2d is free software: you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or any later version.
*
* jplot2d is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with jplot2d. If not, see .
*/
package org.jplot2d.renderer;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import org.jplot2d.element.impl.ComponentEx;
/**
* This renderer assemble all cacheable component in a individual thread asynchronously.
*
* @author Jingjing Li
*
*/
public class AsyncImageRenderer extends ImageRenderer {
/**
* Add ability to cancel component rendering futures.
*/
private abstract class CancelableRendererCallable implements Callable {
protected final long sn;
protected final Rectangle bounds;
public CancelableRendererCallable(long sn, Rectangle bounds) {
this.sn = sn;
this.bounds = bounds;
}
/**
* Cancel its component rendering tasks.
*/
public abstract void cancel();
}
/**
* This callable render whole plot in a single thread
*/
private final class SingleRendererCallable extends CancelableRendererCallable {
private final CacheableBlock cacheableBlock;
public SingleRendererCallable(long sn, Rectangle bounds, CacheableBlock cacheableBlock) {
super(sn, bounds);
this.cacheableBlock = cacheableBlock;
}
public BufferedImage call() throws Exception {
return renderCacheableBlock(bounds, cacheableBlock);
}
public void cancel() {
// no component renderer need to cancel
}
}
/**
* This callable assemble component rendering futures.
*/
private final class RenderAssemblyCallable extends CancelableRendererCallable {
private final ImageAssemblyInfo ainfo;
private RenderAssemblyCallable(long sn, Rectangle bounds, ImageAssemblyInfo ainfo) {
super(sn, bounds);
this.ainfo = ainfo;
}
public BufferedImage call() throws Exception {
return assembleResult(sn, bounds, ainfo);
}
public void cancel() {
for (ComponentEx comp : ainfo.componentSet()) {
if (!isFutureCached(comp, ainfo.getFuture(comp))) {
// boolean cancelled =
ainfo.getFuture(comp).cancel(true);
// logger.trace("Cancelling cacheable block renderer for R.{} {}", sn, cancelled);
}
}
}
}
/**
* A FutureTask to run CancelableRendererCallable.
*/
private final class AsyncImageRendererTask extends FutureTask {
private final CancelableRendererCallable callable;
public AsyncImageRendererTask(CancelableRendererCallable callable) {
super(callable);
this.callable = callable;
}
public long getSN() {
return callable.sn;
}
protected final void done() {
if (this.isCancelled()) {
callable.cancel();
return;
}
BufferedImage result = null;
try {
result = get();
} catch (InterruptedException e) {
// should not happen. Normal cancellation will set the internal state to CANCELLED
} catch (ExecutionException e) {
logger.warn("Renderer exception, drop R." + getSN(), e);
}
fireRenderingFinished(getSN(), result);
/*
* remove this renderer and all old renderers from the queue, and cancel all old renderers. The cancelling
* only occur in CANCEL_AFTER_NEWER_DONE mode. In CANCEL_BEFORE_EXEC_NEWER mode, old renderers are cancelled
* and removed from the queue when new renderer is added.
*/
if (cancelPolicy == RendererCancelPolicy.CANCEL_AFTER_NEWER_DONE) {
synchronized (renderLock) {
/*
* The tasks in the queue are on SN order. We can't guarantee the current tasks exist in the queue.
* Another newer task may be done just before this method is called, and sweep all old tasks,
* include this. In this case, The renderer queue might be empty.
*/
AsyncImageRendererTask head = renderTaskQueue.peek();
if (head != null && head.getSN() <= getSN()) {
for (;;) {
AsyncImageRendererTask renderer = renderTaskQueue.poll();
if (renderer.getSN() == getSN()) {
break;
}
/* cancel old renderer */
if (renderer.getSN() < getSN() && renderer.cancel(true)) {
logger.trace("Renderer {} finished. Cancel the old renderer {}", getSN(),
renderer.getSN());
}
}
}
}
}
}
}
public static enum RendererCancelPolicy {
CANCEL_BEFORE_EXEC_NEWER, CANCEL_AFTER_NEWER_DONE, NO_CANCEL
}
private volatile RendererCancelPolicy cancelPolicy = RendererCancelPolicy.CANCEL_BEFORE_EXEC_NEWER;
/** synchronized area for renderer task queue */
private Object renderLock = new Object();
/** synchronized by renderLock */
private final Queue renderTaskQueue = new LinkedList();
public AsyncImageRenderer(ImageFactory imageFactory) {
super(imageFactory);
}
@Override
public final void render(ComponentEx comp, List cacheBlockList) {
Rectangle bounds = getDeviceBounds(comp);
CancelableRendererCallable callable;
if (cacheBlockList.size() == 1) {
// If the plot has no cacheable component, run renderer directly
callable = new SingleRendererCallable(fsn++, bounds, cacheBlockList.get(0));
} else {
// run cacheable component renderer
ImageAssemblyInfo ainfo = runCompRender(executor, cacheBlockList);
callable = new RenderAssemblyCallable(fsn++, bounds, ainfo);
}
synchronized (renderLock) {
AsyncImageRendererTask task = new AsyncImageRendererTask(callable);
/* remove all renderers from queue and cancel them */
if (cancelPolicy == RendererCancelPolicy.CANCEL_BEFORE_EXEC_NEWER) {
for (;;) {
AsyncImageRendererTask rtask = renderTaskQueue.poll();
if (rtask == null) {
break;
}
if (rtask.cancel(true)) {
logger.trace("Render task {} to be exec. The render task {} is cancelled.", task.getSN(),
rtask.getSN());
}
}
}
// add new task
renderTaskQueue.offer(task);
executor.execute(task);
}
}
public RendererCancelPolicy getRendererCancelPolicy() {
return cancelPolicy;
}
/**
* In PARALLEL mode, all scheduled renderer exist in the queue. In SURPASS mode, there is only 1 renderer in the
* queue is scheduled
*/
public void setRendererCancelPolicy(RendererCancelPolicy policy) {
cancelPolicy = policy;
}
}
Ads help maintain this website.