diff --git a/configuration.txt b/configuration.txt index ead88f0b..80fb53f8 100644 --- a/configuration.txt +++ b/configuration.txt @@ -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 diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 589cbcc7..36054875 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -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 tileQueue; + private static final int DEFAULT_CHUNKS_PER_TICK = 200; + public List worlds = new ArrayList(); public Map worldsLookup = new HashMap(); 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 active_renders = new HashMap(); + /* List of MapChunkCache requests to be processed */ + private ConcurrentLinkedQueue chunkloads = new ConcurrentLinkedQueue(); /* Tile hash manager */ public TileHashManager hashman; /* lock for our data structures */ @@ -60,10 +65,22 @@ public class MapManager { public Collection 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 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 job = new Callable() { - 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 rslt = scheduler.callSyncMethod(plug_in, job); + public MapChunkCache createMapChunkCache(final DynmapWorld w, final List 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 diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 72e62ece..3aa68b19 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -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 getRequiredChunks(MapTile tile); public abstract boolean render(MapChunkCache cache, MapTile tile, File outputFile); diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 68e6497b..c003415f 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -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 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 result = new ArrayList(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; } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 299dc81b..c7183092 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -164,7 +164,7 @@ public class KzedMap extends MapType { return false; } @Override - public DynmapChunk[] getRequiredChunks(MapTile tile) { + public List 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(); } } diff --git a/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java index 6a2b467f..054ce8dd 100644 --- a/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java +++ b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java @@ -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 chunks; + private ListIterator 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 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 diff --git a/src/main/java/org/dynmap/utils/MapChunkCache.java b/src/main/java/org/dynmap/utils/MapChunkCache.java index 897af9d2..226cfdb5 100644 --- a/src/main/java/org/dynmap/utils/MapChunkCache.java +++ b/src/main/java/org/dynmap/utils/MapChunkCache.java @@ -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 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 */ diff --git a/src/main/java/org/dynmap/utils/NewMapChunkCache.java b/src/main/java/org/dynmap/utils/NewMapChunkCache.java index 1cd63cb1..1c666696 100644 --- a/src/main/java/org/dynmap/utils/NewMapChunkCache.java +++ b/src/main/java/org/dynmap/utils/NewMapChunkCache.java @@ -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 chunks; + private ListIterator 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 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