diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index c7c24723..1012eb37 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -18,6 +18,9 @@ import org.bukkit.World; import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.command.CommandSender; import org.dynmap.debug.Debug; +import org.dynmap.utils.LegacyMapChunkCache; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.NewMapChunkCache; public class MapManager { public AsynchronousQueue tileQueue; @@ -407,13 +410,24 @@ public class MapManager { return world.updates.getUpdatedObjects(since); } + private static boolean use_legacy = false; /** * Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread */ public MapChunkCache createMapChunkCache(final World w, final DynmapChunk[] chunks) { Callable job = new Callable() { public MapChunkCache call() { - return new MapChunkCache(w, chunks); + MapChunkCache c = null; + try { + if(!use_legacy) + c = new NewMapChunkCache(); + } catch (NoClassDefFoundError ncdfe) { + use_legacy = true; + } + if(c == null) + c = new LegacyMapChunkCache(); + c.loadChunks(w, chunks); + return c; } }; Future rslt = scheduler.callSyncMethod(plug_in, job); diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 8617b713..72e62ece 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -3,6 +3,7 @@ package org.dynmap; import java.io.File; import org.bukkit.Location; +import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; public abstract class MapType { diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index afe026e3..3dda7c16 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -23,8 +23,9 @@ import org.dynmap.MapType; import org.dynmap.debug.Debug; import org.dynmap.kzedmap.KzedMap; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; -import org.dynmap.MapChunkCache; import org.dynmap.utils.FileLockManager; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; import org.json.simple.JSONObject; public class FlatMap extends MapType { @@ -126,7 +127,7 @@ public class FlatMap extends MapType { argb_buf_day = im_day.argb_buf; pixel_day = new int[4]; } - MapChunkCache.MapIterator mapiter = cache.getIterator(t.x * t.size, 127, t.y * t.size); + MapIterator mapiter = cache.getIterator(t.x * t.size, 127, t.y * t.size); for (int x = 0; x < t.size; x++) { mapiter.initialize(t.x * t.size + x, 127, t.y * t.size); for (int y = 0; y < t.size; y++, mapiter.incrementZ()) { @@ -135,7 +136,7 @@ public class FlatMap extends MapType { if(isnether) { while((blockType = mapiter.getBlockTypeID()) != 0) { mapiter.decrementY(); - if(mapiter.y < 0) { /* Solid - use top */ + if(mapiter.getY() < 0) { /* Solid - use top */ mapiter.setY(127); blockType = mapiter.getBlockTypeID(); break; @@ -144,7 +145,7 @@ public class FlatMap extends MapType { if(blockType == 0) { /* Hit air - now find non-air */ while((blockType = mapiter.getBlockTypeID()) == 0) { mapiter.decrementY(); - if(mapiter.y < 0) { + if(mapiter.getY() < 0) { mapiter.setY(0); break; } @@ -187,7 +188,7 @@ public class FlatMap extends MapType { } /* If ambient light less than 15, do scaling */ else if((shadowscale != null) && (ambientlight < 15)) { - if(mapiter.y < 127) + if(mapiter.getY() < 127) mapiter.incrementY(); if(night_and_day) { /* Use unscaled color for day (no shadows from above) */ pixel_day[0] = pixel[0]; @@ -202,10 +203,10 @@ public class FlatMap extends MapType { pixel[3] = 255; } else { /* Only do height keying if we're not messing with ambient light */ - boolean below = mapiter.y < 64; + boolean below = mapiter.getY() < 64; // Make height range from 0 - 1 (1 - 0 for below and 0 - 1 above) - float height = (below ? 64 - mapiter.y : mapiter.y - 64) / 64.0f; + float height = (below ? 64 - mapiter.getY() : mapiter.getY() - 64) / 64.0f; // Defines the 'step' in coloring. float step = 10 / 128.0f; @@ -306,7 +307,7 @@ public class FlatMap extends MapType { return rendered; } - private void process_transparent(int[] pixel, int[] pixel_day, MapChunkCache.MapIterator mapiter) { + private void process_transparent(int[] pixel, int[] pixel_day, MapIterator mapiter) { int r = pixel[0], g = pixel[1], b = pixel[2], a = pixel[3]; int r_day = 0, g_day = 0, b_day = 0, a_day = 0; if(pixel_day != null) { @@ -318,7 +319,7 @@ public class FlatMap extends MapType { /* Handle lighting on cube */ if((shadowscale != null) && (ambientlight < 15)) { boolean did_inc = false; - if(mapiter.y < 127) { + if(mapiter.getY() < 127) { mapiter.incrementY(); did_inc = true; } @@ -337,7 +338,7 @@ public class FlatMap extends MapType { if(pixel_day != null) pixel_day[0] = pixel_day[1] = pixel_day[2] = pixel_day[3] = 0; mapiter.decrementY(); - if(mapiter.y >= 0) { + if(mapiter.getY() >= 0) { int blockType = mapiter.getBlockTypeID(); int data = 0; Color[] colors = colorScheme.colors[blockType]; diff --git a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java index 42d39799..67a02e71 100644 --- a/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/CaveTileRenderer.java @@ -1,9 +1,9 @@ package org.dynmap.kzedmap; import org.bukkit.World; -import org.dynmap.MapChunkCache; import org.dynmap.Color; import org.dynmap.ConfigurationNode; +import org.dynmap.utils.MapIterator; public class CaveTileRenderer extends DefaultTileRenderer { @@ -13,11 +13,11 @@ public class CaveTileRenderer extends DefaultTileRenderer { @Override protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, - MapChunkCache.MapIterator mapiter) { + MapIterator mapiter) { boolean air = true; result.setTransparent(); for (;;) { - if (mapiter.y < 0) + if (mapiter.getY() < 0) return; int id = mapiter.getBlockTypeID(); @@ -63,12 +63,12 @@ public class CaveTileRenderer extends DefaultTileRenderer { int cr, cg, cb; int mult = 256; - if (mapiter.y < 64) { + if (mapiter.getY() < 64) { cr = 0; - cg = 64 + mapiter.y * 3; - cb = 255 - mapiter.y * 4; + cg = 64 + mapiter.getY() * 3; + cb = 255 - mapiter.getY() * 4; } else { - cr = (mapiter.y - 64) * 4; + cr = (mapiter.getY() - 64) * 4; cg = 255; cb = 0; } diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index cdfa854d..d7628a33 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -18,9 +18,10 @@ import org.dynmap.ConfigurationNode; import org.dynmap.MapManager; import org.dynmap.TileHashManager; import org.dynmap.debug.Debug; -import org.dynmap.MapChunkCache; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.dynmap.utils.FileLockManager; +import org.dynmap.utils.MapChunkCache; +import org.dynmap.utils.MapIterator; import org.json.simple.JSONObject; public class DefaultTileRenderer implements MapTileRenderer { @@ -108,7 +109,7 @@ public class DefaultTileRenderer implements MapTileRenderer { int x, y; - MapChunkCache.MapIterator mapiter = cache.getIterator(ix, iy, iz); + MapIterator mapiter = cache.getIterator(ix, iy, iz); Color c1 = new Color(); Color c2 = new Color(); @@ -356,14 +357,14 @@ public class DefaultTileRenderer implements MapTileRenderer { } protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, - MapChunkCache.MapIterator mapiter) { + MapIterator mapiter) { int lightlevel = 15; int lightlevel_day = 15; result.setTransparent(); if(result_day != null) result_day.setTransparent(); for (;;) { - if (mapiter.y < 0) { + if (mapiter.getY() < 0) { return; } int id = mapiter.getBlockTypeID(); @@ -383,7 +384,7 @@ public class DefaultTileRenderer implements MapTileRenderer { if(colorScheme.datacolors[id] != null) { /* If data colored */ data = mapiter.getBlockData(); } - if((shadowscale != null) && (mapiter.y < 127)) { + if((shadowscale != null) && (mapiter.getY() < 127)) { /* Find light level of previous chunk */ switch(seq) { case 0: diff --git a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java index 8ea72b77..f2f48850 100644 --- a/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/HighlightTileRenderer.java @@ -1,12 +1,12 @@ package org.dynmap.kzedmap; import java.util.HashSet; -import org.dynmap.MapChunkCache; import java.util.List; import org.bukkit.World; import org.dynmap.Color; import org.dynmap.ConfigurationNode; +import org.dynmap.utils.MapIterator; public class HighlightTileRenderer extends DefaultTileRenderer { protected HashSet highlightBlocks = new HashSet(); @@ -21,10 +21,10 @@ public class HighlightTileRenderer extends DefaultTileRenderer { @Override protected void scan(World world,int seq, boolean isnether, final Color result, final Color result_day, - MapChunkCache.MapIterator mapiter) { + MapIterator mapiter) { result.setTransparent(); for (;;) { - if (mapiter.y < 0) { + if (mapiter.getY() < 0) { break; } diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 43037570..299dc81b 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -17,7 +17,7 @@ import org.dynmap.Log; import org.dynmap.MapManager; import org.dynmap.MapTile; import org.dynmap.MapType; -import org.dynmap.MapChunkCache; +import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; import java.awt.image.DataBufferInt; import java.awt.image.DataBuffer; diff --git a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java index 2dd34ff1..96f42c0e 100644 --- a/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/MapTileRenderer.java @@ -1,7 +1,8 @@ package org.dynmap.kzedmap; import java.io.File; -import org.dynmap.MapChunkCache; + +import org.dynmap.utils.MapChunkCache; import org.json.simple.JSONObject; diff --git a/src/main/java/org/dynmap/CraftChunkSnapshot.java b/src/main/java/org/dynmap/utils/CraftChunkSnapshot.java similarity index 96% rename from src/main/java/org/dynmap/CraftChunkSnapshot.java rename to src/main/java/org/dynmap/utils/CraftChunkSnapshot.java index 8987eb41..2f31ef8b 100644 --- a/src/main/java/org/dynmap/CraftChunkSnapshot.java +++ b/src/main/java/org/dynmap/utils/CraftChunkSnapshot.java @@ -1,10 +1,11 @@ -package org.dynmap; +package org.dynmap.utils; + /** * 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 implements ChunkSnapshot { +public class CraftChunkSnapshot implements LegacyChunkSnapshot { private final int x, z; private final byte[] buf; /* Flat buffer in uncompressed chunk file format */ private final byte[] hmap; /* Highest Y map */ diff --git a/src/main/java/org/dynmap/ChunkSnapshot.java b/src/main/java/org/dynmap/utils/LegacyChunkSnapshot.java similarity index 94% rename from src/main/java/org/dynmap/ChunkSnapshot.java rename to src/main/java/org/dynmap/utils/LegacyChunkSnapshot.java index 7dbf2363..ca3edc20 100644 --- a/src/main/java/org/dynmap/ChunkSnapshot.java +++ b/src/main/java/org/dynmap/utils/LegacyChunkSnapshot.java @@ -1,10 +1,10 @@ -package org.dynmap; +package org.dynmap.utils; /** * 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 interface ChunkSnapshot { +public interface LegacyChunkSnapshot { /** * Get block type for block at corresponding coordinate in the chunk * diff --git a/src/main/java/org/dynmap/MapChunkCache.java b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java similarity index 88% rename from src/main/java/org/dynmap/MapChunkCache.java rename to src/main/java/org/dynmap/utils/LegacyMapChunkCache.java index e7483a59..1e2ca23b 100644 --- a/src/main/java/org/dynmap/MapChunkCache.java +++ b/src/main/java/org/dynmap/utils/LegacyMapChunkCache.java @@ -1,15 +1,19 @@ -package org.dynmap; +package org.dynmap.utils; import java.lang.reflect.Method; import java.lang.reflect.Field; +import java.util.LinkedList; import org.bukkit.World; import org.bukkit.Chunk; import org.bukkit.entity.Entity; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; /** * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread */ -public class MapChunkCache { +public class LegacyMapChunkCache implements MapChunkCache { + private World w; private static Method getchunkdata = null; private static Method gethandle = null; private static Method poppreservedchunk = null; @@ -19,16 +23,16 @@ public class MapChunkCache { private int x_min, x_max, z_min, z_max; private int x_dim; - private ChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ + private LegacyChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ /** * Iterator for traversing map chunk cache (base is for non-snapshot) */ - public class MapIterator { - public int x, y, z; - private ChunkSnapshot snap; + public class OurMapIterator implements MapIterator { + private int x, y, z; + private LegacyChunkSnapshot snap; - MapIterator(int x0, int y0, int z0) { + OurMapIterator(int x0, int y0, int z0) { initialize(x0, y0, z0); } public final void initialize(int x0, int y0, int z0) { @@ -105,12 +109,15 @@ public class MapChunkCache { public final void setY(int y) { this.y = y; } + public final int getY() { + return y; + } } /** * Chunk cache for representing unloaded chunk */ - private static class EmptyChunk implements ChunkSnapshot { + private static class EmptyChunk implements LegacyChunkSnapshot { public final int getBlockTypeId(int x, int y, int z) { return 0; } @@ -129,6 +136,12 @@ public class MapChunkCache { } private static final EmptyChunk EMPTY = new EmptyChunk(); + + /** + * Construct empty cache + */ + public LegacyMapChunkCache() { + } /** * Create chunk cache container * @param w - world @@ -138,7 +151,7 @@ public class MapChunkCache { * @param z_max - maximum chunk z coordinate */ @SuppressWarnings({ "unchecked" }) - public MapChunkCache(World w, DynmapChunk[] chunks) { + public void loadChunks(World w, DynmapChunk[] chunks) { /* Compute range */ if(chunks.length == 0) { this.x_min = 0; @@ -162,10 +175,10 @@ public class MapChunkCache { } x_dim = x_max - x_min + 1; } + this.w = w; if(!initialized) { try { - @SuppressWarnings("rawtypes") 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 }); @@ -178,7 +191,6 @@ public class MapChunkCache { } /* Get CraftWorld.popPreservedChunk(x,z) - reduces memory bloat from map traversals (optional) */ try { - @SuppressWarnings("rawtypes") Class c = Class.forName("org.bukkit.craftbukkit.CraftWorld"); poppreservedchunk = c.getDeclaredMethod("popPreservedChunk", new Class[] { int.class, int.class }); } catch (ClassNotFoundException cnfx) { @@ -192,7 +204,7 @@ public class MapChunkCache { return; } } - snaparray = new ChunkSnapshot[x_dim * (z_max-z_min+1)]; + snaparray = new LegacyChunkSnapshot[x_dim * (z_max-z_min+1)]; if(gethandle != null) { // Load the required chunks. for (DynmapChunk chunk : chunks) { @@ -258,39 +270,39 @@ public class MapChunkCache { * Get block ID at coordinates */ public int getBlockTypeID(int x, int y, int z) { - ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + LegacyChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; return ss.getBlockTypeId(x & 0xF, y, z & 0xF); } /** * Get block data at coordiates */ public byte getBlockData(int x, int y, int z) { - ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + LegacyChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; return (byte)ss.getBlockData(x & 0xF, y, z & 0xF); } /* Get highest block Y * */ public int getHighestBlockYAt(int x, int z) { - ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + LegacyChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; return ss.getHighestBlockYAt(x & 0xF, z & 0xF); } /* Get sky light level */ public int getBlockSkyLight(int x, int y, int z) { - ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + LegacyChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; return ss.getBlockSkyLight(x & 0xF, y, z & 0xF); } /* Get emitted light level */ public int getBlockEmittedLight(int x, int y, int z) { - ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + LegacyChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; return ss.getBlockEmittedLight(x & 0xF, y, z & 0xF); } /** * Get cache iterator */ public MapIterator getIterator(int x, int y, int z) { - return new MapIterator(x, y, z); + return new OurMapIterator(x, y, z); } } diff --git a/src/main/java/org/dynmap/utils/MapChunkCache.java b/src/main/java/org/dynmap/utils/MapChunkCache.java new file mode 100644 index 00000000..9ce1fd7f --- /dev/null +++ b/src/main/java/org/dynmap/utils/MapChunkCache.java @@ -0,0 +1,40 @@ +package org.dynmap.utils; +import org.bukkit.World; +import org.dynmap.DynmapChunk; + +public interface MapChunkCache { + /** + * Load chunks into cache + * @param w - world + * @param chunks - chunks to be loaded + */ + void loadChunks(World w, DynmapChunk[] chunks); + /** + * Unload chunks + */ + void unloadChunks(); + /** + * Get block ID at coordinates + */ + int getBlockTypeID(int x, int y, int z); + /** + * Get block data at coordiates + */ + byte getBlockData(int x, int y, int z); + /** + * Get highest block Y + */ + int getHighestBlockYAt(int x, int z); + /** + * Get sky light level + */ + int getBlockSkyLight(int x, int y, int z); + /** + * Get emitted light level + */ + int getBlockEmittedLight(int x, int y, int z); + /** + * Get cache iterator + */ + public MapIterator getIterator(int x, int y, int z); +} diff --git a/src/main/java/org/dynmap/utils/MapIterator.java b/src/main/java/org/dynmap/utils/MapIterator.java new file mode 100644 index 00000000..ce20d20b --- /dev/null +++ b/src/main/java/org/dynmap/utils/MapIterator.java @@ -0,0 +1,74 @@ +package org.dynmap.utils; + +/** + * Iterator for traversing map chunk cache (base is for non-snapshot) + */ +public interface MapIterator { + /** + * Initialize iterator at given coordinates + * + * @param x0 + * @param y0 + * @param z0 + */ + void initialize(int x0, int y0, int z0); + /** + * Get block ID at current coordinates + * + * @return block id + */ + int getBlockTypeID(); + /** + * Get block data at current coordinates + * @return block data + */ + int getBlockData(); + /** + * Get highest block Y coordinate at current X,Z + * @return highest block coord + */ + int getHighestBlockYAt(); + /** + * Get block sky light level at current coordinate + * @return sky light level + */ + int getBlockSkyLight(); + /** + * Get emitted light level at current coordinate + * @return emitted light level + */ + int getBlockEmittedLight(); + /** + * Increment X of current position + */ + void incrementX(); + /** + * Decrement X of current position + */ + void decrementX(); + /** + * Increment Y of current position + */ + void incrementY(); + /** + * Decrement Y of current position + */ + void decrementY(); + /** + * Increment Z of current position + */ + void incrementZ(); + /** + * Decrement Y of current position + */ + void decrementZ(); + /** + * Set Y coordinate of current position + * @param y + */ + void setY(int y); + /** + * Get Y coordinate + */ + int getY(); +} diff --git a/src/main/java/org/dynmap/utils/NewMapChunkCache.java b/src/main/java/org/dynmap/utils/NewMapChunkCache.java new file mode 100644 index 00000000..6d83f647 --- /dev/null +++ b/src/main/java/org/dynmap/utils/NewMapChunkCache.java @@ -0,0 +1,285 @@ +package org.dynmap.utils; + +import java.lang.reflect.Method; +import java.util.LinkedList; +import org.bukkit.World; +import org.bukkit.Chunk; +import org.bukkit.block.Biome; +import org.bukkit.entity.Entity; +import org.bukkit.ChunkSnapshot; +import org.dynmap.DynmapChunk; +import org.dynmap.Log; + +/** + * Container for managing chunks - dependent upon using chunk snapshots, since rendering is off server thread + */ +public class NewMapChunkCache implements MapChunkCache { + private World w; + + private static Method poppreservedchunk = null; + + private int x_min, x_max, z_min, z_max; + private int x_dim; + + private ChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */ + + /** + * Iterator for traversing map chunk cache (base is for non-snapshot) + */ + public class OurMapIterator implements MapIterator { + private int x, y, z; + private ChunkSnapshot snap; + + OurMapIterator(int x0, int y0, int z0) { + initialize(x0, y0, z0); + } + public final void initialize(int x0, int y0, int z0) { + this.x = x0; + this.y = y0; + this.z = z0; + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + public final int getBlockTypeID() { + return snap.getBlockTypeId(x & 0xF, y, z & 0xF); + } + public final int getBlockData() { + return snap.getBlockData(x & 0xF, y, z & 0xF); + } + public final int getHighestBlockYAt() { + return snap.getHighestBlockYAt(x & 0xF, z & 0xF); + } + public final int getBlockSkyLight() { + return snap.getBlockSkyLight(x & 0xF, y, z & 0xF); + } + public final int getBlockEmittedLight() { + return snap.getBlockEmittedLight(x & 0xF, y, z & 0xF); + } + public final void incrementX() { + x++; + if((x & 0xF) == 0) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public final void decrementX() { + x--; + if((x & 0xF) == 15) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public final void incrementY() { + y++; + } + public final void decrementY() { + y--; + } + public final void incrementZ() { + z++; + if((z & 0xF) == 0) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public final void decrementZ() { + z--; + if((z & 0xF) == 15) { /* Next chunk? */ + try { + snap = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + } catch (ArrayIndexOutOfBoundsException aioobx) { + snap = EMPTY; + } + } + } + public final void setY(int y) { + this.y = y; + } + public final int getY() { + return y; + } + } + + /** + * Chunk cache for representing unloaded chunk + */ + private static class EmptyChunk implements ChunkSnapshot { + /* Need these for interface, but not used */ + public int getX() { return 0; } + public int getZ() { return 0; } + public String getWorldName() { return ""; } + public Biome getBiome(int x, int z) { return null; } + public double getRawBiomeTemperature(int x, int z) { return 0.0; } + public double getRawBiomeRainfall(int x, int z) { return 0.0; } + public long getCaptureFullTime() { return 0; } + + public final int getBlockTypeId(int x, int y, int z) { + return 0; + } + public final int getBlockData(int x, int y, int z) { + return 0; + } + public final int getBlockSkyLight(int x, int y, int z) { + return 15; + } + public final int getBlockEmittedLight(int x, int y, int z) { + return 0; + } + public final int getHighestBlockYAt(int x, int z) { + return 1; + } + } + + private static final EmptyChunk EMPTY = new EmptyChunk(); + + /** + * Construct empty cache + */ + 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) { + if(poppreservedchunk == null) { + /* Get CraftWorld.popPreservedChunk(x,z) - reduces memory bloat from map traversals (optional) */ + try { + Class c = Class.forName("org.bukkit.craftbukkit.CraftWorld"); + poppreservedchunk = c.getDeclaredMethod("popPreservedChunk", new Class[] { int.class, int.class }); + } catch (ClassNotFoundException cnfx) { + } catch (NoSuchMethodException nsmx) { + } + } + /* 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; + + snaparray = new ChunkSnapshot[x_dim * (z_max-z_min+1)]; + // 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); + snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = c.getChunkSnapshot(); + } + 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); + /* And pop preserved chunk - this is a bad leak in Bukkit for map traversals like us */ + try { + if(poppreservedchunk != null) + poppreservedchunk.invoke(w, chunk.x, chunk.z); + } catch (Exception x) { + Log.severe("Cannot pop preserved chunk - " + x.toString()); + } + } + } + /* Fill missing chunks with empty dummy chunk */ + for(int i = 0; i < snaparray.length; i++) { + if(snaparray[i] == null) + snaparray[i] = EMPTY; + } + } + /** + * Unload chunks + */ + public void unloadChunks() { + if(snaparray != null) { + for(int i = 0; i < snaparray.length; i++) { + snaparray[i] = null; + } + snaparray = null; + } + } + /** + * Get block ID at coordinates + */ + public int getBlockTypeID(int x, int y, int z) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockTypeId(x & 0xF, y, z & 0xF); + } + /** + * Get block data at coordiates + */ + public byte getBlockData(int x, int y, int z) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return (byte)ss.getBlockData(x & 0xF, y, z & 0xF); + } + /* Get highest block Y + * + */ + public int getHighestBlockYAt(int x, int z) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getHighestBlockYAt(x & 0xF, z & 0xF); + } + /* Get sky light level + */ + public int getBlockSkyLight(int x, int y, int z) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockSkyLight(x & 0xF, y, z & 0xF); + } + /* Get emitted light level + */ + public int getBlockEmittedLight(int x, int y, int z) { + ChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim]; + return ss.getBlockEmittedLight(x & 0xF, y, z & 0xF); + } + /** + * Get cache iterator + */ + public MapIterator getIterator(int x, int y, int z) { + return new OurMapIterator(x, y, z); + } +}