diff --git a/configuration.txt b/configuration.txt index 3dd74ef8..5689a57d 100644 --- a/configuration.txt +++ b/configuration.txt @@ -65,6 +65,9 @@ display-whitelist: false # How often a tile gets rendered (in seconds). renderinterval: 1 +# Tile hashing is used to minimize tile file updates when no changes have occurred - set to false to disable +enabletilehash: true + render-triggers: # - chunkloaded # - playermove diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index fe8642f2..ff45f8da 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -277,7 +277,9 @@ public class DynmapPlugin extends JavaPlugin { "hide", "show", "fullrender", - "reload" })); + "reload", + "stats", + "resetstats" })); @Override public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { @@ -343,6 +345,16 @@ public class DynmapPlugin extends JavaPlugin { sender.sendMessage("Reloading Dynmap..."); reload(); sender.sendMessage("Dynmap reloaded"); + } else if (c.equals("stats") && checkPlayerPermission(sender, "stats")) { + if(args.length == 1) + mapManager.printStats(sender, null); + else + mapManager.printStats(sender, args[1]); + } else if (c.equals("resetstats") && checkPlayerPermission(sender, "resetstats")) { + if(args.length == 1) + mapManager.resetStats(sender, null); + else + mapManager.resetStats(sender, args[1]); } return true; } diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index b26c8261..1a9c8cd0 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -8,6 +8,7 @@ import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.TreeSet; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.Callable; @@ -15,6 +16,7 @@ import java.util.concurrent.Future; import org.bukkit.Location; import org.bukkit.World; import org.bukkit.scheduler.BukkitScheduler; +import org.bukkit.command.CommandSender; import org.dynmap.debug.Debug; public class MapManager { @@ -27,16 +29,26 @@ public class MapManager { private long timeslice_int = 0; /* In milliseconds */ /* Which fullrenders are active */ private HashMap active_renders = new HashMap(); - + /* Tile hash manager */ + public TileHashManager hashman; /* lock for our data structures */ public static final Object lock = new Object(); public static MapManager mapman; /* Our singleton */ /* Thread pool for processing renders */ - private ScheduledThreadPoolExecutor renderpool; + private DynmapScheduledThreadPoolExecutor renderpool; private static final int POOL_SIZE = 3; + private HashMap mapstats = new HashMap(); + + private static class MapStats { + int loggedcnt; + int renderedcnt; + int updatedcnt; + int transparentcnt; + } + public DynmapWorld getWorld(String name) { DynmapWorld world = worldsLookup.get(name); return world; @@ -46,6 +58,21 @@ public class MapManager { return worlds; } + private class DynmapScheduledThreadPoolExecutor extends ScheduledThreadPoolExecutor { + DynmapScheduledThreadPoolExecutor() { + super(POOL_SIZE); + } + + protected void afterExecute(Runnable r, Throwable x) { + if(r instanceof FullWorldRenderState) { + ((FullWorldRenderState)r).cleanup(); + } + if(x != null) { + Log.severe("Exception during render job: " + r); + x.printStackTrace(); + } + } + } /* This always runs on render pool threads - no bukkit calls from here */ private class FullWorldRenderState implements Runnable { DynmapWorld world; /* Which world are we rendering */ @@ -56,6 +83,7 @@ public class MapManager { HashSet rendered = null; LinkedList renderQueue = null; MapTile tile0 = null; + MapTile tile = null; int rendercnt = 0; /* Full world, all maps render */ @@ -73,8 +101,18 @@ public class MapManager { tile0 = t; } + public String toString() { + return "world=" + world.world.getName() + ", map=" + map + " tile=" + tile; + } + + public void cleanup() { + if(tile0 == null) { + synchronized(lock) { + active_renders.remove(world.world.getName()); + } + } + } public void run() { - MapTile tile; long tstart = System.currentTimeMillis(); if(tile0 == null) { /* Not single tile render */ @@ -90,9 +128,7 @@ public class MapManager { map_index++; /* Next map */ if(map_index >= world.maps.size()) { /* Last one done? */ Log.info("Full render of '" + world.world.getName() + "' finished."); - synchronized(lock) { - active_renders.remove(world.world.getName()); - } + cleanup(); return; } map = world.maps.get(map_index); @@ -125,6 +161,7 @@ public class MapManager { DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile); MapChunkCache cache = createMapChunkCache(w, requiredChunks); if(cache == null) { + cleanup(); return; /* Cancelled/aborted */ } if(tile0 != null) { /* Single tile? */ @@ -159,6 +196,9 @@ public class MapManager { renderpool.execute(this); } } + else { + cleanup(); + } } } @@ -193,6 +233,8 @@ public class MapManager { scheduler = plugin.getServer().getScheduler(); + hashman = new TileHashManager(DynmapPlugin.tilesDirectory, configuration.getBoolean("enabletilehash", true)); + tileQueue.start(); for (World world : plug_in.getServer().getWorlds()) { @@ -308,7 +350,7 @@ public class MapManager { public void startRendering() { tileQueue.start(); - renderpool = new ScheduledThreadPoolExecutor(POOL_SIZE); + renderpool = new DynmapScheduledThreadPoolExecutor(); } public void stopRendering() { @@ -379,4 +421,65 @@ public class MapManager { return null; } } + /** + * Update map tile statistics + */ + public void updateStatistics(MapTile tile, String subtype, boolean rendered, boolean updated, boolean transparent) { + synchronized(lock) { + String k = tile.getKey(); + if(subtype != null) + k += "." + subtype; + MapStats ms = mapstats.get(k); + if(ms == null) { + ms = new MapStats(); + mapstats.put(k, ms); + } + ms.loggedcnt++; + if(rendered) + ms.renderedcnt++; + if(updated) + ms.updatedcnt++; + if(transparent) + ms.transparentcnt++; + } + } + /** + * Print statistics command + */ + public void printStats(CommandSender sender, String prefix) { + sender.sendMessage("Tile Render Statistics:"); + MapStats tot = new MapStats(); + synchronized(lock) { + for(String k: new TreeSet(mapstats.keySet())) { + if((prefix != null) && !k.startsWith(prefix)) + continue; + MapStats ms = mapstats.get(k); + sender.sendMessage(" " + k + ": processed=" + ms.loggedcnt + ", rendered=" + ms.renderedcnt + + ", updated=" + ms.updatedcnt + ", transparent=" + ms.transparentcnt); + tot.loggedcnt += ms.loggedcnt; + tot.renderedcnt += ms.renderedcnt; + tot.updatedcnt += ms.updatedcnt; + tot.transparentcnt += ms.transparentcnt; + } + } + sender.sendMessage(" TOTALS: processed=" + tot.loggedcnt + ", rendered=" + tot.renderedcnt + + ", updated=" + tot.updatedcnt + ", transparent=" + tot.transparentcnt); + } + /** + * Reset statistics + */ + public void resetStats(CommandSender sender, String prefix) { + synchronized(lock) { + for(String k : mapstats.keySet()) { + if((prefix != null) && !k.startsWith(prefix)) + continue; + MapStats ms = mapstats.get(k); + ms.loggedcnt = 0; + ms.renderedcnt = 0; + ms.updatedcnt = 0; + ms.transparentcnt = 0; + } + } + sender.sendMessage("Tile Render Statistics reset"); + } } diff --git a/src/main/java/org/dynmap/MapTile.java b/src/main/java/org/dynmap/MapTile.java index e23ef1cd..552ad561 100644 --- a/src/main/java/org/dynmap/MapTile.java +++ b/src/main/java/org/dynmap/MapTile.java @@ -36,4 +36,8 @@ public abstract class MapTile { } return super.equals(obj); } + + public String getKey() { + return world.getName() + "." + map.getName(); + } } diff --git a/src/main/java/org/dynmap/MapType.java b/src/main/java/org/dynmap/MapType.java index 8e75fd03..8617b713 100644 --- a/src/main/java/org/dynmap/MapType.java +++ b/src/main/java/org/dynmap/MapType.java @@ -18,4 +18,6 @@ public abstract class MapType { public void buildClientConfiguration(JSONObject worldObject) { } + + public abstract String getName(); } diff --git a/src/main/java/org/dynmap/TileHashManager.java b/src/main/java/org/dynmap/TileHashManager.java new file mode 100644 index 00000000..83030a45 --- /dev/null +++ b/src/main/java/org/dynmap/TileHashManager.java @@ -0,0 +1,152 @@ +package org.dynmap; +import java.io.File; +import java.io.RandomAccessFile; +import java.util.Arrays; +import java.util.LinkedHashMap; +import java.io.IOException; +import java.util.zip.CRC32; + +/** + * Image hash code manager - used to reduce compression and notification of updated tiles that do not actually yield new content + * + */ +public class TileHashManager { + private File tiledir; /* Base tile directory */ + private boolean enabled; + + /** + * Each tile hash file is a 32x32 tile grid, with each file having a CRC32 hash code generated from its pre-compression frame buffer + */ + private static class TileHashFile { + final String key; + final String subtype; + final int x; /* minimum tile coordinate / 32 */ + final int y; /* minimum tile coordinate / 32 */ + private File hf; + TileHashFile(String key, String subtype, int x, int y) { + this.key = key; + if(subtype != null) + this.subtype = subtype; + else + this.subtype = ""; + this.x = x; + this.y = y; + } + @Override + public boolean equals(Object o) { + if(!(o instanceof TileHashFile)) + return false; + TileHashFile fo = (TileHashFile)o; + return (x == fo.x) && (y == fo.y) && key.equals(fo.key) && (subtype.equals(fo.subtype)); + } + @Override + public int hashCode() { + return key.hashCode() ^ subtype.hashCode() ^ (x << 16) ^ y; + } + + public File getHashFile(File tiledir) { + if(hf == null) + hf = new File(tiledir, key + (subtype.equals("")?"":("." + subtype)) + "_" + x + "_" + y + ".hash"); + return hf; + } + /* Write to file */ + public void writeToFile(File tiledir, byte[] crcbuf) { + try { + RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "rw"); + fd.seek(0); + fd.write(crcbuf); + fd.close(); + } catch (IOException iox) { + Log.severe("Error writing hash file - " + getHashFile(tiledir).getPath()); + } + } + + /* Read from file */ + public void readFromFile(File tiledir, byte[] crcbuf) { + try { + RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "r"); + fd.seek(0); + fd.read(crcbuf); + fd.close(); + } catch (IOException iox) { + Arrays.fill(crcbuf, (byte)0xFF); + writeToFile(tiledir, crcbuf); + } + } + /* Read CRC */ + public long getCRC(int tx, int ty, byte[] crcbuf) { + int off = (128 * (ty & 0x1F)) + (4 * (tx & 0x1F)); + long crc = 0; + for(int i = 0; i < 4; i++) + crc = (crc << 8) + (0xFF & (int)crcbuf[off+i]); + return crc; + } + /* Set CRC */ + public void setCRC(int tx, int ty, byte[] crcbuf, long crc) { + int off = (128 * (ty & 0x1F)) + (4 * (tx & 0x1F)); + for(int i = 0; i < 4; i++) + crcbuf[off+i] = (byte)((crc >> ((3-i)*8)) & 0xFF); + } + } + private Object lock = new Object(); + private LinkedHashMap tilehash = new LinkedHashMap(16, (float) 0.75, true); + private CRC32 crc32 = new CRC32(); + + public TileHashManager(File tileroot, boolean enabled) { + tiledir = tileroot; + this.enabled = enabled; + } + + /* Read cached hashcode for given tile */ + public long getImageHashCode(String key, String subtype, int tx, int ty) { + if(!enabled) { + return -1; /* Return value that never matches */ + } + TileHashFile thf = new TileHashFile(key, subtype, tx >> 5, ty >> 5); + synchronized(lock) { + byte[] crcbuf = tilehash.get(thf); /* See if we have it cached */ + if(crcbuf == null) { /* If not in cache, load it */ + crcbuf = new byte[32*32*4]; /* Get our space */ + Arrays.fill(crcbuf, (byte)0xFF); /* Fill with -1 */ + tilehash.put(thf, crcbuf); /* Add to cache */ + thf.readFromFile(tiledir, crcbuf); + } + return thf.getCRC(tx & 0x1F, ty & 0x1F, crcbuf); + } + } + /* Calculate hash code for given buffer */ + public long calculateTileHash(int[] newbuf) { + if(!enabled) { + return 0; /* Return value that doesn't match */ + } + synchronized(lock) { + /* Calculate CRC-32 for buffer */ + crc32.reset(); + for(int i = 0; i < newbuf.length; i++) { + int v = newbuf[i]; + crc32.update(0xFF & v); + crc32.update(0xFF & (v >> 8)); + crc32.update(0xFF & (v >> 16)); + crc32.update(0xFF & (v >> 24)); + } + return crc32.getValue(); + } + } + /* Update hashcode for given tile */ + public void updateHashCode(String key, String subtype, int tx, int ty, long newcrc) { + if(!enabled) + return; + synchronized(lock) { + /* Now, find and check existing value */ + TileHashFile thf = new TileHashFile(key, subtype, tx >> 5, ty >> 5); + byte[] crcbuf = tilehash.get(thf); /* See if we have it cached */ + if(crcbuf == null) { /* If not in cache, load it */ + crcbuf = new byte[32*32*4]; /* Get our space */ + tilehash.put(thf, crcbuf); /* Add to cache */ + thf.readFromFile(tiledir, crcbuf); + } + thf.setCRC(tx & 0x1F, ty & 0x1F, crcbuf, newcrc); /* Update field */ + thf.writeToFile(tiledir, crcbuf); /* And write it out */ + } + } +} diff --git a/src/main/java/org/dynmap/flat/FlatMap.java b/src/main/java/org/dynmap/flat/FlatMap.java index e6092538..769e3509 100644 --- a/src/main/java/org/dynmap/flat/FlatMap.java +++ b/src/main/java/org/dynmap/flat/FlatMap.java @@ -20,6 +20,7 @@ import org.dynmap.ColorScheme; import org.dynmap.ConfigurationNode; import org.dynmap.DynmapChunk; import org.dynmap.MapManager; +import org.dynmap.TileHashManager; import org.dynmap.MapTile; import org.dynmap.MapType; import org.dynmap.debug.Debug; @@ -234,35 +235,63 @@ public class FlatMap extends MapType { rendered = true; } } - /* Wrap buffer as buffered image */ - Debug.debug("saving image " + outputFile.getPath()); - try { - ImageIO.write(im.buf_img, "png", outputFile); - } catch (IOException e) { - Debug.error("Failed to save image: " + outputFile.getPath(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e); + /* Test to see if we're unchanged from older tile */ + TileHashManager hashman = MapManager.mapman.hashman; + long crc = hashman.calculateTileHash(argb_buf); + boolean tile_update = false; + if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.x, t.y))) { + /* Wrap buffer as buffered image */ + Debug.debug("saving image " + outputFile.getPath()); + try { + ImageIO.write(im.buf_img, "png", outputFile); + } catch (IOException e) { + Debug.error("Failed to save image: " + outputFile.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e); + } + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename())); + hashman.updateHashCode(tile.getKey(), null, t.x, t.y, crc); + tile_update = true; + } + else { + Debug.debug("skipping image " + outputFile.getPath() + " - hash match"); } KzedMap.freeBufferedImage(im); - MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename())); + MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered); /* If day too, handle it */ if(night_and_day) { File dayfile = new File(outputFile.getParent(), tile.getDayFilename()); - Debug.debug("saving image " + dayfile.getPath()); - try { - ImageIO.write(im_day.buf_img, "png", dayfile); - } catch (IOException e) { - Debug.error("Failed to save image: " + dayfile.getPath(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e); + + crc = hashman.calculateTileHash(argb_buf_day); + if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) { + Debug.debug("saving image " + dayfile.getPath()); + try { + ImageIO.write(im_day.buf_img, "png", dayfile); + } catch (IOException e) { + Debug.error("Failed to save image: " + dayfile.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e); + } + MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getDayFilename())); + hashman.updateHashCode(tile.getKey(), "day", t.x, t.y, crc); + tile_update = true; + } + else { + Debug.debug("skipping image " + dayfile.getPath() + " - hash match"); + tile_update = false; } KzedMap.freeBufferedImage(im_day); - MapManager.mapman.pushUpdate(tile.getWorld(), new Client.Tile(tile.getDayFilename())); + MapManager.mapman.updateStatistics(tile, "day", true, tile_update, !rendered); } return rendered; } + + public String getName() { + return prefix; + } + public static class FlatMapTile extends MapTile { FlatMap map; @@ -286,6 +315,9 @@ public class FlatMap extends MapType { public String getDayFilename() { return map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png"; } + public String toString() { + return getWorld().getName() + ":" + getFilename(); + } } @Override diff --git a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java index 7b5b72a1..eb62f8ba 100644 --- a/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java +++ b/src/main/java/org/dynmap/kzedmap/DefaultTileRenderer.java @@ -16,6 +16,7 @@ import org.dynmap.Color; import org.dynmap.ColorScheme; 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; @@ -188,7 +189,7 @@ public class DefaultTileRenderer implements MapTileRenderer { (KzedMap) tile.getMap(), tile); File zoomFile = MapManager.mapman.getTileFile(zmtile); - doFileWrites(outputFile, tile, im, im_day, zmtile, zoomFile, zim, zim_day); + doFileWrites(outputFile, tile, im, im_day, zmtile, zoomFile, zim, zim_day, !isempty); return !isempty; } @@ -220,49 +221,83 @@ public class DefaultTileRenderer implements MapTileRenderer { private void doFileWrites(final File fname, final KzedMapTile mtile, final KzedBufferedImage img, final KzedBufferedImage img_day, final KzedZoomedMapTile zmtile, final File zoomFile, - final KzedBufferedImage zimg, final KzedBufferedImage zimg_day) { - Debug.debug("saving image " + fname.getPath()); - try { - ImageIO.write(img.buf_img, "png", fname); - } catch (IOException e) { - Debug.error("Failed to save image: " + fname.getPath(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); + final KzedBufferedImage zimg, final KzedBufferedImage zimg_day, boolean rendered) { + + /* Get coordinates of zoomed tile */ + int ox = (mtile.px == zmtile.getTileX())?0:KzedMap.tileWidth/2; + int oy = (mtile.py == zmtile.getTileY())?0:KzedMap.tileHeight/2; + + /* Test to see if we're unchanged from older tile */ + TileHashManager hashman = MapManager.mapman.hashman; + long crc = hashman.calculateTileHash(img.argb_buf); + boolean updated_fname = false; + if((!fname.exists()) || (crc != hashman.getImageHashCode(mtile.getKey(), null, mtile.px, mtile.py))) { + Debug.debug("saving image " + fname.getPath()); + try { + ImageIO.write(img.buf_img, "png", fname); + } catch (IOException e) { + Debug.error("Failed to save image: " + fname.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); + } + MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getFilename())); + hashman.updateHashCode(mtile.getKey(), null, mtile.px, mtile.py, crc); + updated_fname = true; } KzedMap.freeBufferedImage(img); + MapManager.mapman.updateStatistics(mtile, null, true, updated_fname, !rendered); + + mtile.file = fname; + + boolean updated_dfname = false; + File dfname = new File(fname.getParent(), mtile.getDayFilename()); if(img_day != null) { - File dfname = new File(fname.getParent(), mtile.getDayFilename()); - Debug.debug("saving image " + dfname.getPath()); - try { - ImageIO.write(img_day.buf_img, "png", dfname); - } catch (IOException e) { - Debug.error("Failed to save image: " + dfname.getPath(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save image (NullPointerException): " + dfname.getPath(), e); + crc = hashman.calculateTileHash(img.argb_buf); + if((!dfname.exists()) || (crc != hashman.getImageHashCode(mtile.getKey(), "day", mtile.px, mtile.py))) { + Debug.debug("saving image " + dfname.getPath()); + try { + ImageIO.write(img_day.buf_img, "png", dfname); + } catch (IOException e) { + Debug.error("Failed to save image: " + dfname.getPath(), e); + } catch (java.lang.NullPointerException e) { + Debug.error("Failed to save image (NullPointerException): " + dfname.getPath(), e); + } + MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getDayFilename())); + hashman.updateHashCode(mtile.getKey(), "day", mtile.px, mtile.py, crc); + updated_dfname = true; } KzedMap.freeBufferedImage(img_day); + MapManager.mapman.updateStatistics(mtile, "day", true, updated_dfname, !rendered); } - mtile.file = fname; + // Since we've already got the new tile, and we're on an async thread, just // make the zoomed tile here - int px = mtile.px; - int py = mtile.py; - int zpx = zmtile.getTileX(); - int zpy = zmtile.getTileY(); - - /* scaled size */ - int scw = KzedMap.tileWidth / 2; - int sch = KzedMap.tileHeight / 2; - - /* origin in zoomed-out tile */ - int ox = 0; - int oy = 0; - - if (zpx != px) - ox = scw; - if (zpy != py) - oy = sch; + boolean ztile_updated = false; + if(updated_fname || (!zoomFile.exists())) { + saveZoomedTile(zmtile, zoomFile, zimg, ox, oy); + MapManager.mapman.pushUpdate(zmtile.getWorld(), + new Client.Tile(zmtile.getFilename())); + ztile_updated = true; + } + KzedMap.freeBufferedImage(zimg); + MapManager.mapman.updateStatistics(zmtile, null, true, ztile_updated, !rendered); + + if(zimg_day != null) { + File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename()); + ztile_updated = false; + if(updated_dfname || (!zoomFile_day.exists())) { + saveZoomedTile(zmtile, zoomFile_day, zimg_day, ox, oy); + MapManager.mapman.pushUpdate(zmtile.getWorld(), + new Client.Tile(zmtile.getDayFilename())); + ztile_updated = true; + } + KzedMap.freeBufferedImage(zimg_day); + MapManager.mapman.updateStatistics(zmtile, "day", true, ztile_updated, !rendered); + } + } + private void saveZoomedTile(final KzedZoomedMapTile zmtile, final File zoomFile, + final KzedBufferedImage zimg, int ox, int oy) { BufferedImage zIm = null; KzedBufferedImage kzIm = null; try { @@ -284,7 +319,6 @@ public class DefaultTileRenderer implements MapTileRenderer { /* blit scaled rendered tile onto zoom-out tile */ zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, zimg.argb_buf, 0, KzedMap.tileWidth/2); - KzedMap.freeBufferedImage(zimg); /* save zoom-out tile */ @@ -300,61 +334,8 @@ public class DefaultTileRenderer implements MapTileRenderer { KzedMap.freeBufferedImage(kzIm); else zIm.flush(); - - if(zimg_day != null) { - File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename()); - - zIm = null; - kzIm = null; - try { - zIm = ImageIO.read(zoomFile_day); - } catch (IOException e) { - } catch (IndexOutOfBoundsException e) { - } - zIm_allocated = false; - if (zIm == null) { - /* create new one */ - kzIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight); - zIm = kzIm.buf_img; - zIm_allocated = true; - Debug.debug("New zoom-out tile created " + zmtile.getFilename()); - } else { - Debug.debug("Loaded zoom-out tile from " + zmtile.getFilename()); - } - - /* blit scaled rendered tile onto zoom-out tile */ - zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, zimg_day.argb_buf, 0, KzedMap.tileWidth/2); - KzedMap.freeBufferedImage(zimg_day); - - /* save zoom-out tile */ - - try { - ImageIO.write(zIm, "png", zoomFile_day); - Debug.debug("Saved zoom-out tile at " + zoomFile_day.getName()); - } catch (IOException e) { - Debug.error("Failed to save zoom-out tile: " + zoomFile_day.getName(), e); - } catch (java.lang.NullPointerException e) { - Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile_day.getName(), e); - } - if(zIm_allocated) - KzedMap.freeBufferedImage(kzIm); - else - zIm.flush(); - } - /* Push updates for both files.*/ - MapManager.mapman.pushUpdate(mtile.getWorld(), - new Client.Tile(mtile.getFilename())); - MapManager.mapman.pushUpdate(zmtile.getWorld(), - new Client.Tile(zmtile.getFilename())); - if(img_day != null) { - MapManager.mapman.pushUpdate(mtile.getWorld(), - new Client.Tile(mtile.getDayFilename())); - MapManager.mapman.pushUpdate(zmtile.getWorld(), - new Client.Tile(zmtile.getDayFilename())); - } } - protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day, MapChunkCache.MapIterator mapiter) { int lightlevel = 15; diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index b456ae08..201ef50e 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -317,7 +317,11 @@ public class KzedMap extends MapType { } } } - + + public String getName() { + return "KzedMap"; + } + @Override public void buildClientConfiguration(JSONObject worldObject) { for(MapTileRenderer renderer : renderers) { diff --git a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java index 24349360..14ab7baf 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMapTile.java @@ -48,6 +48,10 @@ public class KzedMapTile extends MapTile { return o.px == px && o.py == py && o.getWorld().equals(getWorld()); } + public String getKey() { + return getWorld().getName() + "." + renderer.getName(); + } + public String toString() { return getWorld().getName() + ":" + getFilename(); } diff --git a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java index 9d7a28ff..5d33bffe 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java +++ b/src/main/java/org/dynmap/kzedmap/KzedZoomedMapTile.java @@ -56,4 +56,10 @@ public class KzedZoomedMapTile extends MapTile { } return super.equals(obj); } + + + public String getKey() { + return getWorld().getName() + ".z" + originalTile.renderer.getName(); + } + }