From 3365a96565c5fe4d242fa0fc2e7a258838397eae Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 20 May 2011 01:38:49 -0500 Subject: [PATCH 1/7] Prototype of chunk snapshot support - pre-Bukkit API (reflection based, with fallback to existing APIs) --- .../java/org/dynmap/CraftChunkSnapshot.java | 108 ++++++++++ src/main/java/org/dynmap/MapChunkCache.java | 198 ++++++++++++++++++ src/main/java/org/dynmap/MapManager.java | 127 ++--------- src/main/java/org/dynmap/MapType.java | 2 +- src/main/java/org/dynmap/flat/FlatMap.java | 15 +- .../org/dynmap/kzedmap/CaveTileRenderer.java | 6 +- .../dynmap/kzedmap/DefaultTileRenderer.java | 20 +- .../dynmap/kzedmap/HighlightTileRenderer.java | 8 +- src/main/java/org/dynmap/kzedmap/KzedMap.java | 7 +- .../org/dynmap/kzedmap/MapTileRenderer.java | 3 +- .../dynmap/kzedmap/ZoomedTileRenderer.java | 4 +- 11 files changed, 361 insertions(+), 137 deletions(-) create mode 100644 src/main/java/org/dynmap/CraftChunkSnapshot.java create mode 100644 src/main/java/org/dynmap/MapChunkCache.java diff --git a/src/main/java/org/dynmap/CraftChunkSnapshot.java b/src/main/java/org/dynmap/CraftChunkSnapshot.java new file mode 100644 index 00000000..de080c69 --- /dev/null +++ b/src/main/java/org/dynmap/CraftChunkSnapshot.java @@ -0,0 +1,108 @@ +package org.dynmap; + +/** + * Represents a static, thread-safe snapshot of chunk of blocks + * Purpose is to allow clean, efficient copy of a chunk data to be made, and then handed off for processing in another thread (e.g. map rendering) + */ +public class CraftChunkSnapshot { + private final int x, z; + private final byte[] buf; /* Flat buffer in uncompressed chunk file format */ + + private static final int BLOCKDATA_OFF = 32768; + private static final int BLOCKLIGHT_OFF = BLOCKDATA_OFF + 16384; + private static final int SKYLIGHT_OFF = BLOCKLIGHT_OFF + 16384; + + /** + * Constructor + */ + CraftChunkSnapshot(int x, int z, byte[] buf) { + this.x = x; + this.z = z; + this.buf = buf; + } + + /** + * Gets the X-coordinate of this chunk + * + * @return X-coordinate + */ + public int getX() { + return x; + } + + /** + * Gets the Z-coordinate of this chunk + * + * @return Z-coordinate + */ + public int getZ() { + return z; + } + + /** + * Get block type for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-255 + */ + public int getBlockTypeId(int x, int y, int z) { + return buf[x << 11 | z << 7 | y] & 255; + } + + /** + * Get block data for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockData(int x, int y, int z) { + int off = ((x << 10) | (z << 6) | (y >> 1)) + BLOCKDATA_OFF; + + return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF); + } + + /** + * Get sky light level for block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockSkyLight(int x, int y, int z) { + int off = ((x << 10) | (z << 6) | (y >> 1)) + SKYLIGHT_OFF; + + return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF); + } + + /** + * Get light level emitted by block at corresponding coordinate in the chunk + * + * @param x 0-15 + * @param y 0-127 + * @param z 0-15 + * @return 0-15 + */ + public int getBlockEmittedLight(int x, int y, int z) { + int off = ((x << 10) | (z << 6) | (y >> 1)) + BLOCKLIGHT_OFF; + + return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF); + } + + public int getHighestBlockYAt(int x, int z) { + int off = x << 11 | z << 7 | 127; + int i; + for(i = 127; (i >= 0); i--, off--) { + if(buf[off] != 0) { + if(i < 127) i++; + break; + } + + } + return i; + } +} diff --git a/src/main/java/org/dynmap/MapChunkCache.java b/src/main/java/org/dynmap/MapChunkCache.java new file mode 100644 index 00000000..bcd56715 --- /dev/null +++ b/src/main/java/org/dynmap/MapChunkCache.java @@ -0,0 +1,198 @@ +package org.dynmap; + +import java.lang.reflect.Method; +import java.util.LinkedList; +import org.bukkit.World; +import org.bukkit.Chunk; +import org.bukkit.entity.Entity; + +/** + * Container for managing chunks, as well as abstracting the different methods we may + * handle chunk data (existing chunk loading, versus upcoming chunk snapshots) + * + */ +public class MapChunkCache { + private World w; + private static Method getchunkdata = null; + private static Method gethandle = null; + private static boolean initialized = false; + + private int x_min, x_max, z_min, z_max; + private int x_dim; + + private CraftChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ + private LinkedList loadedChunks = new LinkedList(); + + /** + * 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" }) + public MapChunkCache(World w, DynmapChunk[] chunks) { + /* Compute range */ + if(chunks.length == 0) { + this.x_min = 0; + this.x_max = 0; + this.z_min = 0; + this.z_max = 0; + 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_dim = x_max - x_min + 1; + } + this.w = w; + + if(!initialized) { + try { + Class c = Class.forName("net.minecraft.server.Chunk"); + getchunkdata = c.getDeclaredMethod("a", new Class[] { byte[].class, int.class, + int.class, int.class, int.class, int.class, int.class, int.class }); + c = Class.forName("org.bukkit.craftbukkit.CraftChunk"); + gethandle = c.getDeclaredMethod("getHandle", new Class[0]); + } catch (ClassNotFoundException cnfx) { + } catch (NoSuchMethodException nsmx) { + } + initialized = true; + if(gethandle != null) + Log.info("Chunk snapshot support enabled"); + else + Log.info("Chunk snapshot support disabled"); + } + if(gethandle != null) { /* We can use caching */ + snaparray = new CraftChunkSnapshot[x_dim * (z_max-z_min+1)]; + } + if(snaparray != null) { + // Load the required chunks. + for (DynmapChunk chunk : chunks) { + boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z); + boolean didload = w.loadChunk(chunk.x, chunk.z, false); + /* If it did load, make cache of it */ + if(didload) { + Chunk c = w.getChunkAt(chunk.x, chunk.z); + try { + Object cc = gethandle.invoke(c); + byte[] buf = new byte[32768 + 16384 + 16384 + 16384]; /* Get big enough buffer for whole chunk */ + getchunkdata.invoke(cc, buf, 0, 0, 0, 16, 128, 16, 0); + snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = + new CraftChunkSnapshot(chunk.x, chunk.z, buf); + } catch (Exception x) { + } + } + if ((!wasLoaded) && didload) { + /* 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(chunk.x, chunk.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(chunk.x, chunk.z, false, false); + } + } + } + else { /* Else, load and keep them loaded for now */ + // Load the required chunks. + for (DynmapChunk chunk : chunks) { + boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z); + boolean didload = w.loadChunk(chunk.x, chunk.z, false); + if ((!wasLoaded) && didload) + loadedChunks.add(chunk); + } + } + } + /** + * Unload chunks + */ + public void unloadChunks() { + if(snaparray != null) { + for(int i = 0; i < snaparray.length; i++) { + snaparray[i] = null; + } + } + else { + 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); + } + } + } + /** + * Get block ID at coordinates + */ + public int getBlockTypeID(int x, int y, int z) { + if(snaparray != null) { + CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + if(ss == null) + return 0; + else + return ss.getBlockTypeId(x & 0xF, y, z & 0xF); + } + else { + return w.getBlockTypeIdAt(x, y, z); + } + } + /** + * Get block data at coordiates + */ + public byte getBlockData(int x, int y, int z) { + if(snaparray != null) { + CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + if(ss == null) + return 0; + else + return (byte)ss.getBlockData(x & 0xF, y, z & 0xF); + } + else { + return w.getBlockAt(x, y, z).getData(); + } + } + /* Get highest block Y + * + */ + public int getHighestBlockYAt(int x, int z) { + if(snaparray != null) { + CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + if(ss == null) { + return 0; + } + else + return ss.getHighestBlockYAt(x & 0xF, z & 0xF); + } + else { + return w.getHighestBlockYAt(x, z); + } + } +} diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 413dbbc3..0a0496d5 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -21,9 +21,7 @@ public class MapManager { public Map inactiveworlds = new HashMap(); private BukkitScheduler scheduler; 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 active_renders = new HashMap(); @@ -96,22 +94,14 @@ public class MapManager { else { /* Else, single tile render */ tile = tile0; } - DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile); - LinkedList loadedChunks = new LinkedList(); + MapChunkCache cache = new MapChunkCache(world.world, requiredChunks); 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 */ + render(cache, tile); /* Just render */ } else { - if (render(tile)) { + if (render(cache, tile)) { found.remove(tile); rendered.add(tile); for (MapTile adjTile : map.getAdjecentTiles(tile)) { @@ -129,22 +119,7 @@ public class MapManager { } } /* 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); - } + cache.unloadChunks(); if(tile0 == null) { /* fullrender */ /* Schedule the next tile to be worked */ scheduler.scheduleSyncDelayedTask(plug_in, this, (int)(timeslice_interval*20)); @@ -159,11 +134,8 @@ public class MapManager { this.tileQueue = new AsynchronousQueue(new Handler() { @Override public void handle(MapTile t) { - if(do_sync_render) - scheduler.scheduleSyncDelayedTask(plug_in, - new FullWorldRenderState(t), 1); - else - render(t); + scheduler.scheduleSyncDelayedTask(plug_in, + new FullWorldRenderState(t), 1); } }, (int) (configuration.getDouble("renderinterval", 0.5) * 1000)); @@ -175,9 +147,7 @@ public class MapManager { } }, 10); - do_timesliced_render = configuration.getBoolean("timeslicerender", true); timeslice_interval = configuration.getDouble("timesliceinterval", 0.5); - do_sync_render = configuration.getBoolean("renderonsync", true); for(ConfigurationNode worldConfiguration : configuration.getNodes("worlds")) { String worldName = worldConfiguration.getString("name"); @@ -219,78 +189,17 @@ public class MapManager { Log.severe("Could not render: world '" + l.getWorld().getName() + "' not defined in configuration."); return; } - if(do_timesliced_render) { - String wname = l.getWorld().getName(); - FullWorldRenderState rndr = active_renders.get(wname); - if(rndr != null) { - Log.info("Full world render of world '" + wname + "' already active."); - return; - } - rndr = new FullWorldRenderState(world,l); /* Make new activation record */ - active_renders.put(wname, rndr); /* Add to active table */ - /* Schedule first tile to be worked */ - scheduler.scheduleSyncDelayedTask(plug_in, rndr, (int)(timeslice_interval*20)); - Log.info("Full render starting on world '" + wname + "' (timesliced)..."); - + String wname = l.getWorld().getName(); + FullWorldRenderState rndr = active_renders.get(wname); + if(rndr != null) { + Log.info("Full world render of world '" + wname + "' already active."); return; } - World w = world.world; - - Log.info("Full render starting on world '" + w.getName() + "'..."); - for (MapType map : world.maps) { - int requiredChunkCount = 200; - HashSet found = new HashSet(); - HashSet rendered = new HashSet(); - LinkedList renderQueue = new LinkedList(); - LinkedList loadedChunks = new LinkedList(); - - for (MapTile tile : map.getTiles(l)) { - if (!found.contains(tile)) { - found.add(tile); - renderQueue.add(tile); - } - } - while (!renderQueue.isEmpty()) { - MapTile tile = renderQueue.pollFirst(); - - DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile); - - if (requiredChunks.length > requiredChunkCount) - requiredChunkCount = requiredChunks.length; - // Unload old chunks. - while (loadedChunks.size() >= requiredChunkCount - requiredChunks.length) { - DynmapChunk c = loadedChunks.pollFirst(); - w.unloadChunk(c.x, c.z, false, true); - } - - // Load the required chunks. - for (DynmapChunk chunk : requiredChunks) { - boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z); - w.loadChunk(chunk.x, chunk.z, false); - if (!wasLoaded) - 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); - } - - // Unload remaining chunks to clean-up. - while (!loadedChunks.isEmpty()) { - DynmapChunk c = loadedChunks.pollFirst(); - w.unloadChunk(c.x, c.z, false, true); - } - } - Log.info("Full render finished."); + rndr = new FullWorldRenderState(world,l); /* Make new activation record */ + active_renders.put(wname, rndr); /* Add to active table */ + /* Schedule first tile to be worked */ + scheduler.scheduleSyncDelayedTask(plug_in, rndr, (int)(timeslice_interval*20)); + Log.info("Full render starting on world '" + wname + "' (timesliced)..."); } public void activateWorld(World w) { @@ -337,8 +246,8 @@ public class MapManager { writeQueue.stop(); } - public boolean render(MapTile tile) { - boolean result = tile.getMap().render(tile, getTileFile(tile)); + public boolean render(MapChunkCache cache, MapTile tile) { + boolean result = tile.getMap().render(cache, tile, getTileFile(tile)); //Do update after async file write return result; @@ -387,6 +296,6 @@ public class MapManager { } public boolean doSyncRender() { - return do_sync_render; + return true; } } diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 006d1846..5aa5a35e 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -13,5 +13,5 @@ public abstract class MapType { public abstract DynmapChunk[] getRequiredChunks(MapTile tile); - public abstract boolean render(MapTile tile, File outputFile); + 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 59f30ae2..49a27c3a 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -19,6 +19,7 @@ import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.debug.Debug; +import org.dynmap.MapChunkCache; public class FlatMap extends MapType { private String prefix; @@ -73,7 +74,7 @@ public class FlatMap extends MapType { } @Override - public boolean render(MapTile tile, File outputFile) { + public boolean render(MapChunkCache cache, MapTile tile, File outputFile) { FlatMapTile t = (FlatMapTile) tile; World w = t.getWorld(); boolean isnether = (w.getEnvironment() == Environment.NETHER) && (maximumHeight == 127); @@ -93,16 +94,16 @@ public class FlatMap extends MapType { if(isnether) { /* Scan until we hit air */ my = 127; - while((blockType = w.getBlockTypeIdAt(mx, my, mz)) != 0) { + while((blockType = cache.getBlockTypeID(mx, my, mz)) != 0) { my--; if(my < 0) { /* Solid - use top */ my = 127; - blockType = w.getBlockTypeIdAt(mx, my, mz); + blockType = cache.getBlockTypeID(mx, my, mz); break; } } if(blockType == 0) { /* Hit air - now find non-air */ - while((blockType = w.getBlockTypeIdAt(mx, my, mz)) == 0) { + while((blockType = cache.getBlockTypeID(mx, my, mz)) == 0) { my--; if(my < 0) { my = 0; @@ -112,14 +113,14 @@ public class FlatMap extends MapType { } } else { - my = w.getHighestBlockYAt(mx, mz) - 1; + my = cache.getHighestBlockYAt(mx, mz) - 1; if(my > maximumHeight) my = maximumHeight; - blockType = w.getBlockTypeIdAt(mx, my, mz); + blockType = cache.getBlockTypeID(mx, my, mz); } byte data = 0; Color[] colors = colorScheme.colors[blockType]; if(colorScheme.datacolors[blockType] != null) { - data = w.getBlockAt(mx, my, mz).getData(); + data = cache.getBlockData(mx, my, mz); colors = colorScheme.datacolors[blockType][data]; } if (colors == null) diff --git a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java index d07f5713..59041e16 100644 --- a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java @@ -1,6 +1,7 @@ package org.dynmap.kzedmap; import org.bukkit.World; +import org.dynmap.MapChunkCache; import org.dynmap.Color; import org.dynmap.ConfigurationNode; @@ -11,14 +12,15 @@ public class CaveTileRenderer extends DefaultTileRenderer { } @Override - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) { + protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, + MapChunkCache cache) { boolean air = true; result.setTransparent(); for (;;) { if (y < 0) return; - int id = world.getBlockTypeIdAt(x, y, z); + int id = cache.getBlockTypeID(x, y, z); if(isnether) { /* Make ceiling into air in nether */ if(id != 0) id = 0; diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 963c5d39..f7d1efae 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -18,6 +18,7 @@ import org.dynmap.ColorScheme; import org.dynmap.ConfigurationNode; import org.dynmap.MapManager; import org.dynmap.debug.Debug; +import org.dynmap.MapChunkCache; public class DefaultTileRenderer implements MapTileRenderer { protected static final Color translucent = new Color(0, 0, 0, 0); @@ -44,7 +45,7 @@ public class DefaultTileRenderer implements MapTileRenderer { colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme")); } - public boolean render(KzedMapTile tile, File outputFile) { + public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) { World world = tile.getWorld(); boolean isnether = (world.getEnvironment() == Environment.NETHER); BufferedImage im = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB); @@ -73,8 +74,8 @@ public class DefaultTileRenderer implements MapTileRenderer { jz = iz; for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) { - scan(world, jx, iy, jz, 0, isnether, c1); - scan(world, jx, iy, jz, 2, isnether, c2); + scan(world, jx, iy, jz, 0, isnether, c1, cache); + scan(world, jx, iy, jz, 2, isnether, c2, cache); if(c1.isTransparent() == false) { rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue(); r.setPixel(x, y, rgb); @@ -97,10 +98,10 @@ public class DefaultTileRenderer implements MapTileRenderer { jz = iz - 1; for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) { - scan(world, jx, iy, jz, 2, isnether, c1); + scan(world, jx, iy, jz, 2, isnether, c1, cache); jx++; jz++; - scan(world, jx, iy, jz, 0, isnether, c2); + scan(world, jx, iy, jz, 0, isnether, c2, cache); if(c1.isTransparent() == false) { rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue(); r.setPixel(x, y, rgb); @@ -210,13 +211,14 @@ public class DefaultTileRenderer implements MapTileRenderer { } - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) { + protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, + MapChunkCache cache) { result.setTransparent(); for (;;) { if (y < 0) { return; } - int id = world.getBlockTypeIdAt(x, y, z); + int id = cache.getBlockTypeID(x, y, z); byte data = 0; if(isnether) { /* Make bedrock ceiling into air in nether */ if(id != 0) { @@ -230,7 +232,7 @@ public class DefaultTileRenderer implements MapTileRenderer { isnether = false; } if(colorScheme.datacolors[id] != null) { /* If data colored */ - data = world.getBlockAt(x, y, z).getData(); + data = cache.getBlockData(x, y, z); } switch (seq) { case 0: @@ -270,7 +272,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* this block is transparent, so recurse */ - scan(world, x, y, z, seq, isnether, result); + scan(world, x, y, z, seq, isnether, result, cache); int cr = c.getRed(); int cg = c.getGreen(); diff --git a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java index d5314e54..2c379878 100644 --- a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java @@ -1,6 +1,7 @@ package org.dynmap.kzedmap; import java.util.HashSet; +import org.dynmap.MapChunkCache; import java.util.List; import org.bukkit.World; @@ -19,14 +20,15 @@ public class HighlightTileRenderer extends DefaultTileRenderer { } @Override - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) { + protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, + MapChunkCache cache) { result.setTransparent(); for (;;) { if (y < 0) { break; } - int id = world.getBlockTypeIdAt(x, y, z); + int id = cache.getBlockTypeID(x, y, z); if(isnether) { /* Make bedrock ceiling into air in nether */ if(id != 0) { /* Remember first color we see, in case we wind up solid */ @@ -40,7 +42,7 @@ public class HighlightTileRenderer extends DefaultTileRenderer { } byte data = 0; if(colorScheme.datacolors[id] != null) { /* If data colored */ - data = world.getBlockAt(x, y, z).getData(); + data = cache.getBlockData(x, y, z); } switch (seq) { diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index ab395aa7..0b11697b 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -12,6 +12,7 @@ import org.dynmap.DynmapChunk; import org.dynmap.Log; import org.dynmap.MapTile; import org.dynmap.MapType; +import org.dynmap.MapChunkCache; public class KzedMap extends MapType { protected static final Logger log = Logger.getLogger("Minecraft"); @@ -203,12 +204,12 @@ public class KzedMap extends MapType { } @Override - public boolean render(MapTile tile, File outputFile) { + public boolean render(MapChunkCache cache, MapTile tile, File outputFile) { if (tile instanceof KzedZoomedMapTile) { - zoomrenderer.render((KzedZoomedMapTile) tile, outputFile); + zoomrenderer.render(cache, (KzedZoomedMapTile) tile, outputFile); return true; } else if (tile instanceof KzedMapTile) { - return ((KzedMapTile) tile).renderer.render((KzedMapTile) tile, outputFile); + return ((KzedMapTile) tile).renderer.render(cache, (KzedMapTile) tile, outputFile); } return false; } diff --git a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java index 7674990f..7ad8dd83 100644 --- a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java @@ -1,9 +1,10 @@ package org.dynmap.kzedmap; import java.io.File; +import org.dynmap.MapChunkCache; public interface MapTileRenderer { String getName(); - boolean render(KzedMapTile tile, File outputFile); + boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile); } diff --git a/src/main/java/org/dynmap/kzedmap/ZoomedTileRenderer.java b/src/main/java/org/dynmap/kzedmap/ZoomedTileRenderer.java index a0de1520..7e1d5d09 100644 --- a/src/main/java/org/dynmap/kzedmap/ZoomedTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/ZoomedTileRenderer.java @@ -2,12 +2,12 @@ package org.dynmap.kzedmap; import java.io.File; import java.util.Map; - +import org.dynmap.MapChunkCache; public class ZoomedTileRenderer { public ZoomedTileRenderer(Map configuration) { } - public void render(final KzedZoomedMapTile zt, final File outputPath) { + public void render(MapChunkCache cache, final KzedZoomedMapTile zt, final File outputPath) { return; /* Doing this in Default render, since image already loaded */ } } From 2ccf70d3fe96bd5c36274f6bfbca2103819f8d1b Mon Sep 17 00:00:00 2001 From: mikeprimm Date: Fri, 20 May 2011 08:46:12 -0700 Subject: [PATCH 2/7] Make sure boundary condition is right on getMaximumY - must be 1 or higher --- src/main/java/org/dynmap/MapChunkCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/dynmap/MapChunkCache.java b/src/main/java/org/dynmap/MapChunkCache.java index bcd56715..1649e457 100644 --- a/src/main/java/org/dynmap/MapChunkCache.java +++ b/src/main/java/org/dynmap/MapChunkCache.java @@ -186,7 +186,7 @@ public class MapChunkCache { if(snaparray != null) { CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; if(ss == null) { - return 0; + return 1; } else return ss.getHighestBlockYAt(x & 0xF, z & 0xF); From 920dea04ee96d787a4a2cb5d3a84e0d84b12b718 Mon Sep 17 00:00:00 2001 From: mikeprimm Date: Fri, 20 May 2011 08:47:35 -0700 Subject: [PATCH 3/7] Make sure boundary condition is respected - getHighestBlockY must return 1 or higher --- src/main/java/org/dynmap/CraftChunkSnapshot.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/dynmap/CraftChunkSnapshot.java b/src/main/java/org/dynmap/CraftChunkSnapshot.java index de080c69..8ab42381 100644 --- a/src/main/java/org/dynmap/CraftChunkSnapshot.java +++ b/src/main/java/org/dynmap/CraftChunkSnapshot.java @@ -94,14 +94,12 @@ public class CraftChunkSnapshot { } public int getHighestBlockYAt(int x, int z) { - int off = x << 11 | z << 7 | 127; + int off = x << 11 | z << 7 | 126; int i; - for(i = 127; (i >= 0); i--, off--) { + for(i = 127; (i >= 2); i--, off--) { if(buf[off] != 0) { - if(i < 127) i++; break; } - } return i; } From 992a905b0a05896109775697f31540515f366169 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 20 May 2011 20:52:34 -0500 Subject: [PATCH 4/7] Add shadowstrength attribute for surface renderer - enables shadows based on top-down chunk sky light data --- configuration.txt | 392 +++++++++--------- src/main/java/org/dynmap/MapChunkCache.java | 19 +- .../dynmap/kzedmap/DefaultTileRenderer.java | 45 +- 3 files changed, 257 insertions(+), 199 deletions(-) diff --git a/configuration.txt b/configuration.txt index c765f162..b02ad709 100644 --- a/configuration.txt +++ b/configuration.txt @@ -1,195 +1,197 @@ -# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/plugins/dynmap/ - -# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) -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: -# - chunkloaded -# - playermove -# - playerjoin - - blockplaced - - blockbreak - -# The path where the tile-files are placed. -tilespath: web/tiles - -# The path where the web-files are located. -webpath: web - -# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). -webserver-bindaddress: 0.0.0.0 - -# The TCP-port the webserver will listen on. -webserver-port: 8123 - -# Disables Webserver portion of Dynmap (Advanced users only) -disable-webserver: false - -# Writes JSON to file in the webpath -jsonfile: false - -# How often the json file gets written to(in seconds) -jsonfile-interval: 1 - -# Output player health for web usage -health-in-json: false - -# Use timesliced fullrender - takes a bit longer, but much more polite for server -timeslicerender: true - -# Period between tile renders for timesliced fullrender, in seconds -timesliceinterval: 0.5 - -# The maptypes Dynmap will use to render. -worlds: - - name: world - maps: - - class: org.dynmap.flat.FlatMap - prefix: flat - colorscheme: default - - class: org.dynmap.kzedmap.KzedMap - renderers: - - class: org.dynmap.kzedmap.DefaultTileRenderer - prefix: t - maximumheight: 127 - colorscheme: default - #- class: org.dynmap.kzedmap.HighlightTileRenderer - # prefix: ht - # maximumheight: 127 - # colorscheme: default - # highlight: # For highlighting multiple block-types. - # - 56 # Highlight diamond-ore - # - 66 # Highlight minecart track - # highlight: 56 # For highlighting a single block-type. - - class: org.dynmap.kzedmap.CaveTileRenderer - prefix: ct - maximumheight: 127 - - name: nether - maps: - - class: org.dynmap.flat.FlatMap - prefix: flat - colorscheme: default - - class: org.dynmap.kzedmap.KzedMap - renderers: - - class: org.dynmap.kzedmap.DefaultTileRenderer - prefix: nt - maximumheight: 127 - colorscheme: default - -web: - # Handles the clientside updates differently only enable if using jsonfile - jsonfile: false - - # Interval the browser should poll for updates. - updaterate: 2000 - - allowchat: true - allowwebchat: true - webchat-interval: 5 - # Set to true to enable HeroChat support - enableherochat: false - # Control which HeroChat channel messages from web are directed to - herochatwebchannel: Global - # Control which channels are monitored and reported to the web - herochatchannels: - - Global - #- Trade - #- Haggle - - showplayerfacesinmenu: true - - joinmessage: "%playername% joined" - quitmessage: "%playername% quit" - spammessage: "You may only chat once every %interval% seconds." - - components: - - type: chat - - type: chatballoon - focuschatballoons: false - - type: chatbox - showplayerfaces: true - messagettl: 5 - - type: playermarkers - showplayerfaces: true - showplayerhealth: false - #- type: digitalclock - - type: timeofdayclock - showdigitalclock: true - #showweather: true - #- type: regions - # name: WorldGuard - # useworldpath: true - # filename: regions.yml - # basenode: regions - # use3dregions: true - # infowindow: '
%regionname% - %priority% (%parent%)
Owners %playerowners% %groupowners%
Members %playermembers% %groupmembers%
Flags
%flags%
' - # regionstyle: - # strokeColor: "#FF0000" - # strokeOpacity: 0.8 - # strokeWeight: 3 - # fillColor: "#FF0000" - # fillOpacity: 0.35 - - defaultzoom: 0 - defaultworld: world - worlds: - - title: World - name: world - center: - x: 0 - y: 64 - z: 0 - maps: - - type: FlatMapType - title: Flat - name: flat - prefix: flat - - type: KzedMapType - title: Surface - name: surface - prefix: t - #- type: KzedMapType - # title: Highlighted Map - # name: highlight - # prefix: ht - - type: KzedMapType - title: Cave - name: cave - prefix: ct - - title: Nether - name: nether - center: - x: 0 - y: 64 - z: 0 - maps: - - type: FlatMapType - title: Flat - name: flat - prefix: flat - - type: KzedMapType - title: Surface - name: nether - prefix: nt - # Example: - #- title: Other World # With what name the world is displayed. - # name: world_other # The actual name of the world (equal to your directory-name). - # maps: - # - type: KzedMapType # The type (or perspective) of the map. At the moment, there are no others than KzedMapType. - # title: Surface # The name of the map that will be displayed. - # name: surface # The actual name of the map (should be unique for this world). - # prefix: t # The prefix of the tile-files that are generated. - # icon: images/block_other.png # Sets a custom icon for the map. (optional) - # - type: KzedMapType - # title: Cave - # name: cave - # prefix: ct -# Enables debugging. -#debuggers: -# - class: org.dynmap.debug.LogDebugger +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/plugins/dynmap/ + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +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: +# - chunkloaded +# - playermove +# - playerjoin + - blockplaced + - blockbreak + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Writes JSON to file in the webpath +jsonfile: false + +# How often the json file gets written to(in seconds) +jsonfile-interval: 1 + +# Output player health for web usage +health-in-json: false + +# Use timesliced fullrender - takes a bit longer, but much more polite for server +timeslicerender: true + +# Period between tile renders for timesliced fullrender, in seconds +timesliceinterval: 0.5 + +# The maptypes Dynmap will use to render. +worlds: + - name: world + maps: + - class: org.dynmap.flat.FlatMap + prefix: flat + colorscheme: default + - class: org.dynmap.kzedmap.KzedMap + renderers: + - class: org.dynmap.kzedmap.DefaultTileRenderer + prefix: t + maximumheight: 127 + colorscheme: default + # Add shadows to world (based on top-down shadows from chunk data) + # shadowstrength: 1.0 + #- class: org.dynmap.kzedmap.HighlightTileRenderer + # prefix: ht + # maximumheight: 127 + # colorscheme: default + # highlight: # For highlighting multiple block-types. + # - 56 # Highlight diamond-ore + # - 66 # Highlight minecart track + # highlight: 56 # For highlighting a single block-type. + - class: org.dynmap.kzedmap.CaveTileRenderer + prefix: ct + maximumheight: 127 + - name: nether + maps: + - class: org.dynmap.flat.FlatMap + prefix: flat + colorscheme: default + - class: org.dynmap.kzedmap.KzedMap + renderers: + - class: org.dynmap.kzedmap.DefaultTileRenderer + prefix: nt + maximumheight: 127 + colorscheme: default + +web: + # Handles the clientside updates differently only enable if using jsonfile + jsonfile: false + + # Interval the browser should poll for updates. + updaterate: 2000 + + allowchat: true + allowwebchat: true + webchat-interval: 5 + # Set to true to enable HeroChat support + enableherochat: false + # Control which HeroChat channel messages from web are directed to + herochatwebchannel: Global + # Control which channels are monitored and reported to the web + herochatchannels: + - Global + #- Trade + #- Haggle + + showplayerfacesinmenu: true + + joinmessage: "%playername% joined" + quitmessage: "%playername% quit" + spammessage: "You may only chat once every %interval% seconds." + + components: + - type: chat + - type: chatballoon + focuschatballoons: false + - type: chatbox + showplayerfaces: true + messagettl: 5 + - type: playermarkers + showplayerfaces: true + showplayerhealth: false + #- type: digitalclock + - type: timeofdayclock + showdigitalclock: true + #showweather: true + #- type: regions + # name: WorldGuard + # useworldpath: true + # filename: regions.yml + # basenode: regions + # use3dregions: true + # infowindow: '
%regionname% - %priority% (%parent%)
Owners %playerowners% %groupowners%
Members %playermembers% %groupmembers%
Flags
%flags%
' + # regionstyle: + # strokeColor: "#FF0000" + # strokeOpacity: 0.8 + # strokeWeight: 3 + # fillColor: "#FF0000" + # fillOpacity: 0.35 + + defaultzoom: 0 + defaultworld: world + worlds: + - title: World + name: world + center: + x: 0 + y: 64 + z: 0 + maps: + - type: FlatMapType + title: Flat + name: flat + prefix: flat + - type: KzedMapType + title: Surface + name: surface + prefix: t + #- type: KzedMapType + # title: Highlighted Map + # name: highlight + # prefix: ht + - type: KzedMapType + title: Cave + name: cave + prefix: ct + - title: Nether + name: nether + center: + x: 0 + y: 64 + z: 0 + maps: + - type: FlatMapType + title: Flat + name: flat + prefix: flat + - type: KzedMapType + title: Surface + name: nether + prefix: nt + # Example: + #- title: Other World # With what name the world is displayed. + # name: world_other # The actual name of the world (equal to your directory-name). + # maps: + # - type: KzedMapType # The type (or perspective) of the map. At the moment, there are no others than KzedMapType. + # title: Surface # The name of the map that will be displayed. + # name: surface # The actual name of the map (should be unique for this world). + # prefix: t # The prefix of the tile-files that are generated. + # icon: images/block_other.png # Sets a custom icon for the map. (optional) + # - type: KzedMapType + # title: Cave + # name: cave + # prefix: ct +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger diff --git a/src/main/java/org/dynmap/MapChunkCache.java b/src/main/java/org/dynmap/MapChunkCache.java index 1649e457..bd4efb10 100644 --- a/src/main/java/org/dynmap/MapChunkCache.java +++ b/src/main/java/org/dynmap/MapChunkCache.java @@ -89,8 +89,8 @@ public class MapChunkCache { Object cc = gethandle.invoke(c); byte[] buf = new byte[32768 + 16384 + 16384 + 16384]; /* Get big enough buffer for whole chunk */ getchunkdata.invoke(cc, buf, 0, 0, 0, 16, 128, 16, 0); - snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = - new CraftChunkSnapshot(chunk.x, chunk.z, buf); + CraftChunkSnapshot ss = new CraftChunkSnapshot(chunk.x, chunk.z, buf); + snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = ss; } catch (Exception x) { } } @@ -195,4 +195,19 @@ public class MapChunkCache { return w.getHighestBlockYAt(x, z); } } + /* Get sky light level + */ + public int getBlockSkyLight(int x, int y, int z) { + if(snaparray != null) { + CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + if(ss == null) { + return 15; + } + else + return ss.getBlockSkyLight(x & 0xF, y, z & 0xF); + } + else { + return 15; + } + } } diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index f7d1efae..4c10b118 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -29,6 +29,7 @@ public class DefaultTileRenderer implements MapTileRenderer { protected HashSet highlightBlocks = new HashSet(); protected Color highlightColor = new Color(255, 0, 0); + protected int shadowscale[]; /* index=skylight level, value = 256 * scaling value */ @Override public String getName() { return name; @@ -42,6 +43,19 @@ public class DefaultTileRenderer implements MapTileRenderer { if (maximumHeight > 127) maximumHeight = 127; } + o = configuration.get("shadowstrength"); + if(o != null) { + double shadowweight = Double.parseDouble(String.valueOf(o)); + if(shadowweight > 0.0) { + shadowscale = new int[16]; + for(int i = 0; i < 16; i++) { + double v = 256.0 * (1.0 - (shadowweight * (15-i) / 15.0)); + shadowscale[i] = (int)v; + if(shadowscale[i] > 256) shadowscale[i] = 256; + if(shadowscale[i] < 0) shadowscale[i] = 0; + } + } + } colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme")); } @@ -210,9 +224,14 @@ public class DefaultTileRenderer implements MapTileRenderer { new Client.Tile(zmtile.getFilename())); } - protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, MapChunkCache cache) { + scan(world, x, y, z, seq, isnether, result, cache, 15); + } + + private void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, + MapChunkCache cache, int lightlevel) { + int newlightlevel = 15; result.setTransparent(); for (;;) { if (y < 0) { @@ -234,6 +253,10 @@ public class DefaultTileRenderer implements MapTileRenderer { if(colorScheme.datacolors[id] != null) { /* If data colored */ data = cache.getBlockData(x, y, z); } + if(shadowscale != null) { + newlightlevel = cache.getBlockSkyLight(x, y, z); /* Remember this - light path for next block */ + } + switch (seq) { case 0: x--; @@ -268,16 +291,25 @@ public class DefaultTileRenderer implements MapTileRenderer { if (c.getAlpha() == 255) { /* it's opaque - the ray ends here */ result.setColor(c); + if(lightlevel < 15) { /* Not full light? */ + shadowColor(result, lightlevel); + } return; } /* this block is transparent, so recurse */ - scan(world, x, y, z, seq, isnether, result, cache); + scan(world, x, y, z, seq, isnether, result, cache, newlightlevel); int cr = c.getRed(); int cg = c.getGreen(); int cb = c.getBlue(); int ca = c.getAlpha(); + if(lightlevel < 15) { + int scale = shadowscale[lightlevel]; + cr = (cr * scale) >> 8; + cg = (cg * scale) >> 8; + cb = (cb * scale) >> 8; + } cr *= ca; cg *= ca; cb *= ca; @@ -287,6 +319,15 @@ public class DefaultTileRenderer implements MapTileRenderer { } } } + lightlevel = newlightlevel; /* Advance - next block uses last block's light */ } } + private final void shadowColor(Color c, int lightlevel) { + int scale = shadowscale[lightlevel]; + if(scale == 0) + c.setRGBA(0, 0, 0, c.getAlpha()); + else if(scale < 256) + c.setRGBA((c.getRed() * scale) >> 8, (c.getGreen() * scale) >> 8, + (c.getBlue() * scale) >> 8, c.getAlpha()); + } } From b56332eae81f081b41e889583e4d796c762a3a27 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 20 May 2011 20:59:39 -0500 Subject: [PATCH 5/7] Fix line feeds... --- configuration.txt | 394 +++++++++++++++++++++++----------------------- 1 file changed, 197 insertions(+), 197 deletions(-) diff --git a/configuration.txt b/configuration.txt index b02ad709..6924b988 100644 --- a/configuration.txt +++ b/configuration.txt @@ -1,197 +1,197 @@ -# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/plugins/dynmap/ - -# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) -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: -# - chunkloaded -# - playermove -# - playerjoin - - blockplaced - - blockbreak - -# The path where the tile-files are placed. -tilespath: web/tiles - -# The path where the web-files are located. -webpath: web - -# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). -webserver-bindaddress: 0.0.0.0 - -# The TCP-port the webserver will listen on. -webserver-port: 8123 - -# Disables Webserver portion of Dynmap (Advanced users only) -disable-webserver: false - -# Writes JSON to file in the webpath -jsonfile: false - -# How often the json file gets written to(in seconds) -jsonfile-interval: 1 - -# Output player health for web usage -health-in-json: false - -# Use timesliced fullrender - takes a bit longer, but much more polite for server -timeslicerender: true - -# Period between tile renders for timesliced fullrender, in seconds -timesliceinterval: 0.5 - -# The maptypes Dynmap will use to render. -worlds: - - name: world - maps: - - class: org.dynmap.flat.FlatMap - prefix: flat - colorscheme: default - - class: org.dynmap.kzedmap.KzedMap - renderers: - - class: org.dynmap.kzedmap.DefaultTileRenderer - prefix: t - maximumheight: 127 - colorscheme: default - # Add shadows to world (based on top-down shadows from chunk data) - # shadowstrength: 1.0 - #- class: org.dynmap.kzedmap.HighlightTileRenderer - # prefix: ht - # maximumheight: 127 - # colorscheme: default - # highlight: # For highlighting multiple block-types. - # - 56 # Highlight diamond-ore - # - 66 # Highlight minecart track - # highlight: 56 # For highlighting a single block-type. - - class: org.dynmap.kzedmap.CaveTileRenderer - prefix: ct - maximumheight: 127 - - name: nether - maps: - - class: org.dynmap.flat.FlatMap - prefix: flat - colorscheme: default - - class: org.dynmap.kzedmap.KzedMap - renderers: - - class: org.dynmap.kzedmap.DefaultTileRenderer - prefix: nt - maximumheight: 127 - colorscheme: default - -web: - # Handles the clientside updates differently only enable if using jsonfile - jsonfile: false - - # Interval the browser should poll for updates. - updaterate: 2000 - - allowchat: true - allowwebchat: true - webchat-interval: 5 - # Set to true to enable HeroChat support - enableherochat: false - # Control which HeroChat channel messages from web are directed to - herochatwebchannel: Global - # Control which channels are monitored and reported to the web - herochatchannels: - - Global - #- Trade - #- Haggle - - showplayerfacesinmenu: true - - joinmessage: "%playername% joined" - quitmessage: "%playername% quit" - spammessage: "You may only chat once every %interval% seconds." - - components: - - type: chat - - type: chatballoon - focuschatballoons: false - - type: chatbox - showplayerfaces: true - messagettl: 5 - - type: playermarkers - showplayerfaces: true - showplayerhealth: false - #- type: digitalclock - - type: timeofdayclock - showdigitalclock: true - #showweather: true - #- type: regions - # name: WorldGuard - # useworldpath: true - # filename: regions.yml - # basenode: regions - # use3dregions: true - # infowindow: '
%regionname% - %priority% (%parent%)
Owners %playerowners% %groupowners%
Members %playermembers% %groupmembers%
Flags
%flags%
' - # regionstyle: - # strokeColor: "#FF0000" - # strokeOpacity: 0.8 - # strokeWeight: 3 - # fillColor: "#FF0000" - # fillOpacity: 0.35 - - defaultzoom: 0 - defaultworld: world - worlds: - - title: World - name: world - center: - x: 0 - y: 64 - z: 0 - maps: - - type: FlatMapType - title: Flat - name: flat - prefix: flat - - type: KzedMapType - title: Surface - name: surface - prefix: t - #- type: KzedMapType - # title: Highlighted Map - # name: highlight - # prefix: ht - - type: KzedMapType - title: Cave - name: cave - prefix: ct - - title: Nether - name: nether - center: - x: 0 - y: 64 - z: 0 - maps: - - type: FlatMapType - title: Flat - name: flat - prefix: flat - - type: KzedMapType - title: Surface - name: nether - prefix: nt - # Example: - #- title: Other World # With what name the world is displayed. - # name: world_other # The actual name of the world (equal to your directory-name). - # maps: - # - type: KzedMapType # The type (or perspective) of the map. At the moment, there are no others than KzedMapType. - # title: Surface # The name of the map that will be displayed. - # name: surface # The actual name of the map (should be unique for this world). - # prefix: t # The prefix of the tile-files that are generated. - # icon: images/block_other.png # Sets a custom icon for the map. (optional) - # - type: KzedMapType - # title: Cave - # name: cave - # prefix: ct -# Enables debugging. -#debuggers: -# - class: org.dynmap.debug.LogDebugger +# All paths in this configuration file are relative to Dynmap's data-folder: minecraft_server/plugins/dynmap/ + +# Treat hiddenplayers.txt as a whitelist for players to be shown on the map? (Default false) +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: +# - chunkloaded +# - playermove +# - playerjoin + - blockplaced + - blockbreak + +# The path where the tile-files are placed. +tilespath: web/tiles + +# The path where the web-files are located. +webpath: web + +# The network-interface the webserver will bind to (0.0.0.0 for all interfaces, 127.0.0.1 for only local access). +webserver-bindaddress: 0.0.0.0 + +# The TCP-port the webserver will listen on. +webserver-port: 8123 + +# Disables Webserver portion of Dynmap (Advanced users only) +disable-webserver: false + +# Writes JSON to file in the webpath +jsonfile: false + +# How often the json file gets written to(in seconds) +jsonfile-interval: 1 + +# Output player health for web usage +health-in-json: false + +# Use timesliced fullrender - takes a bit longer, but much more polite for server +timeslicerender: true + +# Period between tile renders for timesliced fullrender, in seconds +timesliceinterval: 0.5 + +# The maptypes Dynmap will use to render. +worlds: + - name: world + maps: + - class: org.dynmap.flat.FlatMap + prefix: flat + colorscheme: default + - class: org.dynmap.kzedmap.KzedMap + renderers: + - class: org.dynmap.kzedmap.DefaultTileRenderer + prefix: t + maximumheight: 127 + colorscheme: default + # Add shadows to world (based on top-down shadows from chunk data) + # shadowstrength: 1.0 + #- class: org.dynmap.kzedmap.HighlightTileRenderer + # prefix: ht + # maximumheight: 127 + # colorscheme: default + # highlight: # For highlighting multiple block-types. + # - 56 # Highlight diamond-ore + # - 66 # Highlight minecart track + # highlight: 56 # For highlighting a single block-type. + - class: org.dynmap.kzedmap.CaveTileRenderer + prefix: ct + maximumheight: 127 + - name: nether + maps: + - class: org.dynmap.flat.FlatMap + prefix: flat + colorscheme: default + - class: org.dynmap.kzedmap.KzedMap + renderers: + - class: org.dynmap.kzedmap.DefaultTileRenderer + prefix: nt + maximumheight: 127 + colorscheme: default + +web: + # Handles the clientside updates differently only enable if using jsonfile + jsonfile: false + + # Interval the browser should poll for updates. + updaterate: 2000 + + allowchat: true + allowwebchat: true + webchat-interval: 5 + # Set to true to enable HeroChat support + enableherochat: false + # Control which HeroChat channel messages from web are directed to + herochatwebchannel: Global + # Control which channels are monitored and reported to the web + herochatchannels: + - Global + #- Trade + #- Haggle + + showplayerfacesinmenu: true + + joinmessage: "%playername% joined" + quitmessage: "%playername% quit" + spammessage: "You may only chat once every %interval% seconds." + + components: + - type: chat + - type: chatballoon + focuschatballoons: false + - type: chatbox + showplayerfaces: true + messagettl: 5 + - type: playermarkers + showplayerfaces: true + showplayerhealth: false + #- type: digitalclock + - type: timeofdayclock + showdigitalclock: true + #showweather: true + #- type: regions + # name: WorldGuard + # useworldpath: true + # filename: regions.yml + # basenode: regions + # use3dregions: true + # infowindow: '
%regionname% - %priority% (%parent%)
Owners %playerowners% %groupowners%
Members %playermembers% %groupmembers%
Flags
%flags%
' + # regionstyle: + # strokeColor: "#FF0000" + # strokeOpacity: 0.8 + # strokeWeight: 3 + # fillColor: "#FF0000" + # fillOpacity: 0.35 + + defaultzoom: 0 + defaultworld: world + worlds: + - title: World + name: world + center: + x: 0 + y: 64 + z: 0 + maps: + - type: FlatMapType + title: Flat + name: flat + prefix: flat + - type: KzedMapType + title: Surface + name: surface + prefix: t + #- type: KzedMapType + # title: Highlighted Map + # name: highlight + # prefix: ht + - type: KzedMapType + title: Cave + name: cave + prefix: ct + - title: Nether + name: nether + center: + x: 0 + y: 64 + z: 0 + maps: + - type: FlatMapType + title: Flat + name: flat + prefix: flat + - type: KzedMapType + title: Surface + name: nether + prefix: nt + # Example: + #- title: Other World # With what name the world is displayed. + # name: world_other # The actual name of the world (equal to your directory-name). + # maps: + # - type: KzedMapType # The type (or perspective) of the map. At the moment, there are no others than KzedMapType. + # title: Surface # The name of the map that will be displayed. + # name: surface # The actual name of the map (should be unique for this world). + # prefix: t # The prefix of the tile-files that are generated. + # icon: images/block_other.png # Sets a custom icon for the map. (optional) + # - type: KzedMapType + # title: Cave + # name: cave + # prefix: ct +# Enables debugging. +#debuggers: +# - class: org.dynmap.debug.LogDebugger From 024e7dc96c138c1ce28f2e4577fd4ad1994a0b03 Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Fri, 20 May 2011 23:26:46 -0500 Subject: [PATCH 6/7] Tune shadow render, add to accept wait queue on web server --- .../dynmap/kzedmap/DefaultTileRenderer.java | 119 +++++++++++------- src/main/java/org/dynmap/web/HttpServer.java | 2 +- 2 files changed, 72 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 4c10b118..abe97a29 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -63,8 +63,9 @@ public class DefaultTileRenderer implements MapTileRenderer { World world = tile.getWorld(); boolean isnether = (world.getEnvironment() == Environment.NETHER); BufferedImage im = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB); - + BufferedImage zim = new BufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2, BufferedImage.TYPE_INT_RGB); WritableRaster r = im.getRaster(); + WritableRaster zr = zim.getRaster(); boolean isempty = true; int ix = KzedMap.anchorx + tile.px / 2 + tile.py / 2 - ((127-maximumHeight)/2); @@ -81,7 +82,8 @@ public class DefaultTileRenderer implements MapTileRenderer { Color c1 = new Color(); Color c2 = new Color(); - int[] rgb = new int[3]; + int[] rgb = new int[3*KzedMap.tileWidth]; + int[] zrgb = new int[3*KzedMap.tileWidth/2]; /* draw the map */ for (y = 0; y < KzedMap.tileHeight;) { jx = ix; @@ -90,21 +92,27 @@ public class DefaultTileRenderer implements MapTileRenderer { for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) { scan(world, jx, iy, jz, 0, isnether, c1, cache); scan(world, jx, iy, jz, 2, isnether, c2, cache); - if(c1.isTransparent() == false) { - rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue(); - r.setPixel(x, y, rgb); - isempty = false; - } - if(c2.isTransparent() == false) { - rgb[0] = c2.getRed(); rgb[1] = c2.getGreen(); rgb[2] = c2.getBlue(); - r.setPixel(x - 1, y, rgb); - isempty = false; - } + rgb[3*x] = c1.getRed(); + rgb[3*x+1] = c1.getGreen(); + rgb[3*x+2] = c1.getBlue(); + rgb[3*x-3] = c2.getRed(); + rgb[3*x-2] = c2.getGreen(); + rgb[3*x-1] = c2.getBlue(); + + isempty = isempty && c1.isTransparent() && c2.isTransparent(); + jx++; jz++; } + r.setPixels(0, y, KzedMap.tileWidth, 1, rgb); + /* Sum up zoomed pixels - bilinar filter */ + for(x = 0; x < KzedMap.tileWidth / 2; x++) { + zrgb[3*x] = rgb[6*x] + rgb[6*x+3]; + zrgb[3*x+1] = rgb[6*x+1] + rgb[6*x+4]; + zrgb[3*x+2] = rgb[6*x+2] + rgb[6*x+5]; + } y++; @@ -116,19 +124,25 @@ public class DefaultTileRenderer implements MapTileRenderer { jx++; jz++; scan(world, jx, iy, jz, 0, isnether, c2, cache); - if(c1.isTransparent() == false) { - rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue(); - r.setPixel(x, y, rgb); - isempty = false; - } - if(c2.isTransparent() == false) { - rgb[0] = c2.getRed(); rgb[1] = c2.getGreen(); rgb[2] = c2.getBlue(); - r.setPixel(x - 1, y, rgb); - isempty = false; - } + rgb[3*x] = c1.getRed(); + rgb[3*x+1] = c1.getGreen(); + rgb[3*x+2] = c1.getBlue(); + rgb[3*x-3] = c2.getRed(); + rgb[3*x-2] = c2.getGreen(); + rgb[3*x-1] = c2.getBlue(); + + isempty = isempty && c1.isTransparent() && c2.isTransparent(); } - + r.setPixels(0, y, KzedMap.tileWidth, 1, rgb); + /* Finish summing values for zoomed pixels */ + for(x = 0; x < KzedMap.tileWidth / 2; x++) { + zrgb[3*x] = (zrgb[3*x] + rgb[6*x] + rgb[6*x+3]) >> 2; + zrgb[3*x+1] = (zrgb[3*x+1] + rgb[6*x+1] + rgb[6*x+4]) >> 2; + zrgb[3*x+2] = (zrgb[3*x+2] + rgb[6*x+2] + rgb[6*x+5]) >> 2; + } + zr.setPixels(0, y/2, KzedMap.tileWidth/2, 1, zrgb); + y++; ix++; @@ -139,13 +153,14 @@ public class DefaultTileRenderer implements MapTileRenderer { final File fname = outputFile; final KzedMapTile mtile = tile; final BufferedImage img = im; + final BufferedImage zimg = zim; final KzedZoomedMapTile zmtile = new KzedZoomedMapTile(mtile.getWorld(), (KzedMap) mtile.getMap(), mtile); final File zoomFile = MapManager.mapman.getTileFile(zmtile); MapManager.mapman.enqueueImageWrite(new Runnable() { public void run() { - doFileWrites(fname, mtile, img, zmtile, zoomFile); + doFileWrites(fname, mtile, img, zmtile, zoomFile, zimg); } }); @@ -153,7 +168,8 @@ public class DefaultTileRenderer implements MapTileRenderer { } private void doFileWrites(final File fname, final KzedMapTile mtile, - final BufferedImage img, final KzedZoomedMapTile zmtile, final File zoomFile) { + final BufferedImage img, final KzedZoomedMapTile zmtile, final File zoomFile, + final BufferedImage zimg) { Debug.debug("saving image " + fname.getPath()); try { ImageIO.write(img, "png", fname); @@ -162,6 +178,8 @@ public class DefaultTileRenderer implements MapTileRenderer { } catch (java.lang.NullPointerException e) { Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); } + img.flush(); + mtile.file = fname; // Since we've already got the new tile, and we're on an async thread, just // make the zoomed tile here @@ -199,12 +217,9 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* blit scaled rendered tile onto zoom-out tile */ - Graphics2D g2 = zIm.createGraphics(); - g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g2.drawImage(img, ox, oy, scw, sch, null); - g2.dispose(); /* Supposed to speed up non-heap memory recovery */ - - img.flush(); + WritableRaster zim = zIm.getRaster(); + zim.setRect(ox, oy, zimg.getRaster()); + zimg.flush(); /* save zoom-out tile */ @@ -226,12 +241,7 @@ public class DefaultTileRenderer implements MapTileRenderer { protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, MapChunkCache cache) { - scan(world, x, y, z, seq, isnether, result, cache, 15); - } - - private void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result, - MapChunkCache cache, int lightlevel) { - int newlightlevel = 15; + int lightlevel = 15; result.setTransparent(); for (;;) { if (y < 0) { @@ -250,13 +260,29 @@ public class DefaultTileRenderer implements MapTileRenderer { else isnether = false; } - if(colorScheme.datacolors[id] != null) { /* If data colored */ - data = cache.getBlockData(x, y, z); + if(id != 0) { /* No update needed for air */ + if(colorScheme.datacolors[id] != null) { /* If data colored */ + data = cache.getBlockData(x, y, z); + } + if((shadowscale != null) && (y < 127)) { + /* Find light level of previous chunk */ + switch(seq) { + case 0: + lightlevel = cache.getBlockSkyLight(x, y+1, z); + break; + case 1: + lightlevel = cache.getBlockSkyLight(x+1, y, z); + break; + case 2: + lightlevel = cache.getBlockSkyLight(x, y+1, z); + break; + case 3: + lightlevel = cache.getBlockSkyLight(x, y, z-1); + break; + } + } } - if(shadowscale != null) { - newlightlevel = cache.getBlockSkyLight(x, y, z); /* Remember this - light path for next block */ - } - + switch (seq) { case 0: x--; @@ -298,7 +324,7 @@ public class DefaultTileRenderer implements MapTileRenderer { } /* this block is transparent, so recurse */ - scan(world, x, y, z, seq, isnether, result, cache, newlightlevel); + scan(world, x, y, z, seq, isnether, result, cache); int cr = c.getRed(); int cg = c.getGreen(); @@ -319,14 +345,11 @@ public class DefaultTileRenderer implements MapTileRenderer { } } } - lightlevel = newlightlevel; /* Advance - next block uses last block's light */ } } private final void shadowColor(Color c, int lightlevel) { int scale = shadowscale[lightlevel]; - if(scale == 0) - c.setRGBA(0, 0, 0, c.getAlpha()); - else if(scale < 256) + if(scale < 256) c.setRGBA((c.getRed() * scale) >> 8, (c.getGreen() * scale) >> 8, (c.getBlue() * scale) >> 8, c.getAlpha()); } diff --git a/src/main/java/org/dynmap/web/HttpServer.java b/src/main/java/org/dynmap/web/HttpServer.java index 8ba67232..8ada63e0 100644 --- a/src/main/java/org/dynmap/web/HttpServer.java +++ b/src/main/java/org/dynmap/web/HttpServer.java @@ -29,7 +29,7 @@ public class HttpServer extends Thread { } public void startServer() throws IOException { - sock = new ServerSocket(port, 5, bindAddress); + sock = new ServerSocket(port, 50, bindAddress); /* 5 too low - more than a couple users during render will get connect errors on some tile loads */ listeningThread = this; start(); Log.info("Dynmap WebServer started on " + bindAddress + ":" + port); From 325f069b469dab461e45088bd7dcf0f5d95bb30b Mon Sep 17 00:00:00 2001 From: Mike Primm Date: Sat, 21 May 2011 00:30:42 -0500 Subject: [PATCH 7/7] Add BufferedImage cache --- src/main/java/org/dynmap/flat/FlatMap.java | 5 +- .../dynmap/kzedmap/DefaultTileRenderer.java | 17 +++--- src/main/java/org/dynmap/kzedmap/KzedMap.java | 56 ++++++++++++++++++- 3 files changed, 68 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index 49a27c3a..0cb51f43 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -19,6 +19,7 @@ import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.debug.Debug; +import org.dynmap.kzedmap.KzedMap; import org.dynmap.MapChunkCache; public class FlatMap extends MapType { @@ -80,7 +81,7 @@ public class FlatMap extends MapType { boolean isnether = (w.getEnvironment() == Environment.NETHER) && (maximumHeight == 127); boolean rendered = false; - BufferedImage im = new BufferedImage(t.size, t.size, BufferedImage.TYPE_INT_RGB); + BufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size); WritableRaster raster = im.getRaster(); int[] pixel = new int[4]; @@ -177,7 +178,7 @@ public class FlatMap extends MapType { } catch (java.lang.NullPointerException e) { Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); } - img.flush(); + KzedMap.freeBufferedImage(img); MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getFilename())); } diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index abe97a29..c3526ccf 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -1,7 +1,5 @@ package org.dynmap.kzedmap; -import java.awt.Graphics2D; -import java.awt.RenderingHints; import java.awt.image.BufferedImage; import java.awt.image.WritableRaster; import java.io.File; @@ -62,8 +60,8 @@ public class DefaultTileRenderer implements MapTileRenderer { public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) { World world = tile.getWorld(); boolean isnether = (world.getEnvironment() == Environment.NETHER); - BufferedImage im = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB); - BufferedImage zim = new BufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2, BufferedImage.TYPE_INT_RGB); + BufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + BufferedImage zim = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2); WritableRaster r = im.getRaster(); WritableRaster zr = zim.getRaster(); boolean isempty = true; @@ -208,9 +206,11 @@ public class DefaultTileRenderer implements MapTileRenderer { } catch (IndexOutOfBoundsException e) { } + boolean zIm_allocated = false; if (zIm == null) { /* create new one */ - zIm = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB); + zIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); + zIm_allocated = true; Debug.debug("New zoom-out tile created " + zmtile.getFilename()); } else { Debug.debug("Loaded zoom-out tile from " + zmtile.getFilename()); @@ -219,7 +219,7 @@ public class DefaultTileRenderer implements MapTileRenderer { /* blit scaled rendered tile onto zoom-out tile */ WritableRaster zim = zIm.getRaster(); zim.setRect(ox, oy, zimg.getRaster()); - zimg.flush(); + KzedMap.freeBufferedImage(zimg); /* save zoom-out tile */ @@ -231,7 +231,10 @@ public class DefaultTileRenderer implements MapTileRenderer { } catch (java.lang.NullPointerException e) { Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e); } - zIm.flush(); + if(zIm_allocated) + KzedMap.freeBufferedImage(zIm); + else + zIm.flush(); /* Push updates for both files.*/ MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getFilename())); diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 0b11697b..44b8f024 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -1,7 +1,10 @@ package org.dynmap.kzedmap; +import java.awt.image.BufferedImage; import java.io.File; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.logging.Logger; @@ -33,10 +36,17 @@ public class KzedMap extends MapType { public static final int anchorx = 0; public static final int anchory = 127; public static final int anchorz = 0; - + MapTileRenderer[] renderers; ZoomedTileRenderer zoomrenderer; + /* BufferedImage cache - we use the same things a lot... */ + private static Object lock = new Object(); + private static HashMap> imgcache = + new HashMap>(); /* Indexed by resolution - X<<32+Y */ + private static int[] zerobuf = new int[128]; + private static final int CACHE_LIMIT = 10; + public KzedMap(ConfigurationNode configuration) { Log.info("Loading renderers for map '" + getClass().toString() + "'..."); List renderers = configuration.createInstances("renderers", new Class[0], new Object[0]); @@ -246,4 +256,48 @@ public class KzedMap extends MapType { else return y - (y % zTileHeight); } + + /** + * Allocate buffered image from pool, if possible + * @param x - x dimension + * @param y - y dimension + */ + public static BufferedImage allocateBufferedImage(int x, int y) { + BufferedImage img = null; + synchronized(lock) { + long k = (x<<16) + y; + LinkedList ll = imgcache.get(k); + if(ll != null) { + img = ll.poll(); + } + } + if(img != null) { /* Got it - reset it for use */ + if(zerobuf.length < x) + zerobuf = new int[x]; + img.setRGB(0, 0, x, y, zerobuf, 0, 0); + } + else { + img = new BufferedImage(x, y, BufferedImage.TYPE_INT_RGB); + } + return img; + } + + /** + * Return buffered image to pool + */ + public static void freeBufferedImage(BufferedImage img) { + img.flush(); + synchronized(lock) { + long k = (img.getWidth()<<16) + img.getHeight(); + LinkedList ll = imgcache.get(k); + if(ll == null) { + ll = new LinkedList(); + imgcache.put(k, ll); + } + if(ll.size() < CACHE_LIMIT) { + ll.add(img); + img = null; + } + } + } }