mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-28 13:15:30 +01:00
Merge pull request #156 from mikeprimm/master
Implement chunk snapshotting approach to drop memory use and CPU use significantly, add shadows option (via shadowstrength) on defaulttilerender, add fix for nicknamed players' skins
This commit is contained in:
commit
cb010802db
@ -59,6 +59,8 @@ worlds:
|
||||
prefix: t
|
||||
maximumheight: 127
|
||||
colorscheme: default
|
||||
# Add shadows to world (based on top-down shadows from chunk data)
|
||||
# shadowstrength: 1.0
|
||||
#- class: org.dynmap.kzedmap.HighlightTileRenderer
|
||||
# prefix: ht
|
||||
# maximumheight: 127
|
||||
|
106
src/main/java/org/dynmap/CraftChunkSnapshot.java
Normal file
106
src/main/java/org/dynmap/CraftChunkSnapshot.java
Normal file
@ -0,0 +1,106 @@
|
||||
package org.dynmap;
|
||||
|
||||
/**
|
||||
* Represents a static, thread-safe snapshot of chunk of blocks
|
||||
* Purpose is to allow clean, efficient copy of a chunk data to be made, and then handed off for processing in another thread (e.g. map rendering)
|
||||
*/
|
||||
public class CraftChunkSnapshot {
|
||||
private final int x, z;
|
||||
private final byte[] buf; /* Flat buffer in uncompressed chunk file format */
|
||||
|
||||
private static final int BLOCKDATA_OFF = 32768;
|
||||
private static final int BLOCKLIGHT_OFF = BLOCKDATA_OFF + 16384;
|
||||
private static final int SKYLIGHT_OFF = BLOCKLIGHT_OFF + 16384;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
CraftChunkSnapshot(int x, int z, byte[] buf) {
|
||||
this.x = x;
|
||||
this.z = z;
|
||||
this.buf = buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the X-coordinate of this chunk
|
||||
*
|
||||
* @return X-coordinate
|
||||
*/
|
||||
public int getX() {
|
||||
return x;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Z-coordinate of this chunk
|
||||
*
|
||||
* @return Z-coordinate
|
||||
*/
|
||||
public int getZ() {
|
||||
return z;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block type for block at corresponding coordinate in the chunk
|
||||
*
|
||||
* @param x 0-15
|
||||
* @param y 0-127
|
||||
* @param z 0-15
|
||||
* @return 0-255
|
||||
*/
|
||||
public int getBlockTypeId(int x, int y, int z) {
|
||||
return buf[x << 11 | z << 7 | y] & 255;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get block data for block at corresponding coordinate in the chunk
|
||||
*
|
||||
* @param x 0-15
|
||||
* @param y 0-127
|
||||
* @param z 0-15
|
||||
* @return 0-15
|
||||
*/
|
||||
public int getBlockData(int x, int y, int z) {
|
||||
int off = ((x << 10) | (z << 6) | (y >> 1)) + BLOCKDATA_OFF;
|
||||
|
||||
return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get sky light level for block at corresponding coordinate in the chunk
|
||||
*
|
||||
* @param x 0-15
|
||||
* @param y 0-127
|
||||
* @param z 0-15
|
||||
* @return 0-15
|
||||
*/
|
||||
public int getBlockSkyLight(int x, int y, int z) {
|
||||
int off = ((x << 10) | (z << 6) | (y >> 1)) + SKYLIGHT_OFF;
|
||||
|
||||
return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get light level emitted by block at corresponding coordinate in the chunk
|
||||
*
|
||||
* @param x 0-15
|
||||
* @param y 0-127
|
||||
* @param z 0-15
|
||||
* @return 0-15
|
||||
*/
|
||||
public int getBlockEmittedLight(int x, int y, int z) {
|
||||
int off = ((x << 10) | (z << 6) | (y >> 1)) + BLOCKLIGHT_OFF;
|
||||
|
||||
return ((y & 1) == 0) ? (buf[off] & 0xF) : ((buf[off] >> 4) & 0xF);
|
||||
}
|
||||
|
||||
public int getHighestBlockYAt(int x, int z) {
|
||||
int off = x << 11 | z << 7 | 126;
|
||||
int i;
|
||||
for(i = 127; (i >= 2); i--, off--) {
|
||||
if(buf[off] != 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
213
src/main/java/org/dynmap/MapChunkCache.java
Normal file
213
src/main/java/org/dynmap/MapChunkCache.java
Normal file
@ -0,0 +1,213 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedList;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.Chunk;
|
||||
import org.bukkit.entity.Entity;
|
||||
|
||||
/**
|
||||
* Container for managing chunks, as well as abstracting the different methods we may
|
||||
* handle chunk data (existing chunk loading, versus upcoming chunk snapshots)
|
||||
*
|
||||
*/
|
||||
public class MapChunkCache {
|
||||
private World w;
|
||||
private static Method getchunkdata = null;
|
||||
private static Method gethandle = null;
|
||||
private static boolean initialized = false;
|
||||
|
||||
private int x_min, x_max, z_min, z_max;
|
||||
private int x_dim;
|
||||
|
||||
private CraftChunkSnapshot[] snaparray; /* Index = (x-x_min) + ((z-z_min)*x_dim) */
|
||||
private LinkedList<DynmapChunk> loadedChunks = new LinkedList<DynmapChunk>();
|
||||
|
||||
/**
|
||||
* Create chunk cache container
|
||||
* @param w - world
|
||||
* @param x_min - minimum chunk x coordinate
|
||||
* @param z_min - minimum chunk z coordinate
|
||||
* @param x_max - maximum chunk x coordinate
|
||||
* @param z_max - maximum chunk z coordinate
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
public MapChunkCache(World w, DynmapChunk[] chunks) {
|
||||
/* Compute range */
|
||||
if(chunks.length == 0) {
|
||||
this.x_min = 0;
|
||||
this.x_max = 0;
|
||||
this.z_min = 0;
|
||||
this.z_max = 0;
|
||||
x_dim = 1;
|
||||
}
|
||||
else {
|
||||
x_min = x_max = chunks[0].x;
|
||||
z_min = z_max = chunks[0].z;
|
||||
for(int i = 1; i < chunks.length; i++) {
|
||||
if(chunks[i].x > x_max)
|
||||
x_max = chunks[i].x;
|
||||
if(chunks[i].x < x_min)
|
||||
x_min = chunks[i].x;
|
||||
if(chunks[i].z > z_max)
|
||||
z_max = chunks[i].z;
|
||||
if(chunks[i].z < z_min)
|
||||
z_min = chunks[i].z;
|
||||
}
|
||||
x_dim = x_max - x_min + 1;
|
||||
}
|
||||
this.w = w;
|
||||
|
||||
if(!initialized) {
|
||||
try {
|
||||
Class c = Class.forName("net.minecraft.server.Chunk");
|
||||
getchunkdata = c.getDeclaredMethod("a", new Class[] { byte[].class, int.class,
|
||||
int.class, int.class, int.class, int.class, int.class, int.class });
|
||||
c = Class.forName("org.bukkit.craftbukkit.CraftChunk");
|
||||
gethandle = c.getDeclaredMethod("getHandle", new Class[0]);
|
||||
} catch (ClassNotFoundException cnfx) {
|
||||
} catch (NoSuchMethodException nsmx) {
|
||||
}
|
||||
initialized = true;
|
||||
if(gethandle != null)
|
||||
Log.info("Chunk snapshot support enabled");
|
||||
else
|
||||
Log.info("Chunk snapshot support disabled");
|
||||
}
|
||||
if(gethandle != null) { /* We can use caching */
|
||||
snaparray = new CraftChunkSnapshot[x_dim * (z_max-z_min+1)];
|
||||
}
|
||||
if(snaparray != null) {
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : chunks) {
|
||||
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
|
||||
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
|
||||
/* If it did load, make cache of it */
|
||||
if(didload) {
|
||||
Chunk c = w.getChunkAt(chunk.x, chunk.z);
|
||||
try {
|
||||
Object cc = gethandle.invoke(c);
|
||||
byte[] buf = new byte[32768 + 16384 + 16384 + 16384]; /* Get big enough buffer for whole chunk */
|
||||
getchunkdata.invoke(cc, buf, 0, 0, 0, 16, 128, 16, 0);
|
||||
CraftChunkSnapshot ss = new CraftChunkSnapshot(chunk.x, chunk.z, buf);
|
||||
snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = ss;
|
||||
} catch (Exception x) {
|
||||
}
|
||||
}
|
||||
if ((!wasLoaded) && didload) {
|
||||
/* It looks like bukkit "leaks" entities - they don't get removed from the world-level table
|
||||
* when chunks are unloaded but not saved - removing them seems to do the trick */
|
||||
Chunk cc = w.getChunkAt(chunk.x, chunk.z);
|
||||
if(cc != null) {
|
||||
for(Entity e: cc.getEntities())
|
||||
e.remove();
|
||||
}
|
||||
/* Since we only remember ones we loaded, and we're synchronous, no player has
|
||||
* moved, so it must be safe (also prevent chunk leak, which appears to happen
|
||||
* because isChunkInUse defined "in use" as being within 256 blocks of a player,
|
||||
* while the actual in-use chunk area for a player where the chunks are managed
|
||||
* by the MC base server is 21x21 (or about a 160 block radius) */
|
||||
w.unloadChunk(chunk.x, chunk.z, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
else { /* Else, load and keep them loaded for now */
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : chunks) {
|
||||
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
|
||||
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
|
||||
if ((!wasLoaded) && didload)
|
||||
loadedChunks.add(chunk);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Unload chunks
|
||||
*/
|
||||
public void unloadChunks() {
|
||||
if(snaparray != null) {
|
||||
for(int i = 0; i < snaparray.length; i++) {
|
||||
snaparray[i] = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
while (!loadedChunks.isEmpty()) {
|
||||
DynmapChunk c = loadedChunks.pollFirst();
|
||||
/* It looks like bukkit "leaks" entities - they don't get removed from the world-level table
|
||||
* when chunks are unloaded but not saved - removing them seems to do the trick */
|
||||
Chunk cc = w.getChunkAt(c.x, c.z);
|
||||
if(cc != null) {
|
||||
for(Entity e: cc.getEntities())
|
||||
e.remove();
|
||||
}
|
||||
/* Since we only remember ones we loaded, and we're synchronous, no player has
|
||||
* moved, so it must be safe (also prevent chunk leak, which appears to happen
|
||||
* because isChunkInUse defined "in use" as being within 256 blocks of a player,
|
||||
* while the actual in-use chunk area for a player where the chunks are managed
|
||||
* by the MC base server is 21x21 (or about a 160 block radius) */
|
||||
w.unloadChunk(c.x, c.z, false, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get block ID at coordinates
|
||||
*/
|
||||
public int getBlockTypeID(int x, int y, int z) {
|
||||
if(snaparray != null) {
|
||||
CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim];
|
||||
if(ss == null)
|
||||
return 0;
|
||||
else
|
||||
return ss.getBlockTypeId(x & 0xF, y, z & 0xF);
|
||||
}
|
||||
else {
|
||||
return w.getBlockTypeIdAt(x, y, z);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get block data at coordiates
|
||||
*/
|
||||
public byte getBlockData(int x, int y, int z) {
|
||||
if(snaparray != null) {
|
||||
CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim];
|
||||
if(ss == null)
|
||||
return 0;
|
||||
else
|
||||
return (byte)ss.getBlockData(x & 0xF, y, z & 0xF);
|
||||
}
|
||||
else {
|
||||
return w.getBlockAt(x, y, z).getData();
|
||||
}
|
||||
}
|
||||
/* Get highest block Y
|
||||
*
|
||||
*/
|
||||
public int getHighestBlockYAt(int x, int z) {
|
||||
if(snaparray != null) {
|
||||
CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim];
|
||||
if(ss == null) {
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
return ss.getHighestBlockYAt(x & 0xF, z & 0xF);
|
||||
}
|
||||
else {
|
||||
return w.getHighestBlockYAt(x, z);
|
||||
}
|
||||
}
|
||||
/* Get sky light level
|
||||
*/
|
||||
public int getBlockSkyLight(int x, int y, int z) {
|
||||
if(snaparray != null) {
|
||||
CraftChunkSnapshot ss = snaparray[((x>>4) - x_min) + ((z>>4) - z_min) * x_dim];
|
||||
if(ss == null) {
|
||||
return 15;
|
||||
}
|
||||
else
|
||||
return ss.getBlockSkyLight(x & 0xF, y, z & 0xF);
|
||||
}
|
||||
else {
|
||||
return 15;
|
||||
}
|
||||
}
|
||||
}
|
@ -21,9 +21,7 @@ public class MapManager {
|
||||
public Map<String, DynmapWorld> inactiveworlds = new HashMap<String, DynmapWorld>();
|
||||
private BukkitScheduler scheduler;
|
||||
private DynmapPlugin plug_in;
|
||||
private boolean do_timesliced_render = false;
|
||||
private double timeslice_interval = 0.0;
|
||||
private boolean do_sync_render = false; /* Do incremental renders on sync thread too */
|
||||
/* Which timesliced renders are active */
|
||||
private HashMap<String, FullWorldRenderState> active_renders = new HashMap<String, FullWorldRenderState>();
|
||||
|
||||
@ -96,22 +94,14 @@ public class MapManager {
|
||||
else { /* Else, single tile render */
|
||||
tile = tile0;
|
||||
}
|
||||
|
||||
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
|
||||
LinkedList<DynmapChunk> loadedChunks = new LinkedList<DynmapChunk>();
|
||||
MapChunkCache cache = new MapChunkCache(world.world, requiredChunks);
|
||||
World w = world.world;
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : requiredChunks) {
|
||||
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
|
||||
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
|
||||
if ((!wasLoaded) && didload)
|
||||
loadedChunks.add(chunk);
|
||||
}
|
||||
if(tile0 != null) { /* Single tile? */
|
||||
render(tile); /* Just render */
|
||||
render(cache, tile); /* Just render */
|
||||
}
|
||||
else {
|
||||
if (render(tile)) {
|
||||
if (render(cache, tile)) {
|
||||
found.remove(tile);
|
||||
rendered.add(tile);
|
||||
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
|
||||
@ -129,22 +119,7 @@ public class MapManager {
|
||||
}
|
||||
}
|
||||
/* And unload what we loaded */
|
||||
while (!loadedChunks.isEmpty()) {
|
||||
DynmapChunk c = loadedChunks.pollFirst();
|
||||
/* It looks like bukkit "leaks" entities - they don't get removed from the world-level table
|
||||
* when chunks are unloaded but not saved - removing them seems to do the trick */
|
||||
Chunk cc = w.getChunkAt(c.x, c.z);
|
||||
if(cc != null) {
|
||||
for(Entity e: cc.getEntities())
|
||||
e.remove();
|
||||
}
|
||||
/* Since we only remember ones we loaded, and we're synchronous, no player has
|
||||
* moved, so it must be safe (also prevent chunk leak, which appears to happen
|
||||
* because isChunkInUse defined "in use" as being within 256 blocks of a player,
|
||||
* while the actual in-use chunk area for a player where the chunks are managed
|
||||
* by the MC base server is 21x21 (or about a 160 block radius) */
|
||||
w.unloadChunk(c.x, c.z, false, false);
|
||||
}
|
||||
cache.unloadChunks();
|
||||
if(tile0 == null) { /* fullrender */
|
||||
/* Schedule the next tile to be worked */
|
||||
scheduler.scheduleSyncDelayedTask(plug_in, this, (int)(timeslice_interval*20));
|
||||
@ -159,11 +134,8 @@ public class MapManager {
|
||||
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
|
||||
@Override
|
||||
public void handle(MapTile t) {
|
||||
if(do_sync_render)
|
||||
scheduler.scheduleSyncDelayedTask(plug_in,
|
||||
new FullWorldRenderState(t), 1);
|
||||
else
|
||||
render(t);
|
||||
}
|
||||
}, (int) (configuration.getDouble("renderinterval", 0.5) * 1000));
|
||||
|
||||
@ -175,9 +147,7 @@ public class MapManager {
|
||||
}
|
||||
}, 10);
|
||||
|
||||
do_timesliced_render = configuration.getBoolean("timeslicerender", true);
|
||||
timeslice_interval = configuration.getDouble("timesliceinterval", 0.5);
|
||||
do_sync_render = configuration.getBoolean("renderonsync", true);
|
||||
|
||||
for(ConfigurationNode worldConfiguration : configuration.getNodes("worlds")) {
|
||||
String worldName = worldConfiguration.getString("name");
|
||||
@ -219,7 +189,6 @@ public class MapManager {
|
||||
Log.severe("Could not render: world '" + l.getWorld().getName() + "' not defined in configuration.");
|
||||
return;
|
||||
}
|
||||
if(do_timesliced_render) {
|
||||
String wname = l.getWorld().getName();
|
||||
FullWorldRenderState rndr = active_renders.get(wname);
|
||||
if(rndr != null) {
|
||||
@ -231,66 +200,6 @@ public class MapManager {
|
||||
/* Schedule first tile to be worked */
|
||||
scheduler.scheduleSyncDelayedTask(plug_in, rndr, (int)(timeslice_interval*20));
|
||||
Log.info("Full render starting on world '" + wname + "' (timesliced)...");
|
||||
|
||||
return;
|
||||
}
|
||||
World w = world.world;
|
||||
|
||||
Log.info("Full render starting on world '" + w.getName() + "'...");
|
||||
for (MapType map : world.maps) {
|
||||
int requiredChunkCount = 200;
|
||||
HashSet<MapTile> found = new HashSet<MapTile>();
|
||||
HashSet<MapTile> rendered = new HashSet<MapTile>();
|
||||
LinkedList<MapTile> renderQueue = new LinkedList<MapTile>();
|
||||
LinkedList<DynmapChunk> loadedChunks = new LinkedList<DynmapChunk>();
|
||||
|
||||
for (MapTile tile : map.getTiles(l)) {
|
||||
if (!found.contains(tile)) {
|
||||
found.add(tile);
|
||||
renderQueue.add(tile);
|
||||
}
|
||||
}
|
||||
while (!renderQueue.isEmpty()) {
|
||||
MapTile tile = renderQueue.pollFirst();
|
||||
|
||||
DynmapChunk[] requiredChunks = tile.getMap().getRequiredChunks(tile);
|
||||
|
||||
if (requiredChunks.length > requiredChunkCount)
|
||||
requiredChunkCount = requiredChunks.length;
|
||||
// Unload old chunks.
|
||||
while (loadedChunks.size() >= requiredChunkCount - requiredChunks.length) {
|
||||
DynmapChunk c = loadedChunks.pollFirst();
|
||||
w.unloadChunk(c.x, c.z, false, true);
|
||||
}
|
||||
|
||||
// Load the required chunks.
|
||||
for (DynmapChunk chunk : requiredChunks) {
|
||||
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
|
||||
w.loadChunk(chunk.x, chunk.z, false);
|
||||
if (!wasLoaded)
|
||||
loadedChunks.add(chunk);
|
||||
}
|
||||
|
||||
if (render(tile)) {
|
||||
found.remove(tile);
|
||||
rendered.add(tile);
|
||||
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
|
||||
if (!found.contains(adjTile) && !rendered.contains(adjTile)) {
|
||||
found.add(adjTile);
|
||||
renderQueue.add(adjTile);
|
||||
}
|
||||
}
|
||||
}
|
||||
found.remove(tile);
|
||||
}
|
||||
|
||||
// Unload remaining chunks to clean-up.
|
||||
while (!loadedChunks.isEmpty()) {
|
||||
DynmapChunk c = loadedChunks.pollFirst();
|
||||
w.unloadChunk(c.x, c.z, false, true);
|
||||
}
|
||||
}
|
||||
Log.info("Full render finished.");
|
||||
}
|
||||
|
||||
public void activateWorld(World w) {
|
||||
@ -337,8 +246,8 @@ public class MapManager {
|
||||
writeQueue.stop();
|
||||
}
|
||||
|
||||
public boolean render(MapTile tile) {
|
||||
boolean result = tile.getMap().render(tile, getTileFile(tile));
|
||||
public boolean render(MapChunkCache cache, MapTile tile) {
|
||||
boolean result = tile.getMap().render(cache, tile, getTileFile(tile));
|
||||
//Do update after async file write
|
||||
|
||||
return result;
|
||||
@ -387,6 +296,6 @@ public class MapManager {
|
||||
}
|
||||
|
||||
public boolean doSyncRender() {
|
||||
return do_sync_render;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -13,5 +13,5 @@ public abstract class MapType {
|
||||
|
||||
public abstract DynmapChunk[] getRequiredChunks(MapTile tile);
|
||||
|
||||
public abstract boolean render(MapTile tile, File outputFile);
|
||||
public abstract boolean render(MapChunkCache cache, MapTile tile, File outputFile);
|
||||
}
|
||||
|
@ -19,6 +19,8 @@ import org.dynmap.MapManager;
|
||||
import org.dynmap.MapTile;
|
||||
import org.dynmap.MapType;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.kzedmap.KzedMap;
|
||||
import org.dynmap.MapChunkCache;
|
||||
|
||||
public class FlatMap extends MapType {
|
||||
private String prefix;
|
||||
@ -73,13 +75,13 @@ public class FlatMap extends MapType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean render(MapTile tile, File outputFile) {
|
||||
public boolean render(MapChunkCache cache, MapTile tile, File outputFile) {
|
||||
FlatMapTile t = (FlatMapTile) tile;
|
||||
World w = t.getWorld();
|
||||
boolean isnether = (w.getEnvironment() == Environment.NETHER) && (maximumHeight == 127);
|
||||
|
||||
boolean rendered = false;
|
||||
BufferedImage im = new BufferedImage(t.size, t.size, BufferedImage.TYPE_INT_RGB);
|
||||
BufferedImage im = KzedMap.allocateBufferedImage(t.size, t.size);
|
||||
WritableRaster raster = im.getRaster();
|
||||
|
||||
int[] pixel = new int[4];
|
||||
@ -93,16 +95,16 @@ public class FlatMap extends MapType {
|
||||
if(isnether) {
|
||||
/* Scan until we hit air */
|
||||
my = 127;
|
||||
while((blockType = w.getBlockTypeIdAt(mx, my, mz)) != 0) {
|
||||
while((blockType = cache.getBlockTypeID(mx, my, mz)) != 0) {
|
||||
my--;
|
||||
if(my < 0) { /* Solid - use top */
|
||||
my = 127;
|
||||
blockType = w.getBlockTypeIdAt(mx, my, mz);
|
||||
blockType = cache.getBlockTypeID(mx, my, mz);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(blockType == 0) { /* Hit air - now find non-air */
|
||||
while((blockType = w.getBlockTypeIdAt(mx, my, mz)) == 0) {
|
||||
while((blockType = cache.getBlockTypeID(mx, my, mz)) == 0) {
|
||||
my--;
|
||||
if(my < 0) {
|
||||
my = 0;
|
||||
@ -112,14 +114,14 @@ public class FlatMap extends MapType {
|
||||
}
|
||||
}
|
||||
else {
|
||||
my = w.getHighestBlockYAt(mx, mz) - 1;
|
||||
my = cache.getHighestBlockYAt(mx, mz) - 1;
|
||||
if(my > maximumHeight) my = maximumHeight;
|
||||
blockType = w.getBlockTypeIdAt(mx, my, mz);
|
||||
blockType = cache.getBlockTypeID(mx, my, mz);
|
||||
}
|
||||
byte data = 0;
|
||||
Color[] colors = colorScheme.colors[blockType];
|
||||
if(colorScheme.datacolors[blockType] != null) {
|
||||
data = w.getBlockAt(mx, my, mz).getData();
|
||||
data = cache.getBlockData(mx, my, mz);
|
||||
colors = colorScheme.datacolors[blockType][data];
|
||||
}
|
||||
if (colors == null)
|
||||
@ -176,7 +178,7 @@ public class FlatMap extends MapType {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e);
|
||||
}
|
||||
img.flush();
|
||||
KzedMap.freeBufferedImage(img);
|
||||
MapManager.mapman.pushUpdate(mtile.getWorld(),
|
||||
new Client.Tile(mtile.getFilename()));
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.MapChunkCache;
|
||||
import org.dynmap.Color;
|
||||
import org.dynmap.ConfigurationNode;
|
||||
|
||||
@ -11,14 +12,15 @@ public class CaveTileRenderer extends DefaultTileRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) {
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result,
|
||||
MapChunkCache cache) {
|
||||
boolean air = true;
|
||||
result.setTransparent();
|
||||
for (;;) {
|
||||
if (y < 0)
|
||||
return;
|
||||
|
||||
int id = world.getBlockTypeIdAt(x, y, z);
|
||||
int id = cache.getBlockTypeID(x, y, z);
|
||||
if(isnether) { /* Make ceiling into air in nether */
|
||||
if(id != 0)
|
||||
id = 0;
|
||||
|
@ -1,7 +1,5 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.awt.image.WritableRaster;
|
||||
import java.io.File;
|
||||
@ -18,6 +16,7 @@ import org.dynmap.ColorScheme;
|
||||
import org.dynmap.ConfigurationNode;
|
||||
import org.dynmap.MapManager;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.MapChunkCache;
|
||||
|
||||
public class DefaultTileRenderer implements MapTileRenderer {
|
||||
protected static final Color translucent = new Color(0, 0, 0, 0);
|
||||
@ -28,6 +27,7 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
protected HashSet<Integer> highlightBlocks = new HashSet<Integer>();
|
||||
protected Color highlightColor = new Color(255, 0, 0);
|
||||
|
||||
protected int shadowscale[]; /* index=skylight level, value = 256 * scaling value */
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
@ -41,15 +41,29 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
if (maximumHeight > 127)
|
||||
maximumHeight = 127;
|
||||
}
|
||||
o = configuration.get("shadowstrength");
|
||||
if(o != null) {
|
||||
double shadowweight = Double.parseDouble(String.valueOf(o));
|
||||
if(shadowweight > 0.0) {
|
||||
shadowscale = new int[16];
|
||||
for(int i = 0; i < 16; i++) {
|
||||
double v = 256.0 * (1.0 - (shadowweight * (15-i) / 15.0));
|
||||
shadowscale[i] = (int)v;
|
||||
if(shadowscale[i] > 256) shadowscale[i] = 256;
|
||||
if(shadowscale[i] < 0) shadowscale[i] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
colorScheme = ColorScheme.getScheme((String)configuration.get("colorscheme"));
|
||||
}
|
||||
|
||||
public boolean render(KzedMapTile tile, File outputFile) {
|
||||
public boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile) {
|
||||
World world = tile.getWorld();
|
||||
boolean isnether = (world.getEnvironment() == Environment.NETHER);
|
||||
BufferedImage im = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB);
|
||||
|
||||
BufferedImage im = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
|
||||
BufferedImage zim = KzedMap.allocateBufferedImage(KzedMap.tileWidth/2, KzedMap.tileHeight/2);
|
||||
WritableRaster r = im.getRaster();
|
||||
WritableRaster zr = zim.getRaster();
|
||||
boolean isempty = true;
|
||||
|
||||
int ix = KzedMap.anchorx + tile.px / 2 + tile.py / 2 - ((127-maximumHeight)/2);
|
||||
@ -66,30 +80,37 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
|
||||
Color c1 = new Color();
|
||||
Color c2 = new Color();
|
||||
int[] rgb = new int[3];
|
||||
int[] rgb = new int[3*KzedMap.tileWidth];
|
||||
int[] zrgb = new int[3*KzedMap.tileWidth/2];
|
||||
/* draw the map */
|
||||
for (y = 0; y < KzedMap.tileHeight;) {
|
||||
jx = ix;
|
||||
jz = iz;
|
||||
|
||||
for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
|
||||
scan(world, jx, iy, jz, 0, isnether, c1);
|
||||
scan(world, jx, iy, jz, 2, isnether, c2);
|
||||
if(c1.isTransparent() == false) {
|
||||
rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue();
|
||||
r.setPixel(x, y, rgb);
|
||||
isempty = false;
|
||||
}
|
||||
if(c2.isTransparent() == false) {
|
||||
rgb[0] = c2.getRed(); rgb[1] = c2.getGreen(); rgb[2] = c2.getBlue();
|
||||
r.setPixel(x - 1, y, rgb);
|
||||
isempty = false;
|
||||
}
|
||||
scan(world, jx, iy, jz, 0, isnether, c1, cache);
|
||||
scan(world, jx, iy, jz, 2, isnether, c2, cache);
|
||||
|
||||
rgb[3*x] = c1.getRed();
|
||||
rgb[3*x+1] = c1.getGreen();
|
||||
rgb[3*x+2] = c1.getBlue();
|
||||
rgb[3*x-3] = c2.getRed();
|
||||
rgb[3*x-2] = c2.getGreen();
|
||||
rgb[3*x-1] = c2.getBlue();
|
||||
|
||||
isempty = isempty && c1.isTransparent() && c2.isTransparent();
|
||||
|
||||
jx++;
|
||||
jz++;
|
||||
|
||||
}
|
||||
r.setPixels(0, y, KzedMap.tileWidth, 1, rgb);
|
||||
/* Sum up zoomed pixels - bilinar filter */
|
||||
for(x = 0; x < KzedMap.tileWidth / 2; x++) {
|
||||
zrgb[3*x] = rgb[6*x] + rgb[6*x+3];
|
||||
zrgb[3*x+1] = rgb[6*x+1] + rgb[6*x+4];
|
||||
zrgb[3*x+2] = rgb[6*x+2] + rgb[6*x+5];
|
||||
}
|
||||
|
||||
y++;
|
||||
|
||||
@ -97,22 +118,28 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
jz = iz - 1;
|
||||
|
||||
for (x = KzedMap.tileWidth - 1; x >= 0; x -= 2) {
|
||||
scan(world, jx, iy, jz, 2, isnether, c1);
|
||||
scan(world, jx, iy, jz, 2, isnether, c1, cache);
|
||||
jx++;
|
||||
jz++;
|
||||
scan(world, jx, iy, jz, 0, isnether, c2);
|
||||
if(c1.isTransparent() == false) {
|
||||
rgb[0] = c1.getRed(); rgb[1] = c1.getGreen(); rgb[2] = c1.getBlue();
|
||||
r.setPixel(x, y, rgb);
|
||||
isempty = false;
|
||||
}
|
||||
if(c2.isTransparent() == false) {
|
||||
rgb[0] = c2.getRed(); rgb[1] = c2.getGreen(); rgb[2] = c2.getBlue();
|
||||
scan(world, jx, iy, jz, 0, isnether, c2, cache);
|
||||
|
||||
r.setPixel(x - 1, y, rgb);
|
||||
isempty = false;
|
||||
rgb[3*x] = c1.getRed();
|
||||
rgb[3*x+1] = c1.getGreen();
|
||||
rgb[3*x+2] = c1.getBlue();
|
||||
rgb[3*x-3] = c2.getRed();
|
||||
rgb[3*x-2] = c2.getGreen();
|
||||
rgb[3*x-1] = c2.getBlue();
|
||||
|
||||
isempty = isempty && c1.isTransparent() && c2.isTransparent();
|
||||
}
|
||||
r.setPixels(0, y, KzedMap.tileWidth, 1, rgb);
|
||||
/* Finish summing values for zoomed pixels */
|
||||
for(x = 0; x < KzedMap.tileWidth / 2; x++) {
|
||||
zrgb[3*x] = (zrgb[3*x] + rgb[6*x] + rgb[6*x+3]) >> 2;
|
||||
zrgb[3*x+1] = (zrgb[3*x+1] + rgb[6*x+1] + rgb[6*x+4]) >> 2;
|
||||
zrgb[3*x+2] = (zrgb[3*x+2] + rgb[6*x+2] + rgb[6*x+5]) >> 2;
|
||||
}
|
||||
zr.setPixels(0, y/2, KzedMap.tileWidth/2, 1, zrgb);
|
||||
|
||||
y++;
|
||||
|
||||
@ -124,13 +151,14 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
final File fname = outputFile;
|
||||
final KzedMapTile mtile = tile;
|
||||
final BufferedImage img = im;
|
||||
final BufferedImage zimg = zim;
|
||||
final KzedZoomedMapTile zmtile = new KzedZoomedMapTile(mtile.getWorld(),
|
||||
(KzedMap) mtile.getMap(), mtile);
|
||||
final File zoomFile = MapManager.mapman.getTileFile(zmtile);
|
||||
|
||||
MapManager.mapman.enqueueImageWrite(new Runnable() {
|
||||
public void run() {
|
||||
doFileWrites(fname, mtile, img, zmtile, zoomFile);
|
||||
doFileWrites(fname, mtile, img, zmtile, zoomFile, zimg);
|
||||
}
|
||||
});
|
||||
|
||||
@ -138,7 +166,8 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
}
|
||||
|
||||
private void doFileWrites(final File fname, final KzedMapTile mtile,
|
||||
final BufferedImage img, final KzedZoomedMapTile zmtile, final File zoomFile) {
|
||||
final BufferedImage img, final KzedZoomedMapTile zmtile, final File zoomFile,
|
||||
final BufferedImage zimg) {
|
||||
Debug.debug("saving image " + fname.getPath());
|
||||
try {
|
||||
ImageIO.write(img, "png", fname);
|
||||
@ -147,6 +176,8 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save image (NullPointerException): " + fname.getPath(), e);
|
||||
}
|
||||
img.flush();
|
||||
|
||||
mtile.file = fname;
|
||||
// Since we've already got the new tile, and we're on an async thread, just
|
||||
// make the zoomed tile here
|
||||
@ -175,21 +206,20 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
} catch (IndexOutOfBoundsException e) {
|
||||
}
|
||||
|
||||
boolean zIm_allocated = false;
|
||||
if (zIm == null) {
|
||||
/* create new one */
|
||||
zIm = new BufferedImage(KzedMap.tileWidth, KzedMap.tileHeight, BufferedImage.TYPE_INT_RGB);
|
||||
zIm = KzedMap.allocateBufferedImage(KzedMap.tileWidth, KzedMap.tileHeight);
|
||||
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 */
|
||||
Graphics2D g2 = zIm.createGraphics();
|
||||
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
|
||||
g2.drawImage(img, ox, oy, scw, sch, null);
|
||||
g2.dispose(); /* Supposed to speed up non-heap memory recovery */
|
||||
|
||||
img.flush();
|
||||
WritableRaster zim = zIm.getRaster();
|
||||
zim.setRect(ox, oy, zimg.getRaster());
|
||||
KzedMap.freeBufferedImage(zimg);
|
||||
|
||||
/* save zoom-out tile */
|
||||
|
||||
@ -201,6 +231,9 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
} catch (java.lang.NullPointerException e) {
|
||||
Debug.error("Failed to save zoom-out tile (NullPointerException): " + zoomFile.getName(), e);
|
||||
}
|
||||
if(zIm_allocated)
|
||||
KzedMap.freeBufferedImage(zIm);
|
||||
else
|
||||
zIm.flush();
|
||||
/* Push updates for both files.*/
|
||||
MapManager.mapman.pushUpdate(mtile.getWorld(),
|
||||
@ -209,14 +242,15 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
new Client.Tile(zmtile.getFilename()));
|
||||
}
|
||||
|
||||
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) {
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result,
|
||||
MapChunkCache cache) {
|
||||
int lightlevel = 15;
|
||||
result.setTransparent();
|
||||
for (;;) {
|
||||
if (y < 0) {
|
||||
return;
|
||||
}
|
||||
int id = world.getBlockTypeIdAt(x, y, z);
|
||||
int id = cache.getBlockTypeID(x, y, z);
|
||||
byte data = 0;
|
||||
if(isnether) { /* Make bedrock ceiling into air in nether */
|
||||
if(id != 0) {
|
||||
@ -229,9 +263,29 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
else
|
||||
isnether = false;
|
||||
}
|
||||
if(id != 0) { /* No update needed for air */
|
||||
if(colorScheme.datacolors[id] != null) { /* If data colored */
|
||||
data = world.getBlockAt(x, y, z).getData();
|
||||
data = cache.getBlockData(x, y, z);
|
||||
}
|
||||
if((shadowscale != null) && (y < 127)) {
|
||||
/* Find light level of previous chunk */
|
||||
switch(seq) {
|
||||
case 0:
|
||||
lightlevel = cache.getBlockSkyLight(x, y+1, z);
|
||||
break;
|
||||
case 1:
|
||||
lightlevel = cache.getBlockSkyLight(x+1, y, z);
|
||||
break;
|
||||
case 2:
|
||||
lightlevel = cache.getBlockSkyLight(x, y+1, z);
|
||||
break;
|
||||
case 3:
|
||||
lightlevel = cache.getBlockSkyLight(x, y, z-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch (seq) {
|
||||
case 0:
|
||||
x--;
|
||||
@ -266,16 +320,25 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
if (c.getAlpha() == 255) {
|
||||
/* it's opaque - the ray ends here */
|
||||
result.setColor(c);
|
||||
if(lightlevel < 15) { /* Not full light? */
|
||||
shadowColor(result, lightlevel);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/* this block is transparent, so recurse */
|
||||
scan(world, x, y, z, seq, isnether, result);
|
||||
scan(world, x, y, z, seq, isnether, result, cache);
|
||||
|
||||
int cr = c.getRed();
|
||||
int cg = c.getGreen();
|
||||
int cb = c.getBlue();
|
||||
int ca = c.getAlpha();
|
||||
if(lightlevel < 15) {
|
||||
int scale = shadowscale[lightlevel];
|
||||
cr = (cr * scale) >> 8;
|
||||
cg = (cg * scale) >> 8;
|
||||
cb = (cb * scale) >> 8;
|
||||
}
|
||||
cr *= ca;
|
||||
cg *= ca;
|
||||
cb *= ca;
|
||||
@ -287,4 +350,10 @@ public class DefaultTileRenderer implements MapTileRenderer {
|
||||
}
|
||||
}
|
||||
}
|
||||
private final void shadowColor(Color c, int lightlevel) {
|
||||
int scale = shadowscale[lightlevel];
|
||||
if(scale < 256)
|
||||
c.setRGBA((c.getRed() * scale) >> 8, (c.getGreen() * scale) >> 8,
|
||||
(c.getBlue() * scale) >> 8, c.getAlpha());
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.util.HashSet;
|
||||
import org.dynmap.MapChunkCache;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.World;
|
||||
@ -19,14 +20,15 @@ public class HighlightTileRenderer extends DefaultTileRenderer {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result) {
|
||||
protected void scan(World world, int x, int y, int z, int seq, boolean isnether, final Color result,
|
||||
MapChunkCache cache) {
|
||||
result.setTransparent();
|
||||
for (;;) {
|
||||
if (y < 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
int id = world.getBlockTypeIdAt(x, y, z);
|
||||
int id = cache.getBlockTypeID(x, y, z);
|
||||
if(isnether) { /* Make bedrock ceiling into air in nether */
|
||||
if(id != 0) {
|
||||
/* Remember first color we see, in case we wind up solid */
|
||||
@ -40,7 +42,7 @@ public class HighlightTileRenderer extends DefaultTileRenderer {
|
||||
}
|
||||
byte data = 0;
|
||||
if(colorScheme.datacolors[id] != null) { /* If data colored */
|
||||
data = world.getBlockAt(x, y, z).getData();
|
||||
data = cache.getBlockData(x, y, z);
|
||||
}
|
||||
|
||||
switch (seq) {
|
||||
|
@ -1,7 +1,10 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@ -12,6 +15,7 @@ import org.dynmap.DynmapChunk;
|
||||
import org.dynmap.Log;
|
||||
import org.dynmap.MapTile;
|
||||
import org.dynmap.MapType;
|
||||
import org.dynmap.MapChunkCache;
|
||||
|
||||
public class KzedMap extends MapType {
|
||||
protected static final Logger log = Logger.getLogger("Minecraft");
|
||||
@ -36,6 +40,13 @@ public class KzedMap extends MapType {
|
||||
MapTileRenderer[] renderers;
|
||||
ZoomedTileRenderer zoomrenderer;
|
||||
|
||||
/* BufferedImage cache - we use the same things a lot... */
|
||||
private static Object lock = new Object();
|
||||
private static HashMap<Long, LinkedList<BufferedImage>> imgcache =
|
||||
new HashMap<Long, LinkedList<BufferedImage>>(); /* Indexed by resolution - X<<32+Y */
|
||||
private static int[] zerobuf = new int[128];
|
||||
private static final int CACHE_LIMIT = 10;
|
||||
|
||||
public KzedMap(ConfigurationNode configuration) {
|
||||
Log.info("Loading renderers for map '" + getClass().toString() + "'...");
|
||||
List<MapTileRenderer> renderers = configuration.<MapTileRenderer>createInstances("renderers", new Class<?>[0], new Object[0]);
|
||||
@ -203,12 +214,12 @@ public class KzedMap extends MapType {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean render(MapTile tile, File outputFile) {
|
||||
public boolean render(MapChunkCache cache, MapTile tile, File outputFile) {
|
||||
if (tile instanceof KzedZoomedMapTile) {
|
||||
zoomrenderer.render((KzedZoomedMapTile) tile, outputFile);
|
||||
zoomrenderer.render(cache, (KzedZoomedMapTile) tile, outputFile);
|
||||
return true;
|
||||
} else if (tile instanceof KzedMapTile) {
|
||||
return ((KzedMapTile) tile).renderer.render((KzedMapTile) tile, outputFile);
|
||||
return ((KzedMapTile) tile).renderer.render(cache, (KzedMapTile) tile, outputFile);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -245,4 +256,48 @@ public class KzedMap extends MapType {
|
||||
else
|
||||
return y - (y % zTileHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate buffered image from pool, if possible
|
||||
* @param x - x dimension
|
||||
* @param y - y dimension
|
||||
*/
|
||||
public static BufferedImage allocateBufferedImage(int x, int y) {
|
||||
BufferedImage img = null;
|
||||
synchronized(lock) {
|
||||
long k = (x<<16) + y;
|
||||
LinkedList<BufferedImage> ll = imgcache.get(k);
|
||||
if(ll != null) {
|
||||
img = ll.poll();
|
||||
}
|
||||
}
|
||||
if(img != null) { /* Got it - reset it for use */
|
||||
if(zerobuf.length < x)
|
||||
zerobuf = new int[x];
|
||||
img.setRGB(0, 0, x, y, zerobuf, 0, 0);
|
||||
}
|
||||
else {
|
||||
img = new BufferedImage(x, y, BufferedImage.TYPE_INT_RGB);
|
||||
}
|
||||
return img;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return buffered image to pool
|
||||
*/
|
||||
public static void freeBufferedImage(BufferedImage img) {
|
||||
img.flush();
|
||||
synchronized(lock) {
|
||||
long k = (img.getWidth()<<16) + img.getHeight();
|
||||
LinkedList<BufferedImage> ll = imgcache.get(k);
|
||||
if(ll == null) {
|
||||
ll = new LinkedList<BufferedImage>();
|
||||
imgcache.put(k, ll);
|
||||
}
|
||||
if(ll.size() < CACHE_LIMIT) {
|
||||
ll.add(img);
|
||||
img = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
package org.dynmap.kzedmap;
|
||||
|
||||
import java.io.File;
|
||||
import org.dynmap.MapChunkCache;
|
||||
|
||||
public interface MapTileRenderer {
|
||||
String getName();
|
||||
|
||||
boolean render(KzedMapTile tile, File outputFile);
|
||||
boolean render(MapChunkCache cache, KzedMapTile tile, File outputFile);
|
||||
}
|
||||
|
@ -2,12 +2,12 @@ package org.dynmap.kzedmap;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Map;
|
||||
|
||||
import org.dynmap.MapChunkCache;
|
||||
public class ZoomedTileRenderer {
|
||||
public ZoomedTileRenderer(Map<String, Object> configuration) {
|
||||
}
|
||||
|
||||
public void render(final KzedZoomedMapTile zt, final File outputPath) {
|
||||
public void render(MapChunkCache cache, final KzedZoomedMapTile zt, final File outputPath) {
|
||||
return; /* Doing this in Default render, since image already loaded */
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ public class HttpServer extends Thread {
|
||||
}
|
||||
|
||||
public void startServer() throws IOException {
|
||||
sock = new ServerSocket(port, 5, bindAddress);
|
||||
sock = new ServerSocket(port, 50, bindAddress); /* 5 too low - more than a couple users during render will get connect errors on some tile loads */
|
||||
listeningThread = this;
|
||||
start();
|
||||
Log.info("Dynmap WebServer started on " + bindAddress + ":" + port);
|
||||
|
Loading…
Reference in New Issue
Block a user