mirror of
https://github.com/webbukkit/dynmap.git
synced 2024-11-28 13:15:30 +01:00
Merge pull request #281 from mikeprimm/hdrender
Add weakref based chunk snapshot cache
This commit is contained in:
commit
862c219d7d
@ -197,6 +197,12 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
return enabledTriggers.contains(s);
|
||||
}
|
||||
|
||||
private boolean onplace;
|
||||
private boolean onbreak;
|
||||
private boolean onsnow;
|
||||
private boolean onleaves;
|
||||
private boolean onburn;
|
||||
|
||||
public void registerEvents() {
|
||||
final PluginManager pm = getServer().getPluginManager();
|
||||
final MapManager mm = mapManager;
|
||||
@ -204,50 +210,61 @@ public class DynmapPlugin extends JavaPlugin {
|
||||
// To trigger rendering.
|
||||
{
|
||||
BlockListener renderTrigger = new BlockListener() {
|
||||
|
||||
@Override
|
||||
public void onBlockPlace(BlockPlaceEvent event) {
|
||||
if(event.isCancelled())
|
||||
return;
|
||||
mm.touch(event.getBlockPlaced().getLocation());
|
||||
if(onplace)
|
||||
mm.touch(event.getBlockPlaced().getLocation());
|
||||
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockBreak(BlockBreakEvent event) {
|
||||
if(event.isCancelled())
|
||||
return;
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
if(onbreak)
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
|
||||
}
|
||||
@Override
|
||||
public void onSnowForm(SnowFormEvent event) {
|
||||
if(event.isCancelled())
|
||||
return;
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
if(onsnow)
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLeavesDecay(LeavesDecayEvent event) {
|
||||
if(event.isCancelled())
|
||||
return;
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
if(onleaves)
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockBurn(BlockBurnEvent event) {
|
||||
if(event.isCancelled())
|
||||
return;
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
if(onburn)
|
||||
mm.touch(event.getBlock().getLocation());
|
||||
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
|
||||
}
|
||||
};
|
||||
if (isTrigger("blockplaced"))
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_PLACE, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
if (isTrigger("blockbreak"))
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BREAK, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
if (isTrigger("snowform"))
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.SNOW_FORM, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
if (isTrigger("leavesdecay"))
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.LEAVES_DECAY, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
if (isTrigger("blockburn"))
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BURN, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
onplace = isTrigger("blockplaced");
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_PLACE, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
onbreak = isTrigger("blockbreak");
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BREAK, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
onsnow = isTrigger("snowform");
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.SNOW_FORM, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
onleaves = isTrigger("leavesdecay");
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.LEAVES_DECAY, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
onburn = isTrigger("blockburn");
|
||||
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BURN, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
|
||||
}
|
||||
{
|
||||
PlayerListener renderTrigger = new PlayerListener() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.dynmap;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
@ -15,6 +16,8 @@ import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.ScheduledThreadPoolExecutor;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
@ -22,9 +25,11 @@ import org.bukkit.command.CommandSender;
|
||||
import org.dynmap.DynmapWorld.AutoGenerateOption;
|
||||
import org.dynmap.debug.Debug;
|
||||
import org.dynmap.hdmap.HDMapManager;
|
||||
import org.dynmap.utils.LRULinkedHashMap;
|
||||
import org.dynmap.utils.LegacyMapChunkCache;
|
||||
import org.dynmap.utils.MapChunkCache;
|
||||
import org.dynmap.utils.NewMapChunkCache;
|
||||
import org.dynmap.utils.SnapshotCache;
|
||||
|
||||
public class MapManager {
|
||||
public AsynchronousQueue<MapTile> tileQueue;
|
||||
@ -50,6 +55,7 @@ public class MapManager {
|
||||
|
||||
public static MapManager mapman; /* Our singleton */
|
||||
public HDMapManager hdmapman;
|
||||
public SnapshotCache sscache;
|
||||
|
||||
/* Thread pool for processing renders */
|
||||
private DynmapScheduledThreadPoolExecutor renderpool;
|
||||
@ -328,7 +334,8 @@ public class MapManager {
|
||||
hdmapman.loadHDShaders(shadercfg);
|
||||
hdmapman.loadHDPerspectives(perspectivecfg);
|
||||
hdmapman.loadHDLightings(lightingscfg);
|
||||
|
||||
sscache = new SnapshotCache(configuration.getInteger("snapshotcachesize", 500));
|
||||
|
||||
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
|
||||
@Override
|
||||
public void handle(MapTile t) {
|
||||
@ -623,6 +630,7 @@ public class MapManager {
|
||||
}
|
||||
sender.sendMessage(" TOTALS: processed=" + tot.loggedcnt + ", rendered=" + tot.renderedcnt +
|
||||
", updated=" + tot.updatedcnt + ", transparent=" + tot.transparentcnt);
|
||||
sender.sendMessage(" Cache hit rate: " + sscache.getHitRate() + "%");
|
||||
}
|
||||
/**
|
||||
* Reset statistics
|
||||
@ -639,6 +647,7 @@ public class MapManager {
|
||||
ms.transparentcnt = 0;
|
||||
}
|
||||
}
|
||||
sscache.resetStats();
|
||||
sender.sendMessage("Tile Render Statistics reset");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.dynmap.Client;
|
||||
import org.dynmap.Color;
|
||||
@ -248,7 +249,14 @@ public class IsoHDPerspective implements HDPerspective {
|
||||
short[] model = scalemodels.getScaledModel(blocktypeid, blockdata);
|
||||
if(model != null) {
|
||||
missed = raytraceSubblock(model);
|
||||
skip_light_update = true; /* Some blocks are light blocking, but not fully blocking - this sucks */
|
||||
/* Some blocks are light blocking, but not fully blocking - this sucks */
|
||||
switch(blocktypeid) {
|
||||
case 53: /* Wood stairs */
|
||||
case 44: /* Slabs */
|
||||
case 67: /* Cobblestone stairs */
|
||||
skip_light_update = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
subalpha = -1;
|
||||
@ -524,6 +532,10 @@ public class IsoHDPerspective implements HDPerspective {
|
||||
int x = t.tx;
|
||||
int y = t.ty;
|
||||
return new MapTile[] {
|
||||
new HDMapTile(w, this, x - 1, y - 1),
|
||||
new HDMapTile(w, this, x + 1, y - 1),
|
||||
new HDMapTile(w, this, x - 1, y + 1),
|
||||
new HDMapTile(w, this, x + 1, y + 1),
|
||||
new HDMapTile(w, this, x, y - 1),
|
||||
new HDMapTile(w, this, x + 1, y),
|
||||
new HDMapTile(w, this, x, y + 1),
|
||||
|
@ -128,6 +128,10 @@ public class KzedMap extends MapType {
|
||||
DynmapWorld world = tile.getDynmapWorld();
|
||||
MapTileRenderer renderer = t.renderer;
|
||||
return new MapTile[] {
|
||||
new KzedMapTile(world, this, renderer, t.px - tileWidth, t.py + tileHeight),
|
||||
new KzedMapTile(world, this, renderer, t.px + tileWidth, t.py - tileHeight),
|
||||
new KzedMapTile(world, this, renderer, t.px - tileWidth, t.py - tileHeight),
|
||||
new KzedMapTile(world, this, renderer, t.px + tileWidth, t.py + tileHeight),
|
||||
new KzedMapTile(world, this, renderer, t.px - tileWidth, t.py),
|
||||
new KzedMapTile(world, this, renderer, t.px + tileWidth, t.py),
|
||||
new KzedMapTile(world, this, renderer, t.px, t.py - tileHeight),
|
||||
|
@ -319,6 +319,20 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Check if cached chunk snapshot found */
|
||||
ChunkSnapshot ss = MapManager.mapman.sscache.getSnapshot(w.getName(), chunk.x, chunk.z, blockdata, biome, biomeraw, highesty);
|
||||
if(ss != null) {
|
||||
if(!vis) {
|
||||
if(hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN)
|
||||
ss = STONE;
|
||||
else if(hidestyle == HiddenChunkStyle.FILL_OCEAN)
|
||||
ss = OCEAN;
|
||||
else
|
||||
ss = EMPTY;
|
||||
}
|
||||
snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = ss;
|
||||
continue;
|
||||
}
|
||||
boolean wasLoaded = w.isChunkLoaded(chunk.x, chunk.z);
|
||||
boolean didload = w.loadChunk(chunk.x, chunk.z, false);
|
||||
boolean didgenerate = false;
|
||||
@ -327,7 +341,6 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
didgenerate = didload = w.loadChunk(chunk.x, chunk.z, true);
|
||||
/* If it did load, make cache of it */
|
||||
if(didload) {
|
||||
ChunkSnapshot ss = null;
|
||||
if(!vis) {
|
||||
if(hidestyle == HiddenChunkStyle.FILL_STONE_PLAIN)
|
||||
ss = STONE;
|
||||
@ -351,6 +364,8 @@ public class NewMapChunkCache implements MapChunkCache {
|
||||
}
|
||||
else
|
||||
ss = c.getChunkSnapshot();
|
||||
if(ss != null)
|
||||
MapManager.mapman.sscache.putSnapshot(w.getName(), chunk.x, chunk.z, ss, blockdata, biome, biomeraw, highesty);
|
||||
}
|
||||
snaparray[(chunk.x-x_min) + (chunk.z - z_min)*x_dim] = ss;
|
||||
}
|
||||
|
145
src/main/java/org/dynmap/utils/SnapshotCache.java
Normal file
145
src/main/java/org/dynmap/utils/SnapshotCache.java
Normal file
@ -0,0 +1,145 @@
|
||||
package org.dynmap.utils;
|
||||
|
||||
import java.lang.ref.Reference;
|
||||
import java.lang.ref.ReferenceQueue;
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.bukkit.ChunkSnapshot;
|
||||
import org.bukkit.Location;
|
||||
import org.dynmap.Log;
|
||||
|
||||
public class SnapshotCache {
|
||||
private CacheHashMap snapcache;
|
||||
private ReferenceQueue<ChunkSnapshot> refqueue;
|
||||
private long cache_attempts;
|
||||
private long cache_success;
|
||||
|
||||
private static class CacheRec {
|
||||
WeakReference<ChunkSnapshot> ref;
|
||||
boolean hasbiome;
|
||||
boolean hasrawbiome;
|
||||
boolean hasblockdata;
|
||||
boolean hashighesty;
|
||||
}
|
||||
|
||||
public class CacheHashMap extends LinkedHashMap<String, CacheRec> {
|
||||
private int limit;
|
||||
private IdentityHashMap<WeakReference<ChunkSnapshot>, String> reverselookup;
|
||||
|
||||
public CacheHashMap(int lim) {
|
||||
super(16, (float)0.75, true);
|
||||
limit = lim;
|
||||
reverselookup = new IdentityHashMap<WeakReference<ChunkSnapshot>, String>();
|
||||
}
|
||||
protected boolean removeEldestEntry(Map.Entry<String, CacheRec> last) {
|
||||
boolean remove = (size() >= limit);
|
||||
if(remove) {
|
||||
reverselookup.remove(last.getValue().ref);
|
||||
}
|
||||
return remove;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create snapshot cache
|
||||
*/
|
||||
public SnapshotCache(int max_size) {
|
||||
snapcache = new CacheHashMap(max_size);
|
||||
refqueue = new ReferenceQueue<ChunkSnapshot>();
|
||||
}
|
||||
private String getKey(Location loc) {
|
||||
return loc.getWorld().getName() + ":" + (loc.getBlockX()>>4) + ":" + (loc.getBlockZ()>>4);
|
||||
}
|
||||
private String getKey(String w, int cx, int cz) {
|
||||
return w + ":" + cx + ":" + cz;
|
||||
}
|
||||
/**
|
||||
* Invalidate cached snapshot, if in cache
|
||||
*/
|
||||
public void invalidateSnapshot(Location loc) {
|
||||
String key = getKey(loc);
|
||||
CacheRec rec = snapcache.remove(key);
|
||||
if(rec != null) {
|
||||
snapcache.reverselookup.remove(rec.ref);
|
||||
rec.ref.clear();
|
||||
}
|
||||
processRefQueue();
|
||||
}
|
||||
/**
|
||||
* Look for chunk snapshot in cache
|
||||
*/
|
||||
public ChunkSnapshot getSnapshot(String w, int chunkx, int chunkz,
|
||||
boolean blockdata, boolean biome, boolean biomeraw, boolean highesty) {
|
||||
String key = getKey(w, chunkx, chunkz);
|
||||
processRefQueue();
|
||||
ChunkSnapshot ss = null;
|
||||
CacheRec rec = snapcache.get(key);
|
||||
if(rec != null) {
|
||||
ss = rec.ref.get();
|
||||
if(ss == null) {
|
||||
snapcache.reverselookup.remove(rec.ref);
|
||||
snapcache.remove(key);
|
||||
}
|
||||
}
|
||||
if(ss != null) {
|
||||
if((blockdata && (!rec.hasblockdata)) ||
|
||||
(biome && (!rec.hasbiome)) ||
|
||||
(biomeraw && (!rec.hasrawbiome)) ||
|
||||
(highesty && (!rec.hashighesty))) {
|
||||
ss = null;
|
||||
}
|
||||
}
|
||||
cache_attempts++;
|
||||
if(ss != null) cache_success++;
|
||||
|
||||
return ss;
|
||||
}
|
||||
/**
|
||||
* Add chunk snapshot to cache
|
||||
*/
|
||||
public void putSnapshot(String w, int chunkx, int chunkz, ChunkSnapshot ss,
|
||||
boolean blockdata, boolean biome, boolean biomeraw, boolean highesty) {
|
||||
String key = getKey(w, chunkx, chunkz);
|
||||
processRefQueue();
|
||||
CacheRec rec = new CacheRec();
|
||||
rec.hasblockdata = blockdata;
|
||||
rec.hasbiome = biome;
|
||||
rec.hasrawbiome = biomeraw;
|
||||
rec.hashighesty = highesty;
|
||||
rec.ref = new WeakReference<ChunkSnapshot>(ss, refqueue);
|
||||
CacheRec prevrec = snapcache.put(key, rec);
|
||||
if(prevrec != null) {
|
||||
snapcache.reverselookup.remove(prevrec.ref);
|
||||
}
|
||||
snapcache.reverselookup.put(rec.ref, key);
|
||||
}
|
||||
/**
|
||||
* Process reference queue
|
||||
*/
|
||||
private void processRefQueue() {
|
||||
Reference<? extends ChunkSnapshot> ref;
|
||||
while((ref = refqueue.poll()) != null) {
|
||||
String k = snapcache.reverselookup.get(ref);
|
||||
if(k != null) snapcache.remove(k);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Get hit rate (percent)
|
||||
*/
|
||||
public double getHitRate() {
|
||||
if(cache_attempts > 0) {
|
||||
return (100.0*cache_success)/(double)cache_attempts;
|
||||
}
|
||||
return 0.0;
|
||||
}
|
||||
/**
|
||||
* Reset cache stats
|
||||
*/
|
||||
public void resetStats() {
|
||||
cache_attempts = cache_success = 0;
|
||||
}
|
||||
}
|
||||
|
@ -292,13 +292,19 @@ DynMap.prototype = {
|
||||
|
||||
var updateHeight = function() {
|
||||
playerlist.height(sidebar.innerHeight() - (playerlist.offset().top - worldlist.offset().top) - 64); // here we need a fix to avoid the static value, but it works fine this way :P
|
||||
var scrollable = playerlist.scrollHeight() > playerlist.height();
|
||||
console.log('scrollheight=' + playerlist.scrollHeight() + ', height=' + playerlist.height());
|
||||
var scrollable = playerlist.scrollHeight() < playerlist.height();
|
||||
upbtn.toggle(scrollable);
|
||||
downbtn.toggle(scrollable);
|
||||
};
|
||||
updateHeight();
|
||||
$(window).resize(updateHeight);
|
||||
|
||||
$(dynmap).bind('playeradded', function() {
|
||||
updateHeight();
|
||||
});
|
||||
$(dynmap).bind('playerremoved', function() {
|
||||
updateHeight();
|
||||
});
|
||||
// The Compass
|
||||
var compass = $('<div/>')
|
||||
.addClass('compass')
|
||||
|
Loading…
Reference in New Issue
Block a user