Add option to run incremental renders on sync thread, using same chunk

logic as timesliced full render, in order to handle cases where
assumption that all chunks needed for incremental render are already
resident (which can be false if player telepots, move quickly,
multiple players are active, etc).
This commit is contained in:
Mike Primm 2011-05-10 11:09:25 +02:00 committed by FrozenCow
parent 88db6445f9
commit 0a1de1b18e
3 changed files with 117 additions and 84 deletions

View File

@ -6,6 +6,9 @@ display-whitelist: false
# How often a tile gets rendered (in seconds).
renderinterval: 1
# Do render on main thread - may generate more server load, but safer and fixes broken tiles
renderonsync: true
render-triggers:
# - playermove
# - playerjoin

View File

@ -28,4 +28,4 @@ public class DynmapPlayerChatListener extends PlayerListener {
plugin.mapManager.pushUpdate(new Client.PlayerQuitMessage(event.getPlayer().getName()));
}
}
}

View File

@ -33,17 +33,127 @@ public class MapManager {
private DynmapPlugin plug_in;
private boolean do_timesliced_render = false;
private double timeslice_interval = 0.0;
private boolean do_sync_render = false; /* Do incremental renders on sync thread too */
/* Which timesliced renders are active */
private HashMap<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>();
/* lock for our data structures */
public static final Object lock = new Object();
private class FullWorldRenderState implements Runnable {
DynmapWorld world; /* Which world are we rendering */
Location loc; /* Start location */
int map_index = -1; /* Which map are we on */
MapType map;
HashSet<MapTile> found = null;
HashSet<MapTile> rendered = null;
LinkedList<MapTile> renderQueue = null;
MapTile tile0 = null;
/* Full world, all maps render */
FullWorldRenderState(DynmapWorld dworld, Location l) {
world = dworld;
loc = l;
found = new HashSet<MapTile>();
rendered = new HashSet<MapTile>();
renderQueue = new LinkedList<MapTile>();
}
/* Single tile render - used for incremental renders */
FullWorldRenderState(MapTile t) {
world = worlds.get(t.getWorld().getName());
tile0 = t;
}
public void run() {
MapTile tile;
if(tile0 == null) { /* Not single tile render */
/* If render queue is empty, start next map */
if(renderQueue.isEmpty()) {
found.clear();
rendered.clear();
map_index++; /* Next map */
if(map_index >= world.maps.size()) { /* Last one done? */
log.info("Full render finished.");
active_renders.remove(world.world.getName());
return;
}
map = world.maps.get(map_index);
/* Now, prime the render queue */
for (MapTile mt : map.getTiles(loc)) {
if (!found.contains(mt)) {
found.add(mt);
renderQueue.add(mt);
}
}
}
tile = renderQueue.pollFirst();
}
else { /* Else, single tile render */
tile = tile0;
}
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
LinkedList<DynmapChunk> loadedChunks = new LinkedList<DynmapChunk>();
World w = world.world;
// Load the required chunks.
for (DynmapChunk chunk : requiredChunks) {
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
if ((!wasLoaded) && didload)
loadedChunks.add(chunk);
}
if(tile0 != null) { /* Single tile? */
render(tile); /* Just render */
}
else {
if (render(tile)) {
found.remove(tile);
rendered.add(tile);
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!found.contains(adjTile) && !rendered.contains(adjTile)) {
found.add(adjTile);
renderQueue.add(adjTile);
}
}
}
found.remove(tile);
}
/* And unload what we loaded */
while (!loadedChunks.isEmpty()) {
DynmapChunk c = loadedChunks.pollFirst();
/* It looks like bukkit "leaks" entities - they don't get removed from the world-level table
* when chunks are unloaded but not saved - removing them seems to do the trick */
Chunk cc = w.getChunkAt(c.x, c.z);
if(cc != null) {
for(Entity e: cc.getEntities())
e.remove();
}
/* Since we only remember ones we loaded, and we're synchronous, no player has
* moved, so it must be safe (also prevent chunk leak, which appears to happen
* because isChunkInUse defined "in use" as being within 256 blocks of a player,
* while the actual in-use chunk area for a player where the chunks are managed
* by the MC base server is 21x21 (or about a 160 block radius) */
w.unloadChunk(c.x, c.z, false, false);
}
if(tile0 == null) { /* fullrender */
/* Schedule the next tile to be worked */
scheduler.scheduleSyncDelayedTask(plug_in, this, (int)(timeslice_interval*20));
}
}
}
public MapManager(DynmapPlugin plugin, ConfigurationNode configuration) {
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
@Override
public void handle(MapTile t) {
render(t);
if(do_sync_render)
scheduler.scheduleSyncDelayedTask(plug_in,
new FullWorldRenderState(t), (int)(timeslice_interval*20));
else
render(t);
}
}, (int) (configuration.getDouble("renderinterval", 0.5) * 1000));
@ -64,6 +174,7 @@ public class MapManager {
}
do_timesliced_render = configuration.getBoolean("timeslicerender", true);
timeslice_interval = configuration.getDouble("timesliceinterval", 0.5);
do_sync_render = configuration.getBoolean("renderonsync", true);
scheduler = plugin.getServer().getScheduler();
plug_in = plugin;
@ -71,88 +182,7 @@ public class MapManager {
tileQueue.start();
}
private class FullWorldRenderState implements Runnable {
DynmapWorld world; /* Which world are we rendering */
Location loc; /* Start location */
int map_index = -1; /* Which map are we on */
MapType map;
HashSet<MapTile> found = new HashSet<MapTile>();
HashSet<MapTile> rendered = new HashSet<MapTile>();
LinkedList<MapTile> renderQueue = new LinkedList<MapTile>();
FullWorldRenderState(DynmapWorld dworld, Location l) {
world = dworld;
loc = l;
}
public void run() {
MapTile tile;
/* If render queue is empty, start next map */
if(renderQueue.isEmpty()) {
found.clear();
rendered.clear();
map_index++; /* Next map */
if(map_index >= world.maps.size()) { /* Last one done? */
log.info("Full render finished.");
active_renders.remove(world.world.getName());
return;
}
map = world.maps.get(map_index);
/* Now, prime the render queue */
for (MapTile mt : map.getTiles(loc)) {
if (!found.contains(mt)) {
found.add(mt);
renderQueue.add(mt);
}
}
}
tile = renderQueue.pollFirst();
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
LinkedList<DynmapChunk> loadedChunks = new LinkedList<DynmapChunk>();
World w = world.world;
// Load the required chunks.
for (DynmapChunk chunk : requiredChunks) {
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
if ((!wasLoaded) && didload)
loadedChunks.add(chunk);
}
if (render(tile)) {
found.remove(tile);
rendered.add(tile);
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!found.contains(adjTile) && !rendered.contains(adjTile)) {
found.add(adjTile);
renderQueue.add(adjTile);
}
}
}
found.remove(tile);
/* And unload what we loaded */
while (!loadedChunks.isEmpty()) {
DynmapChunk c = loadedChunks.pollFirst();
/* It looks like bukkit "leaks" entities - they don't get removed from the world-level table
* when chunks are unloaded but not saved - removing them seems to do the trick */
Chunk cc = w.getChunkAt(c.x, c.z);
if(cc != null) {
for(Entity e: cc.getEntities())
e.remove();
}
/* Since we only remember ones we loaded, and we're synchronous, no player has
* moved, so it must be safe (also prevent chunk leak, which appears to happen
* because isChunkInUse defined "in use" as being within 256 blocks of a player,
* while the actual in-use chunk area for a player where the chunks are managed
* by the MC base server is 21x21 (or about a 160 block radius) */
w.unloadChunk(c.x, c.z, false, false);
}
/* Schedule the next tile to be worked */
scheduler.scheduleSyncDelayedTask(plug_in, this, (int)(timeslice_interval*20));
}
}
void renderFullWorld(Location l) {
DynmapWorld world = worlds.get(l.getWorld().getName());