mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-12-01 06:33:38 +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).
|
# 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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,4 +36,8 @@ public abstract class MapTile {
|
|||||||
}
|
}
|
||||||
return super.equals(obj);
|
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 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.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
|
||||||
|
@ -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;
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user