Add 'parallelrendercnt' - multi-core fullrender support (experimental)

This commit is contained in:
Mike Primm 2011-08-18 12:14:05 +08:00 committed by mikeprimm
parent ae9d1fde90
commit 83a9ff80d0
2 changed files with 136 additions and 62 deletions

View File

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledFuture;
@ -42,6 +43,7 @@ public class MapManager {
private DynmapPlugin plug_in; private DynmapPlugin plug_in;
private long timeslice_int = 0; /* In milliseconds */ private long timeslice_int = 0; /* In milliseconds */
private int max_chunk_loads_per_tick = DEFAULT_CHUNKS_PER_TICK; private int max_chunk_loads_per_tick = DEFAULT_CHUNKS_PER_TICK;
private int parallelrendercnt = 0;
private int zoomout_period = DEFAULT_ZOOMOUT_PERIOD; /* Zoom-out tile processing period, in seconds */ private int zoomout_period = DEFAULT_ZOOMOUT_PERIOD; /* Zoom-out tile processing period, in seconds */
/* Which fullrenders are active */ /* Which fullrenders are active */
@ -96,7 +98,7 @@ public class MapManager {
private class DynmapScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { private class DynmapScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
DynmapScheduledThreadPoolExecutor() { DynmapScheduledThreadPoolExecutor() {
super(POOL_SIZE); super(POOL_SIZE + parallelrendercnt);
this.setThreadFactory(new OurThreadFactory()); this.setThreadFactory(new OurThreadFactory());
/* Set shutdown policy to stop everything */ /* Set shutdown policy to stop everything */
setContinueExistingPeriodicTasksAfterShutdownPolicy(false); setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
@ -156,7 +158,6 @@ public class MapManager {
TileFlags rendered = null; TileFlags rendered = null;
LinkedList<MapTile> renderQueue = null; LinkedList<MapTile> renderQueue = null;
MapTile tile0 = null; MapTile tile0 = null;
MapTile tile = null;
int rendercnt = 0; int rendercnt = 0;
CommandSender sender; CommandSender sender;
long timeaccum; long timeaccum;
@ -207,7 +208,7 @@ public class MapManager {
} }
public String toString() { public String toString() {
return "world=" + world.world.getName() + ", map=" + map + " tile=" + tile; return "world=" + world.world.getName() + ", map=" + map;
} }
public void cleanup() { public void cleanup() {
@ -219,6 +220,8 @@ public class MapManager {
} }
public void run() { public void run() {
long tstart = System.currentTimeMillis(); long tstart = System.currentTimeMillis();
MapTile tile = null;
List<MapTile> tileset = null;
if(cancelled) { if(cancelled) {
cleanup(); cleanup();
@ -292,12 +295,80 @@ public class MapManager {
} }
} }
} }
if(parallelrendercnt > 1) { /* Doing parallel renders? */
tileset = new ArrayList<MapTile>();
for(int i = 0; i < parallelrendercnt; i++) {
tile = renderQueue.pollFirst(); tile = renderQueue.pollFirst();
if(tile != null)
tileset.add(tile);
}
}
else {
tile = renderQueue.pollFirst();
}
} }
else { /* Else, single tile render */ else { /* Else, single tile render */
tile = tile0; tile = tile0;
} }
World w = world.world; World w = world.world;
boolean notdone = true;
if(tileset != null) {
long save_timeaccum = timeaccum;
List<Future<Boolean>> rslt = new ArrayList<Future<Boolean>>();
final int cnt = tileset.size();
for(int i = 1; i < cnt; i++) { /* Do all but first on other threads */
final MapTile mt = tileset.get(i);
if((mapman != null) && (mapman.render_pool != null)) {
final long ts = tstart;
Future<Boolean> future = mapman.render_pool.submit(new Callable<Boolean>() {
public Boolean call() {
return processTile(mt, mt.world.world, ts, cnt);
}
});
rslt.add(future);
}
}
/* Now, do our render (first one) */
notdone = processTile(tileset.get(0), w, tstart, cnt);
/* Now, join with others */
for(int i = 0; i < rslt.size(); i++) {
try {
notdone = notdone && rslt.get(i).get();
} catch (ExecutionException xx) {
Log.severe(xx);
notdone = false;
} catch (InterruptedException ix) {
notdone = false;
}
}
timeaccum = save_timeaccum + System.currentTimeMillis() - tstart;
}
else {
notdone = processTile(tile, w, tstart, 1);
}
if(notdone) {
if(tile0 == null) { /* fullrender */
long tend = System.currentTimeMillis();
if(timeslice_int > (tend-tstart)) { /* We were fast enough */
scheduleDelayedJob(this, timeslice_int - (tend-tstart));
}
else { /* Schedule to run ASAP */
scheduleDelayedJob(this, 0);
}
}
else {
cleanup();
}
}
else {
cleanup();
}
}
private boolean processTile(MapTile tile, World w, long tstart, int parallelcnt) {
/* Get list of chunks required for tile */ /* Get list of chunks required for tile */
List<DynmapChunk> requiredChunks = tile.getRequiredChunks(); List<DynmapChunk> requiredChunks = tile.getRequiredChunks();
/* If we are doing radius limit render, see if any are inside limits */ /* If we are doing radius limit render, see if any are inside limits */
@ -316,8 +387,7 @@ public class MapManager {
tile.isHightestBlockYDataNeeded(), tile.isBiomeDataNeeded(), tile.isHightestBlockYDataNeeded(), tile.isBiomeDataNeeded(),
tile.isRawBiomeDataNeeded()); tile.isRawBiomeDataNeeded());
if(cache == null) { if(cache == null) {
cleanup(); return false; /* Cancelled/aborted */
return; /* Cancelled/aborted */
} }
if(tile0 != null) { /* Single tile? */ if(tile0 != null) { /* Single tile? */
if(cache.isEmpty() == false) if(cache.isEmpty() == false)
@ -327,7 +397,8 @@ public class MapManager {
/* Switch to not checking if rendered tile is blank - breaks us on skylands, where tiles can be nominally blank - just work off chunk cache empty */ /* Switch to not checking if rendered tile is blank - breaks us on skylands, where tiles can be nominally blank - just work off chunk cache empty */
if (cache.isEmpty() == false) { if (cache.isEmpty() == false) {
tile.render(cache, mapname); tile.render(cache, mapname);
found.setFlag(tile.tileOrdinalX(),tile.tileOrdinalY(),false); synchronized(lock) {
// found.setFlag(tile.tileOrdinalX(),tile.tileOrdinalY(),false);
rendered.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), true); rendered.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), true);
for (MapTile adjTile : map.getAdjecentTiles(tile)) { for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!found.getFlag(adjTile.tileOrdinalX(),adjTile.tileOrdinalY()) && if (!found.getFlag(adjTile.tileOrdinalX(),adjTile.tileOrdinalY()) &&
@ -337,7 +408,9 @@ public class MapManager {
} }
} }
} }
found.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), false); }
synchronized(lock) {
// found.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), false);
if(!cache.isEmpty()) { if(!cache.isEmpty()) {
rendercnt++; rendercnt++;
timeaccum += System.currentTimeMillis() - tstart; timeaccum += System.currentTimeMillis() - tstart;
@ -352,20 +425,11 @@ public class MapManager {
} }
} }
} }
}
/* And unload what we loaded */ /* And unload what we loaded */
cache.unloadChunks(); cache.unloadChunks();
if(tile0 == null) { /* fullrender */
long tend = System.currentTimeMillis(); return true;
if(timeslice_int > (tend-tstart)) { /* We were fast enough */
scheduleDelayedJob(this, timeslice_int - (tend-tstart));
}
else { /* Schedule to run ASAP */
scheduleDelayedJob(this, 0);
}
}
else {
cleanup();
}
} }
public void cancelRender() { public void cancelRender() {
@ -422,6 +486,7 @@ public class MapManager {
hdmapman.loadHDPerspectives(plugin); hdmapman.loadHDPerspectives(plugin);
hdmapman.loadHDLightings(plugin); hdmapman.loadHDLightings(plugin);
sscache = new SnapshotCache(configuration.getInteger("snapshotcachesize", 500)); sscache = new SnapshotCache(configuration.getInteger("snapshotcachesize", 500));
parallelrendercnt = configuration.getInteger("parallelrendercnt", 0);
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() { this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
@Override @Override
@ -764,34 +829,38 @@ public class MapManager {
return c; return c;
} }
synchronized(loadlock) {
final MapChunkCache cc = c; final MapChunkCache cc = c;
while(!cc.isDoneLoading()) {
synchronized(loadlock) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
if(cur_tick != (now/50)) { /* New tick? */ if(cur_tick != (now/50)) { /* New tick? */
chunks_in_cur_tick = max_chunk_loads_per_tick; chunks_in_cur_tick = max_chunk_loads_per_tick;
cur_tick = now/50; cur_tick = now/50;
} }
}
while(!cc.isDoneLoading()) { Future<Boolean> f = scheduler.callSyncMethod(plug_in, new Callable<Boolean>() {
final int cntin = chunks_in_cur_tick; public Boolean call() throws Exception {
Future<Integer> f = scheduler.callSyncMethod(plug_in, new Callable<Integer>() { boolean exhausted;
public Integer call() throws Exception { synchronized(loadlock) {
return Integer.valueOf(cntin - cc.loadChunks(cntin)); if(chunks_in_cur_tick > 0)
chunks_in_cur_tick -= cc.loadChunks(chunks_in_cur_tick);
exhausted = (chunks_in_cur_tick == 0);
}
return exhausted;
} }
}); });
boolean delay;
try { try {
chunks_in_cur_tick = f.get(); delay = f.get();
} catch (Exception ix) { } catch (Exception ix) {
Log.severe(ix); Log.severe(ix);
return null; return null;
} }
if(chunks_in_cur_tick == 0) { if(delay)
chunks_in_cur_tick = max_chunk_loads_per_tick;
try { Thread.sleep(50); } catch (InterruptedException ix) {} try { Thread.sleep(50); } catch (InterruptedException ix) {}
} }
}
}
return c; return c;
} }
/** /**

View File

@ -158,6 +158,11 @@ timesliceinterval: 0.0
# Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load # Maximum chunk loads per server tick (1/20th of a second) - reducing this below 90 will impact render performance, but also will reduce server thread load
maxchunkspertick: 200 maxchunkspertick: 200
# EXPERIMENTAL - parallel fullrender: if defined, number of concurrent threads used for fullrender or radiusrender
# Note: setting this will result in much more intensive CPU use, some additional memory use. Caution should be used when
# setting this to equal or exceed the number of physical cores on the system.
#parallelrendercnt: 4
# Interval the browser should poll for updates. # Interval the browser should poll for updates.
updaterate: 2000 updaterate: 2000