Merge pull request #281 from mikeprimm/hdrender

Add weakref based chunk snapshot cache
This commit is contained in:
mikeprimm 2011-07-13 23:10:20 -07:00
commit 862c219d7d
7 changed files with 229 additions and 21 deletions

View File

@ -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,49 +210,60 @@ public class DynmapPlugin extends JavaPlugin {
// To trigger rendering.
{
BlockListener renderTrigger = new BlockListener() {
@Override
public void onBlockPlace(BlockPlaceEvent event) {
if(event.isCancelled())
return;
if(onplace)
mm.touch(event.getBlockPlaced().getLocation());
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
}
@Override
public void onBlockBreak(BlockBreakEvent event) {
if(event.isCancelled())
return;
if(onbreak)
mm.touch(event.getBlock().getLocation());
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
}
@Override
public void onSnowForm(SnowFormEvent event) {
if(event.isCancelled())
return;
if(onsnow)
mm.touch(event.getBlock().getLocation());
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
}
@Override
public void onLeavesDecay(LeavesDecayEvent event) {
if(event.isCancelled())
return;
if(onleaves)
mm.touch(event.getBlock().getLocation());
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
}
@Override
public void onBlockBurn(BlockBurnEvent event) {
if(event.isCancelled())
return;
if(onburn)
mm.touch(event.getBlock().getLocation());
mm.sscache.invalidateSnapshot(event.getBlock().getLocation());
}
};
if (isTrigger("blockplaced"))
onplace = isTrigger("blockplaced");
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_PLACE, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("blockbreak"))
onbreak = isTrigger("blockbreak");
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BREAK, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("snowform"))
onsnow = isTrigger("snowform");
pm.registerEvent(org.bukkit.event.Event.Type.SNOW_FORM, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("leavesdecay"))
onleaves = isTrigger("leavesdecay");
pm.registerEvent(org.bukkit.event.Event.Type.LEAVES_DECAY, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
if (isTrigger("blockburn"))
onburn = isTrigger("blockburn");
pm.registerEvent(org.bukkit.event.Event.Type.BLOCK_BURN, renderTrigger, org.bukkit.event.Event.Priority.Monitor, this);
}
{

View File

@ -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,6 +334,7 @@ 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
@ -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");
}
}

View File

@ -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),

View File

@ -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),

View File

@ -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;
}

View 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;
}
}

View File

@ -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')