diff --git a/src/main/java/org/dynmap/DynmapPlugin.java b/src/main/java/org/dynmap/DynmapPlugin.java index 8cfcfd64..6e9a7fb5 100644 --- a/src/main/java/org/dynmap/DynmapPlugin.java +++ b/src/main/java/org/dynmap/DynmapPlugin.java @@ -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() { diff --git a/src/main/java/org/dynmap/MapManager.java b/src/main/java/org/dynmap/MapManager.java index 42a0c4fe..350f2c84 100644 --- a/src/main/java/org/dynmap/MapManager.java +++ b/src/main/java/org/dynmap/MapManager.java @@ -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 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(new Handler() { @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"); - } + } } diff --git a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java index 689c7231..012030fa 100644 --- a/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java +++ b/src/main/java/org/dynmap/hdmap/IsoHDPerspective.java @@ -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), diff --git a/src/main/java/org/dynmap/kzedmap/KzedMap.java b/src/main/java/org/dynmap/kzedmap/KzedMap.java index 5bc34e4c..73867937 100644 --- a/src/main/java/org/dynmap/kzedmap/KzedMap.java +++ b/src/main/java/org/dynmap/kzedmap/KzedMap.java @@ -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), diff --git a/src/main/java/org/dynmap/utils/NewMapChunkCache.java b/src/main/java/org/dynmap/utils/NewMapChunkCache.java index e4816e96..a350f0d5 100644 --- a/src/main/java/org/dynmap/utils/NewMapChunkCache.java +++ b/src/main/java/org/dynmap/utils/NewMapChunkCache.java @@ -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; } diff --git a/src/main/java/org/dynmap/utils/SnapshotCache.java b/src/main/java/org/dynmap/utils/SnapshotCache.java new file mode 100644 index 00000000..ee321fba --- /dev/null +++ b/src/main/java/org/dynmap/utils/SnapshotCache.java @@ -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 refqueue; + private long cache_attempts; + private long cache_success; + + private static class CacheRec { + WeakReference ref; + boolean hasbiome; + boolean hasrawbiome; + boolean hasblockdata; + boolean hashighesty; + } + + public class CacheHashMap extends LinkedHashMap { + private int limit; + private IdentityHashMap, String> reverselookup; + + public CacheHashMap(int lim) { + super(16, (float)0.75, true); + limit = lim; + reverselookup = new IdentityHashMap, String>(); + } + protected boolean removeEldestEntry(Map.Entry 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(); + } + 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(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 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; + } +} + diff --git a/web/js/map.js b/web/js/map.js index e938b49e..60446c92 100644 --- a/web/js/map.js +++ b/web/js/map.js @@ -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 = $('
') .addClass('compass')