mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-07 19:20:33 +01:00
Add support for throttling chunk load rate, spreading over ticks
This commit is contained in:
parent
8b35f4d3b7
commit
2bc9b410a6
@ -100,6 +100,9 @@ disable-webserver: false
|
||||
# Period between tile renders for fullrender, in seconds (non-zero to pace fullrenders, lessen CPU load)
|
||||
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
|
||||
maxchunkspertick: 200
|
||||
|
||||
# Interval the browser should poll for updates.
|
||||
updaterate: 2000
|
||||
|
||||
|
@ -9,10 +9,10 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.Future;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
@ -25,13 +25,18 @@ import org.dynmap.utils.NewMapChunkCache;
|
||||
public class MapManager {
|
||||
public AsynchronousQueue<MapTile> tileQueue;
|
||||
|
||||
private static final int DEFAULT_CHUNKS_PER_TICK = 200;
|
||||
|
||||
public List<DynmapWorld> worlds = new ArrayList<DynmapWorld>();
|
||||
public Map<String, DynmapWorld> worldsLookup = new HashMap<String, DynmapWorld>();
|
||||
private BukkitScheduler scheduler;
|
||||
private DynmapPlugin plug_in;
|
||||
private long timeslice_int = 0; /* In milliseconds */
|
||||
private int max_chunk_loads_per_tick = DEFAULT_CHUNKS_PER_TICK;
|
||||
/* Which fullrenders are active */
|
||||
private HashMap<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>();
|
||||
/* List of MapChunkCache requests to be processed */
|
||||
private ConcurrentLinkedQueue<MapChunkCache> chunkloads = new ConcurrentLinkedQueue<MapChunkCache>();
|
||||
/* Tile hash manager */
|
||||
public TileHashManager hashman;
|
||||
/* lock for our data structures */
|
||||
@ -60,10 +65,22 @@ public class MapManager {
|
||||
public Collection<DynmapWorld> getWorlds() {
|
||||
return worlds;
|
||||
}
|
||||
|
||||
private static class OurThreadFactory implements ThreadFactory {
|
||||
@Override
|
||||
public Thread newThread(Runnable r) {
|
||||
Thread t = new Thread(r);
|
||||
t.setDaemon(true);
|
||||
t.setPriority(Thread.MIN_PRIORITY);
|
||||
t.setName("Dynmap Render Thread");
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
||||
private class DynmapScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor {
|
||||
DynmapScheduledThreadPoolExecutor() {
|
||||
super(POOL_SIZE);
|
||||
this.setThreadFactory(new OurThreadFactory());
|
||||
}
|
||||
|
||||
protected void afterExecute(Runnable r, Throwable x) {
|
||||
@ -163,7 +180,7 @@ public class MapManager {
|
||||
}
|
||||
World w = world.world;
|
||||
/* Fetch chunk cache from server thread */
|
||||
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
|
||||
List<DynmapChunk> requiredChunks = tile.getMap().getRequiredChunks(tile);
|
||||
MapChunkCache cache = createMapChunkCache(world, requiredChunks);
|
||||
if(cache == null) {
|
||||
cleanup();
|
||||
@ -222,6 +239,25 @@ public class MapManager {
|
||||
}
|
||||
}
|
||||
|
||||
private class ProcessChunkLoads implements Runnable {
|
||||
public void run() {
|
||||
int cnt = max_chunk_loads_per_tick;
|
||||
|
||||
while(cnt > 0) {
|
||||
MapChunkCache c = chunkloads.peek();
|
||||
if(c == null)
|
||||
return;
|
||||
cnt = cnt - c.loadChunks(cnt);
|
||||
if(c.isDoneLoading()) {
|
||||
chunkloads.poll();
|
||||
synchronized(c) {
|
||||
c.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MapManager(DynmapPlugin plugin, ConfigurationNode configuration) {
|
||||
plug_in = plugin;
|
||||
mapman = this;
|
||||
@ -235,7 +271,8 @@ public class MapManager {
|
||||
|
||||
/* On dedicated thread, so default to no delays */
|
||||
timeslice_int = (long)(configuration.getDouble("timesliceinterval", 0.0) * 1000);
|
||||
|
||||
max_chunk_loads_per_tick = configuration.getInteger("maxchunkspertick", DEFAULT_CHUNKS_PER_TICK);
|
||||
if(max_chunk_loads_per_tick < 5) max_chunk_loads_per_tick = 5;
|
||||
scheduler = plugin.getServer().getScheduler();
|
||||
|
||||
hashman = new TileHashManager(DynmapPlugin.tilesDirectory, configuration.getBoolean("enabletilehash", true));
|
||||
@ -247,7 +284,7 @@ public class MapManager {
|
||||
}
|
||||
|
||||
scheduler.scheduleSyncRepeatingTask(plugin, new CheckWorldTimes(), 5*20, 5*20); /* Check very 5 seconds */
|
||||
|
||||
scheduler.scheduleSyncRepeatingTask(plugin, new ProcessChunkLoads(), 1, 1); /* Chunk loader task */
|
||||
}
|
||||
|
||||
void renderFullWorld(Location l, CommandSender sender) {
|
||||
@ -439,35 +476,32 @@ public class MapManager {
|
||||
/**
|
||||
* Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread
|
||||
*/
|
||||
public MapChunkCache createMapChunkCache(final DynmapWorld w, final DynmapChunk[] chunks) {
|
||||
Callable<MapChunkCache> job = new Callable<MapChunkCache>() {
|
||||
public MapChunkCache call() {
|
||||
MapChunkCache c = null;
|
||||
try {
|
||||
if(!use_legacy)
|
||||
c = new NewMapChunkCache();
|
||||
} catch (NoClassDefFoundError ncdfe) {
|
||||
use_legacy = true;
|
||||
}
|
||||
if(c == null)
|
||||
c = new LegacyMapChunkCache();
|
||||
if(w.visibility_limits != null) {
|
||||
for(MapChunkCache.VisibilityLimit limit: w.visibility_limits) {
|
||||
c.setVisibleRange(limit);
|
||||
}
|
||||
c.setHiddenFillStyle(w.hiddenchunkstyle);
|
||||
}
|
||||
c.loadChunks(w.world, chunks);
|
||||
return c;
|
||||
}
|
||||
};
|
||||
Future<MapChunkCache> rslt = scheduler.callSyncMethod(plug_in, job);
|
||||
public MapChunkCache createMapChunkCache(final DynmapWorld w, final List<DynmapChunk> chunks) {
|
||||
MapChunkCache c = null;
|
||||
try {
|
||||
return rslt.get();
|
||||
} catch (Exception x) {
|
||||
Log.info("createMapChunk - " + x);
|
||||
return null;
|
||||
if(!use_legacy)
|
||||
c = new NewMapChunkCache();
|
||||
} catch (NoClassDefFoundError ncdfe) {
|
||||
use_legacy = true;
|
||||
}
|
||||
if(c == null)
|
||||
c = new LegacyMapChunkCache();
|
||||
if(w.visibility_limits != null) {
|
||||
for(MapChunkCache.VisibilityLimit limit: w.visibility_limits) {
|
||||
c.setVisibleRange(limit);
|
||||
}
|
||||
c.setHiddenFillStyle(w.hiddenchunkstyle);
|
||||
}
|
||||
c.setChunks(w.world, chunks);
|
||||
synchronized(c) {
|
||||
chunkloads.add(c);
|
||||
try {
|
||||
c.wait();
|
||||
} catch (InterruptedException ix) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
/**
|
||||
* Update map tile statistics
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.dynmap.utils.MapChunkCache;
|
||||
@ -13,7 +14,7 @@ public abstract class MapType {
|
||||
|
||||
public abstract MapTile[] getAdjecentTiles(MapTile tile);
|
||||
|
||||
public abstract DynmapChunk[] getRequiredChunks(MapTile tile);
|
||||
public abstract List<DynmapChunk> getRequiredChunks(MapTile tile);
|
||||
|
||||
public abstract boolean render(MapChunkCache cache, MapTile tile, File outputFile);
|
||||
|
||||
|
@ -5,6 +5,8 @@ import static org.dynmap.JSONUtils.a;
|
||||
import static org.dynmap.JSONUtils.s;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
@ -101,18 +103,16 @@ public class FlatMap extends MapType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DynmapChunk[] getRequiredChunks(MapTile tile) {
|
||||
public List<DynmapChunk> getRequiredChunks(MapTile tile) {
|
||||
FlatMapTile t = (FlatMapTile) tile;
|
||||
int chunksPerTile = t.size / 16;
|
||||
int sx = t.x * chunksPerTile;
|
||||
int sz = t.y * chunksPerTile;
|
||||
|
||||
DynmapChunk[] result = new DynmapChunk[chunksPerTile * chunksPerTile];
|
||||
int index = 0;
|
||||
ArrayList<DynmapChunk> result = new ArrayList<DynmapChunk>(chunksPerTile * chunksPerTile);
|
||||
for (int x = 0; x < chunksPerTile; x++)
|
||||
for (int z = 0; z < chunksPerTile; z++) {
|
||||
result[index] = new DynmapChunk(sx + x, sz + z);
|
||||
index++;
|
||||
result.add(new DynmapChunk(sx + x, sz + z));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ public class KzedMap extends MapType {
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public DynmapChunk[] getRequiredChunks(MapTile tile) {
|
||||
public List<DynmapChunk> getRequiredChunks(MapTile tile) {
|
||||
if (tile instanceof KzedMapTile) {
|
||||
KzedMapTile t = (KzedMapTile) tile;
|
||||
|
||||
@ -216,12 +216,9 @@ public class KzedMap extends MapType {
|
||||
chunks.add(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
DynmapChunk[] result = new DynmapChunk[chunks.size()];
|
||||
chunks.toArray(result);
|
||||
return result;
|
||||
return chunks;
|
||||
} else {
|
||||
return new DynmapChunk[0];
|
||||
return new ArrayList<DynmapChunk>();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package org.dynmap.utils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.ListIterator;
|
||||
import java.lang.reflect.Field;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.Chunk;
|
||||
@ -20,7 +21,11 @@ public class LegacyMapChunkCache implements MapChunkCache {
|
||||
private static Method poppreservedchunk = null;
|
||||
private static Field heightmap = null;
|
||||
private static boolean initialized = false;
|
||||
|
||||
|
||||
private World w;
|
||||
private List<DynmapChunk> chunks;
|
||||
private ListIterator<DynmapChunk> iterator;
|
||||
|
||||
private int x_min, x_max, z_min, z_max;
|
||||
private int x_dim;
|
||||
private HiddenChunkStyle hidestyle = HiddenChunkStyle.FILL_AIR;
|
||||
@ -177,17 +182,15 @@ public class LegacyMapChunkCache implements MapChunkCache {
|
||||
public LegacyMapChunkCache() {
|
||||
}
|
||||
/**
|
||||
* Create chunk cache container
|
||||
* @param w - world
|
||||
* @param x_min - minimum chunk x coordinate
|
||||
* @param z_min - minimum chunk z coordinate
|
||||
* @param x_max - maximum chunk x coordinate
|
||||
* @param z_max - maximum chunk z coordinate
|
||||
* Set chunks to load, and world to load from
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public void loadChunks(World w, DynmapChunk[] chunks) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public void setChunks(World w, List<DynmapChunk> chunks) {
|
||||
this.w = w;
|
||||
this.chunks = chunks;
|
||||
|
||||
/* Compute range */
|
||||
if(chunks.length == 0) {
|
||||
if(chunks.size() == 0) {
|
||||
this.x_min = 0;
|
||||
this.x_max = 0;
|
||||
this.z_min = 0;
|
||||
@ -195,17 +198,17 @@ public class LegacyMapChunkCache implements MapChunkCache {
|
||||
x_dim = 1;
|
||||
}
|
||||
else {
|
||||
x_min = x_max = chunks[0].x;
|
||||
z_min = z_max = chunks[0].z;
|
||||
for(int i = 1; i < chunks.length; i++) {
|
||||
if(chunks[i].x > x_max)
|
||||
x_max = chunks[i].x;
|
||||
if(chunks[i].x < x_min)
|
||||
x_min = chunks[i].x;
|
||||
if(chunks[i].z > z_max)
|
||||
z_max = chunks[i].z;
|
||||
if(chunks[i].z < z_min)
|
||||
z_min = chunks[i].z;
|
||||
x_min = x_max = chunks.get(0).x;
|
||||
z_min = z_max = chunks.get(0).z;
|
||||
for(DynmapChunk c : chunks) {
|
||||
if(c.x > x_max)
|
||||
x_max = c.x;
|
||||
if(c.x < x_min)
|
||||
x_min = c.x;
|
||||
if(c.z > z_max)
|
||||
z_max = c.z;
|
||||
if(c.z < z_min)
|
||||
z_min = c.z;
|
||||
}
|
||||
x_dim = x_max - x_min + 1;
|
||||
}
|
||||
@ -238,9 +241,17 @@ public class LegacyMapChunkCache implements MapChunkCache {
|
||||
}
|
||||
}
|
||||
snaparray = new LegacyChunkSnapshot[x_dim * (z_max-z_min+1)];
|
||||
if(gethandle != null) {
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : chunks) {
|
||||
}
|
||||
|
||||
public int loadChunks(int max_to_load) {
|
||||
int cnt = 0;
|
||||
if(iterator == null)
|
||||
iterator = chunks.listIterator();
|
||||
|
||||
// Load the required chunks.
|
||||
while((cnt < max_to_load) && iterator.hasNext()) {
|
||||
DynmapChunk chunk = iterator.next();
|
||||
if(gethandle != null) {
|
||||
boolean vis = true;
|
||||
if(visible_limits != null) {
|
||||
vis = false;
|
||||
@ -302,12 +313,25 @@ public class LegacyMapChunkCache implements MapChunkCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
/* Fill missing chunks with empty dummy chunk */
|
||||
for(int i = 0; i < snaparray.length; i++) {
|
||||
if(snaparray[i] == null)
|
||||
snaparray[i] = EMPTY;
|
||||
/* If done, finish table */
|
||||
if(iterator.hasNext() == false) {
|
||||
/* Fill missing chunks with empty dummy chunk */
|
||||
for(int i = 0; i < snaparray.length; i++) {
|
||||
if(snaparray[i] == null)
|
||||
snaparray[i] = EMPTY;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
/**
|
||||
* Test if done loading
|
||||
*/
|
||||
public boolean isDoneLoading() {
|
||||
if(iterator != null)
|
||||
return !iterator.hasNext();
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Unload chunks
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.dynmap.utils;
|
||||
import org.bukkit.World;
|
||||
import java.util.List;
|
||||
import org.dynmap.DynmapChunk;
|
||||
|
||||
public interface MapChunkCache {
|
||||
@ -12,11 +13,19 @@ public interface MapChunkCache {
|
||||
public int x0, x1, z0, z1;
|
||||
}
|
||||
/**
|
||||
* Load chunks into cache
|
||||
* @param w - world
|
||||
* @param chunks - chunks to be loaded
|
||||
* Set chunks to load, and world to load from
|
||||
*/
|
||||
void loadChunks(World w, DynmapChunk[] chunks);
|
||||
void setChunks(World w, List<DynmapChunk> chunks);
|
||||
/**
|
||||
* Load chunks into cache
|
||||
* @param maxToLoad - maximum number to load at once
|
||||
* @return number loaded
|
||||
*/
|
||||
int loadChunks(int maxToLoad);
|
||||
/**
|
||||
* Test if done loading
|
||||
*/
|
||||
boolean isDoneLoading();
|
||||
/**
|
||||
* Unload chunks
|
||||
*/
|
||||
|
@ -3,6 +3,7 @@ package org.dynmap.utils;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.Chunk;
|
||||
@ -18,6 +19,9 @@ import org.dynmap.Log;
|
||||
public class NewMapChunkCache implements MapChunkCache {
|
||||
private static Method poppreservedchunk = null;
|
||||
|
||||
private World w;
|
||||
private List<DynmapChunk> chunks;
|
||||
private ListIterator<DynmapChunk> iterator;
|
||||
private int x_min, x_max, z_min, z_max;
|
||||
private int x_dim;
|
||||
private HiddenChunkStyle hidestyle = HiddenChunkStyle.FILL_AIR;
|
||||
@ -189,16 +193,10 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
*/
|
||||
public NewMapChunkCache() {
|
||||
}
|
||||
/**
|
||||
* Create chunk cache container
|
||||
* @param w - world
|
||||
* @param x_min - minimum chunk x coordinate
|
||||
* @param z_min - minimum chunk z coordinate
|
||||
* @param x_max - maximum chunk x coordinate
|
||||
* @param z_max - maximum chunk z coordinate
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public void loadChunks(World w, DynmapChunk[] chunks) {
|
||||
public void setChunks(World w, List<DynmapChunk> chunks) {
|
||||
this.w = w;
|
||||
this.chunks = chunks;
|
||||
if(poppreservedchunk == null) {
|
||||
/* Get CraftWorld.popPreservedChunk(x,z) - reduces memory bloat from map traversals (optional) */
|
||||
try {
|
||||
@ -209,7 +207,7 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
}
|
||||
}
|
||||
/* Compute range */
|
||||
if(chunks.length == 0) {
|
||||
if(chunks.size() == 0) {
|
||||
this.x_min = 0;
|
||||
this.x_max = 0;
|
||||
this.z_min = 0;
|
||||
@ -217,24 +215,32 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
x_dim = 1;
|
||||
}
|
||||
else {
|
||||
x_min = x_max = chunks[0].x;
|
||||
z_min = z_max = chunks[0].z;
|
||||
for(int i = 1; i < chunks.length; i++) {
|
||||
if(chunks[i].x > x_max)
|
||||
x_max = chunks[i].x;
|
||||
if(chunks[i].x < x_min)
|
||||
x_min = chunks[i].x;
|
||||
if(chunks[i].z > z_max)
|
||||
z_max = chunks[i].z;
|
||||
if(chunks[i].z < z_min)
|
||||
z_min = chunks[i].z;
|
||||
x_min = x_max = chunks.get(0).x;
|
||||
z_min = z_max = chunks.get(0).z;
|
||||
for(DynmapChunk c : chunks) {
|
||||
if(c.x > x_max)
|
||||
x_max = c.x;
|
||||
if(c.x < x_min)
|
||||
x_min = c.x;
|
||||
if(c.z > z_max)
|
||||
z_max = c.z;
|
||||
if(c.z < z_min)
|
||||
z_min = c.z;
|
||||
}
|
||||
x_dim = x_max - x_min + 1;
|
||||
}
|
||||
|
||||
snaparray = new ChunkSnapshot[x_dim * (z_max-z_min+1)];
|
||||
}
|
||||
|
||||
public int loadChunks(int max_to_load) {
|
||||
int cnt = 0;
|
||||
if(iterator == null)
|
||||
iterator = chunks.listIterator();
|
||||
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : chunks) {
|
||||
while((cnt < max_to_load) && iterator.hasNext()) {
|
||||
DynmapChunk chunk = iterator.next();
|
||||
boolean vis = true;
|
||||
if(visible_limits != null) {
|
||||
vis = false;
|
||||
@ -286,12 +292,24 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
Log.severe("Cannot pop preserved chunk - " + x.toString());
|
||||
}
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
/* Fill missing chunks with empty dummy chunk */
|
||||
for(int i = 0; i < snaparray.length; i++) {
|
||||
if(snaparray[i] == null)
|
||||
snaparray[i] = EMPTY;
|
||||
if(iterator.hasNext() == false) { /* If we're done */
|
||||
/* Fill missing chunks with empty dummy chunk */
|
||||
for(int i = 0; i < snaparray.length; i++) {
|
||||
if(snaparray[i] == null)
|
||||
snaparray[i] = EMPTY;
|
||||
}
|
||||
}
|
||||
return cnt;
|
||||
}
|
||||
/**
|
||||
* Test if done loading
|
||||
*/
|
||||
public boolean isDoneLoading() {
|
||||
if(iterator != null)
|
||||
return !iterator.hasNext();
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Unload chunks
|
||||
|
Loading…
Reference in New Issue
Block a user