Add render statistics, support for tile hashcodes to stop non-updates

This commit is contained in:
Mike Primm 2011-05-31 00:33:54 -05:00
parent 4b30fff8a7
commit d393ccf6e9
11 changed files with 418 additions and 115 deletions

View File

@ -65,6 +65,9 @@ display-whitelist: false
# How often a tile gets rendered (in seconds). # How often a tile gets rendered (in seconds).
renderinterval: 1 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: render-triggers:
# - chunkloaded # - chunkloaded
# - playermove # - playermove

View File

@ -277,7 +277,9 @@ public class DynmapPlugin extends JavaPlugin {
"hide", "hide",
"show", "show",
"fullrender", "fullrender",
"reload" })); "reload",
"stats",
"resetstats" }));
@Override @Override
public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) { public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) {
@ -343,6 +345,16 @@ public class DynmapPlugin extends JavaPlugin {
sender.sendMessage("Reloading Dynmap..."); sender.sendMessage("Reloading Dynmap...");
reload(); reload();
sender.sendMessage("Dynmap reloaded"); 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; return true;
} }

View File

@ -8,6 +8,7 @@ import java.util.HashSet;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
@ -15,6 +16,7 @@ import java.util.concurrent.Future;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.command.CommandSender;
import org.dynmap.debug.Debug; import org.dynmap.debug.Debug;
public class MapManager { public class MapManager {
@ -27,16 +29,26 @@ public class MapManager {
private long timeslice_int = 0; /* In milliseconds */ private long timeslice_int = 0; /* In milliseconds */
/* Which fullrenders are active */ /* Which fullrenders are active */
private HashMap<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>(); private HashMap<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>();
/* Tile hash manager */
public TileHashManager hashman;
/* lock for our data structures */ /* lock for our data structures */
public static final Object lock = new Object(); public static final Object lock = new Object();
public static MapManager mapman; /* Our singleton */ public static MapManager mapman; /* Our singleton */
/* Thread pool for processing renders */ /* Thread pool for processing renders */
private ScheduledThreadPoolExecutor renderpool; private DynmapScheduledThreadPoolExecutor renderpool;
private static final int POOL_SIZE = 3; private static final int POOL_SIZE = 3;
private HashMap<String, MapStats> mapstats = new HashMap<String, MapStats>();
private static class MapStats {
int loggedcnt;
int renderedcnt;
int updatedcnt;
int transparentcnt;
}
public DynmapWorld getWorld(String name) { public DynmapWorld getWorld(String name) {
DynmapWorld world = worldsLookup.get(name); DynmapWorld world = worldsLookup.get(name);
return world; return world;
@ -46,6 +58,21 @@ public class MapManager {
return worlds; 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 */ /* This always runs on render pool threads - no bukkit calls from here */
private class FullWorldRenderState implements Runnable { private class FullWorldRenderState implements Runnable {
DynmapWorld world; /* Which world are we rendering */ DynmapWorld world; /* Which world are we rendering */
@ -56,6 +83,7 @@ public class MapManager {
HashSet<MapTile> rendered = null; HashSet<MapTile> rendered = null;
LinkedList<MapTile> renderQueue = null; LinkedList<MapTile> renderQueue = null;
MapTile tile0 = null; MapTile tile0 = null;
MapTile tile = null;
int rendercnt = 0; int rendercnt = 0;
/* Full world, all maps render */ /* Full world, all maps render */
@ -73,8 +101,18 @@ public class MapManager {
tile0 = t; 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() { public void run() {
MapTile tile;
long tstart = System.currentTimeMillis(); long tstart = System.currentTimeMillis();
if(tile0 == null) { /* Not single tile render */ if(tile0 == null) { /* Not single tile render */
@ -90,9 +128,7 @@ public class MapManager {
map_index++; /* Next map */ map_index++; /* Next map */
if(map_index >= world.maps.size()) { /* Last one done? */ if(map_index >= world.maps.size()) { /* Last one done? */
Log.info("Full render of '" + world.world.getName() + "' finished."); Log.info("Full render of '" + world.world.getName() + "' finished.");
synchronized(lock) { cleanup();
active_renders.remove(world.world.getName());
}
return; return;
} }
map = world.maps.get(map_index); map = world.maps.get(map_index);
@ -125,6 +161,7 @@ public class MapManager {
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile); DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
MapChunkCache cache = createMapChunkCache(w, requiredChunks); MapChunkCache cache = createMapChunkCache(w, requiredChunks);
if(cache == null) { if(cache == null) {
cleanup();
return; /* Cancelled/aborted */ return; /* Cancelled/aborted */
} }
if(tile0 != null) { /* Single tile? */ if(tile0 != null) { /* Single tile? */
@ -159,6 +196,9 @@ public class MapManager {
renderpool.execute(this); renderpool.execute(this);
} }
} }
else {
cleanup();
}
} }
} }
@ -193,6 +233,8 @@ public class MapManager {
scheduler = plugin.getServer().getScheduler(); scheduler = plugin.getServer().getScheduler();
hashman = new TileHashManager(DynmapPlugin.tilesDirectory, configuration.getBoolean("enabletilehash", true));
tileQueue.start(); tileQueue.start();
for (World world : plug_in.getServer().getWorlds()) { for (World world : plug_in.getServer().getWorlds()) {
@ -308,7 +350,7 @@ public class MapManager {
public void startRendering() { public void startRendering() {
tileQueue.start(); tileQueue.start();
renderpool = new ScheduledThreadPoolExecutor(POOL_SIZE); renderpool = new DynmapScheduledThreadPoolExecutor();
} }
public void stopRendering() { public void stopRendering() {
@ -379,4 +421,65 @@ public class MapManager {
return null; 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<String>(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");
}
} }

View File

@ -36,4 +36,8 @@ public abstract class MapTile {
} }
return super.equals(obj); return super.equals(obj);
} }
public String getKey() {
return world.getName() + "." + map.getName();
}
} }

View File

@ -18,4 +18,6 @@ public abstract class MapType {
public void buildClientConfiguration(JSONObject worldObject) { public void buildClientConfiguration(JSONObject worldObject) {
} }
public abstract String getName();
} }

View File

@ -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<TileHashFile, byte[]> tilehash = new LinkedHashMap<TileHashFile, byte[]>(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 */
}
}
}

View File

@ -20,6 +20,7 @@ import org.dynmap.ColorScheme;
import org.dynmap.ConfigurationNode; import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapChunk; import org.dynmap.DynmapChunk;
import org.dynmap.MapManager; import org.dynmap.MapManager;
import org.dynmap.TileHashManager;
import org.dynmap.MapTile; import org.dynmap.MapTile;
import org.dynmap.MapType; import org.dynmap.MapType;
import org.dynmap.debug.Debug; import org.dynmap.debug.Debug;
@ -234,36 +235,64 @@ public class FlatMap extends MapType {
rendered = true; rendered = true;
} }
} }
/* Wrap buffer as buffered image */ /* Test to see if we're unchanged from older tile */
Debug.debug("saving image " + outputFile.getPath()); TileHashManager hashman = MapManager.mapman.hashman;
try { long crc = hashman.calculateTileHash(argb_buf);
ImageIO.write(im.buf_img, "png", outputFile); boolean tile_update = false;
} catch (IOException e) { if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.x, t.y))) {
Debug.error("Failed to save image: " + outputFile.getPath(), e); /* Wrap buffer as buffered image */
} catch (java.lang.NullPointerException e) { Debug.debug("saving image " + outputFile.getPath());
Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e); 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); 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 day too, handle it */
if(night_and_day) { if(night_and_day) {
File dayfile = new File(outputFile.getParent(), tile.getDayFilename()); File dayfile = new File(outputFile.getParent(), tile.getDayFilename());
Debug.debug("saving image " + dayfile.getPath());
try { crc = hashman.calculateTileHash(argb_buf_day);
ImageIO.write(im_day.buf_img, "png", dayfile); if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) {
} catch (IOException e) { Debug.debug("saving image " + dayfile.getPath());
Debug.error("Failed to save image: " + dayfile.getPath(), e); try {
} catch (java.lang.NullPointerException e) { ImageIO.write(im_day.buf_img, "png", dayfile);
Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e); } 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); 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; return rendered;
} }
public String getName() {
return prefix;
}
public static class FlatMapTile extends MapTile { public static class FlatMapTile extends MapTile {
FlatMap map; FlatMap map;
public int x; public int x;
@ -286,6 +315,9 @@ public class FlatMap extends MapType {
public String getDayFilename() { public String getDayFilename() {
return map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png"; return map.prefix + "_day_" + size + "_" + -(y+1) + "_" + x + ".png";
} }
public String toString() {
return getWorld().getName() + ":" + getFilename();
}
} }
@Override @Override

View File

@ -16,6 +16,7 @@ import org.dynmap.Color;
import org.dynmap.ColorScheme; import org.dynmap.ColorScheme;
import org.dynmap.ConfigurationNode; import org.dynmap.ConfigurationNode;
import org.dynmap.MapManager; import org.dynmap.MapManager;
import org.dynmap.TileHashManager;
import org.dynmap.debug.Debug; import org.dynmap.debug.Debug;
import org.dynmap.MapChunkCache; import org.dynmap.MapChunkCache;
import org.dynmap.kzedmap.KzedMap.KzedBufferedImage; import org.dynmap.kzedmap.KzedMap.KzedBufferedImage;
@ -188,7 +189,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
(KzedMap) tile.getMap(), tile); (KzedMap) tile.getMap(), tile);
File zoomFile = MapManager.mapman.getTileFile(zmtile); 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; return !isempty;
} }
@ -220,49 +221,83 @@ public class DefaultTileRenderer implements MapTileRenderer {
private void doFileWrites(final File fname, final KzedMapTile mtile, private void doFileWrites(final File fname, final KzedMapTile mtile,
final KzedBufferedImage img, final KzedBufferedImage img_day, final KzedBufferedImage img, final KzedBufferedImage img_day,
final KzedZoomedMapTile zmtile, final File zoomFile, final KzedZoomedMapTile zmtile, final File zoomFile,
final KzedBufferedImage zimg, final KzedBufferedImage zimg_day) { final KzedBufferedImage zimg, final KzedBufferedImage zimg_day, boolean rendered) {
Debug.debug("saving image " + fname.getPath());
try { /* Get coordinates of zoomed tile */
ImageIO.write(img.buf_img, "png", fname); int ox = (mtile.px == zmtile.getTileX())?0:KzedMap.tileWidth/2;
} catch (IOException e) { int oy = (mtile.py == zmtile.getTileY())?0:KzedMap.tileHeight/2;
Debug.error("Failed to save image: " + fname.getPath(), e);
} catch (java.lang.NullPointerException e) { /* Test to see if we're unchanged from older tile */
Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e); 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); 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) { if(img_day != null) {
File dfname = new File(fname.getParent(), mtile.getDayFilename()); crc = hashman.calculateTileHash(img.argb_buf);
Debug.debug("saving image " + dfname.getPath()); if((!dfname.exists()) || (crc != hashman.getImageHashCode(mtile.getKey(), "day", mtile.px, mtile.py))) {
try { Debug.debug("saving image " + dfname.getPath());
ImageIO.write(img_day.buf_img, "png", dfname); try {
} catch (IOException e) { ImageIO.write(img_day.buf_img, "png", dfname);
Debug.error("Failed to save image: " + dfname.getPath(), e); } catch (IOException e) {
} catch (java.lang.NullPointerException e) { Debug.error("Failed to save image: " + dfname.getPath(), e);
Debug.error("Failed to save image (NullPointerException): " + 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); 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 // Since we've already got the new tile, and we're on an async thread, just
// make the zoomed tile here // make the zoomed tile here
int px = mtile.px; boolean ztile_updated = false;
int py = mtile.py; if(updated_fname || (!zoomFile.exists())) {
int zpx = zmtile.getTileX(); saveZoomedTile(zmtile, zoomFile, zimg, ox, oy);
int zpy = zmtile.getTileY(); 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);
/* scaled size */ if(zimg_day != null) {
int scw = KzedMap.tileWidth / 2; File zoomFile_day = new File(zoomFile.getParent(), zmtile.getDayFilename());
int sch = KzedMap.tileHeight / 2; ztile_updated = false;
if(updated_dfname || (!zoomFile_day.exists())) {
/* origin in zoomed-out tile */ saveZoomedTile(zmtile, zoomFile_day, zimg_day, ox, oy);
int ox = 0; MapManager.mapman.pushUpdate(zmtile.getWorld(),
int oy = 0; new Client.Tile(zmtile.getDayFilename()));
ztile_updated = true;
if (zpx != px) }
ox = scw; KzedMap.freeBufferedImage(zimg_day);
if (zpy != py) MapManager.mapman.updateStatistics(zmtile, "day", true, ztile_updated, !rendered);
oy = sch; }
}
private void saveZoomedTile(final KzedZoomedMapTile zmtile, final File zoomFile,
final KzedBufferedImage zimg, int ox, int oy) {
BufferedImage zIm = null; BufferedImage zIm = null;
KzedBufferedImage kzIm = null; KzedBufferedImage kzIm = null;
try { try {
@ -284,7 +319,6 @@ public class DefaultTileRenderer implements MapTileRenderer {
/* blit scaled rendered tile onto zoom-out tile */ /* 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); zIm.setRGB(ox, oy, KzedMap.tileWidth/2, KzedMap.tileHeight/2, zimg.argb_buf, 0, KzedMap.tileWidth/2);
KzedMap.freeBufferedImage(zimg);
/* save zoom-out tile */ /* save zoom-out tile */
@ -301,60 +335,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
else else
zIm.flush(); 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, protected void scan(World world, int seq, boolean isnether, final Color result, final Color result_day,
MapChunkCache.MapIterator mapiter) { MapChunkCache.MapIterator mapiter) {
int lightlevel = 15; int lightlevel = 15;

View File

@ -318,6 +318,10 @@ public class KzedMap extends MapType {
} }
} }
public String getName() {
return "KzedMap";
}
@Override @Override
public void buildClientConfiguration(JSONObject worldObject) { public void buildClientConfiguration(JSONObject worldObject) {
for(MapTileRenderer renderer : renderers) { for(MapTileRenderer renderer : renderers) {

View File

@ -48,6 +48,10 @@ public class KzedMapTile extends MapTile {
return o.px == px && o.py == py && o.getWorld().equals(getWorld()); return o.px == px && o.py == py && o.getWorld().equals(getWorld());
} }
public String getKey() {
return getWorld().getName() + "." + renderer.getName();
}
public String toString() { public String toString() {
return getWorld().getName() + ":" + getFilename(); return getWorld().getName() + ":" + getFilename();
} }

View File

@ -56,4 +56,10 @@ public class KzedZoomedMapTile extends MapTile {
} }
return super.equals(obj); return super.equals(obj);
} }
public String getKey() {
return getWorld().getName() + ".z" + originalTile.renderer.getName();
}
} }