mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-28 13:15:30 +01:00
Add render statistics, support for tile hashcodes to stop non-updates
This commit is contained in:
parent
4b30fff8a7
commit
d393ccf6e9
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>();
|
||||
|
||||
/* 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<String, MapStats> mapstats = new HashMap<String, MapStats>();
|
||||
|
||||
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<MapTile> rendered = null;
|
||||
LinkedList<MapTile> 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<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");
|
||||
}
|
||||
}
|
||||
|
@ -36,4 +36,8 @@ public abstract class MapTile {
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
public String getKey() {
|
||||
return world.getName() + "." + map.getName();
|
||||
}
|
||||
}
|
||||
|
@ -18,4 +18,6 @@ public abstract class MapType {
|
||||
|
||||
public void buildClientConfiguration(JSONObject worldObject) {
|
||||
}
|
||||
|
||||
public abstract String getName();
|
||||
}
|
||||
|
152
src/main/java/org/dynmap/TileHashManager.java
Normal file
152
src/main/java/org/dynmap/TileHashManager.java
Normal 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 */
|
||||
}
|
||||
}
|
||||
}
|
@ -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,6 +235,11 @@ public class FlatMap extends MapType {
|
||||
rendered = true;
|
||||
}
|
||||
}
|
||||
/* 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 {
|
||||
@ -243,12 +249,22 @@ public class FlatMap extends MapType {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + outputFile.getPath(), e);
|
||||
}
|
||||
KzedMap.freeBufferedImage(im);
|
||||
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.updateStatistics(tile, null, true, tile_update, !rendered);
|
||||
|
||||
/* If day too, handle it */
|
||||
if(night_and_day) {
|
||||
File dayfile = new File(outputFile.getParent(), tile.getDayFilename());
|
||||
|
||||
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);
|
||||
@ -257,13 +273,26 @@ public class FlatMap extends MapType {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + dayfile.getPath(), e);
|
||||
}
|
||||
KzedMap.freeBufferedImage(im_day);
|
||||
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.updateStatistics(tile, "day", true, tile_update, !rendered);
|
||||
}
|
||||
|
||||
return rendered;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
|
||||
public static class FlatMapTile extends MapTile {
|
||||
FlatMap map;
|
||||
public int x;
|
||||
@ -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
|
||||
|
@ -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,7 +221,17 @@ 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) {
|
||||
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);
|
||||
@ -229,9 +240,20 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
} 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);
|
||||
if(img_day != null) {
|
||||
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) {
|
||||
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);
|
||||
@ -240,29 +262,42 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + dfname.getPath(), e);
|
||||
}
|
||||
KzedMap.freeBufferedImage(img_day);
|
||||
MapManager.mapman.pushUpdate(mtile.getWorld(), new Client.Tile(mtile.getDayFilename()));
|
||||
hashman.updateHashCode(mtile.getKey(), "day", mtile.px, mtile.py, crc);
|
||||
updated_dfname = true;
|
||||
}
|
||||
mtile.file = fname;
|
||||
KzedMap.freeBufferedImage(img_day);
|
||||
MapManager.mapman.updateStatistics(mtile, "day", true, updated_dfname, !rendered);
|
||||
}
|
||||
|
||||
// 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();
|
||||
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);
|
||||
|
||||
/* 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;
|
||||
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 */
|
||||
|
||||
@ -301,60 +335,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
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;
|
||||
|
@ -318,6 +318,10 @@ public class KzedMap extends MapType {
|
||||
}
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return "KzedMap";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void buildClientConfiguration(JSONObject worldObject) {
|
||||
for(MapTileRenderer renderer : renderers) {
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -56,4 +56,10 @@ public class KzedZoomedMapTile extends MapTile {
|
||||
}
|
||||
return super.equals(obj);
|
||||
}
|
||||
|
||||
|
||||
public String getKey() {
|
||||
return getWorld().getName() + ".z" + originalTile.renderer.getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user