Compare commits

...

69 Commits

Author SHA1 Message Date
Mike Primm babc8cd073 Fix accumulation of weak reference keys (mem leak) in snapshot cache 2011-12-28 14:38:26 +08:00
Mike Primm bd92cc37bf Switch chat name URL parm to 'chatname', add 'playername' URL part for initial player follow selection 2011-12-26 13:11:46 +08:00
Mike Primm ca00a84dc5 Fix cleanup of area outlines with minzoom when switching maps 2011-12-24 22:09:18 -06:00
Mike Primm 1af56db030 Fix minzoom handling for area markers 2011-12-23 14:11:02 +08:00
Mike Primm 93613404ba Adjust handling of transparent biome shading files (grass, foliage), as used in Painter's texture pack 2011-12-23 13:32:27 +08:00
Mike Primm cd067adfdc Fix issue with white blank tiles at edge of JPG rendered maps 2011-12-23 13:32:27 +08:00
Mike Primm b872aa039e Handle Essentials-style nickname color coding (&0-&f) in player nicknames 2011-12-23 07:20:48 +08:00
Mike Primm eafbe62c46 Add support for minzoom attribute on marker sets - hide markers below configurable zoom for each marker set 2011-12-23 04:31:10 +08:00
Mike Primm 7373de85a2 Add support for updating stock shaders.txt, perspectives.txt, lightings.txt with additional defaults. 2011-12-23 04:31:09 +08:00
Mike Primm 44106a799a Add 'allowurlname' setting for 'chat' - permits web name set with playername= parameter 2011-12-23 04:31:09 +08:00
Mike Primm 756202affd Bump to 0.28 2011-12-17 00:00:27 -06:00
Mike Primm 5dedf1d6e8 Add /dynmap radiusrender <world> <x> <z> <radius> - console usable radiusrender 2011-12-17 13:18:43 +08:00
Mike Primm 96e4742bfe Dial the ticklist workaround to align with proposed numbers in CraftBukkit-Bleeding fix 2011-12-17 07:43:06 +08:00
Mike Primm 9c0f33bd3d Adjust ticklist workaround to handle nether's tendancy to have ticklist processed events beget new ticklist events, more or less constantly.... 2011-12-17 07:26:31 +08:00
Mike Primm 4a3e95a118 Remove marker label field if label for marker is blank ("") 2011-12-16 01:26:13 +08:00
Mike Primm 3c849b9d66 Add 'grayplayerswhenhidden' option to allow disable of graying of players in player list when not visible on current map 2011-12-16 01:26:13 +08:00
Mike Primm 5af670de61 Add extra topographical shader, TerraNetworkOrgTopo - thanks to TerraNetworkOrg! 2011-12-16 00:09:48 +08:00
Mike Primm 708f0b3c80 Make it so that blank label ("") on area marker disables popup on area 2011-12-15 23:54:45 +08:00
Mike Primm 102012aa91 Detect scaled models that yield full blocks, and optimize to solid 2011-12-15 15:04:17 +08:00
Mike Primm ba2c3eac1c Add support for cleanup of stale tiles (off edge of map) 2011-12-15 13:26:09 +08:00
Mike Primm 8f66d34752 Make water blocks full blocks - big performance boost 2011-12-15 13:26:09 +08:00
Mike Primm 03b280fe2d Add 'usenormalthreadpriority' setting (makes renders on Windows use normal priority) 2011-12-15 00:35:00 +08:00
Mike Primm f7dedff413 Add 'showlayercontrol' config setting and URL parameter, to allow hide of layer control 2011-12-15 00:02:37 +08:00
Mike Primm d5e1bc1b4d Fix size/layout of player faces in chatbox 2011-12-12 02:20:32 +08:00
Mike Primm ef16a13cd5 Bump to 0.27 2011-12-11 01:22:13 -06:00
Mike Primm 14ecd5f778 Fix rare boundary condition error 2011-12-11 13:54:38 +08:00
Mike Primm 05aa0960f2 Various performance and concurrency optimizations 2011-12-10 14:15:54 +08:00
Mike Primm fd887b47b4 Add tiles-rendered-at-once setting, control update tile concurrency 2011-12-10 11:34:02 +08:00
Mike Primm 9bdf13e460 Add TopoHDShader for generating altitude based topology maps 2011-12-10 08:22:24 +08:00
Mike Primm 1b8fe6bba8 Scrub obsolete events from update queue (reduce traffic) 2011-12-09 13:23:16 +08:00
Mike Primm aea2d29c98 Fix config hashcode inconsistency on world loads (UI reload loop in JSON mode) 2011-12-09 12:22:29 +08:00
Mike Primm 6a86b81417 Start optimize of update event handling 2011-12-08 16:14:51 +08:00
Mike Primm 6ee141cada Revert to older Leaflet until zoom problem is worked out 2011-12-07 15:33:42 +08:00
Mike Primm 36b81768cd Add tick list monitoring - deal with 1.0.0 getting behind when rendering 2011-12-07 15:33:42 +08:00
Mike Primm 3e4bc59834 Retire regions components (Residence, Towny, WorldGuard, Factions) 2011-12-05 14:58:17 +08:00
Mike Primm 4f2f968353 Move DynmapWebChatEvent to API 2011-12-05 14:17:04 +08:00
Mike Primm b936e67a88 Add APIs for hide/show player and for player-oriented web chat input 2011-12-05 14:17:04 +08:00
Mike Primm 710f5eaa7d Update leaflet - improves zoom in panning problem (better, not perfect) 2011-12-04 00:32:25 +08:00
Mike Primm 19f9733aab Add 'fullrenderplayerlimit' - pause fullrender when number of active players reaches optional limit, resumes after 2011-12-03 13:17:26 +08:00
Mike Primm de6b90784f Add '/dynmap pause' command to pause/unpause full/radius and update rendering 2011-12-03 06:20:57 +08:00
Mike Primm 8f6982265a Support checking banned IPs from external server proxied requests 2011-12-03 05:32:43 +08:00
Mike Primm 82318346c2 Add 'hidey' option to 'coord' component - option to show just X,Z 2011-12-03 04:32:25 +08:00
Mike Primm 893117d638 Work player marker layout - center icon on player location 2011-12-03 04:13:51 +08:00
Mike Primm 348a37af87 Add support for 8x8 and 32x32 marker icons (default still 16x16) 2011-12-01 15:24:19 +08:00
Mike Primm 52a09d41b4 Add more performance measurements 2011-12-01 15:24:19 +08:00
Mike Primm edcf1cfd10 Make web UI reload when configuration changes, server restarts 2011-11-30 12:34:13 +08:00
Mike Primm 4af95f57df Fix problem with hashfile code handling dots/periods in world names 2011-11-30 12:12:00 +08:00
Mike Primm 3ad67483ae Turn off smoothing factor on area markers 2011-11-29 21:58:02 +08:00
Mike Primm ba0d197e78 Merge remote branch 'upstream/master' 2011-11-28 20:53:38 -06:00
Mike Primm 047897d0b0 Make compass rose smaller on mobile device browsers 2011-11-29 10:52:15 +08:00
Mike Primm a4056bc502 Bump to 0.26 2011-11-29 10:52:15 +08:00
FrozenCow f13e91ad2b Set worldCopyJump to false, just to be sure of future Leaflet updates. 2011-11-29 10:52:15 +08:00
FrozenCow 6d39aba001 Fixed locked on Y-axis (non continuous world). 2011-11-29 10:52:14 +08:00
Mike Primm 2781f47463 Merge remote branch 'origin/master' 2011-11-28 20:51:09 -06:00
Mike Primm 4992e92aab Make compass rose smaller on mobile device browsers 2011-11-28 20:50:42 -06:00
Mike Primm dc2dc578e9 Bump to 0.26 2011-11-28 20:50:13 -06:00
Mike Primm 176dbcf99f Merge remote branch 'org.eclipse.jgit.transport.RemoteConfig@4424f1a9/leafletupdate' 2011-11-28 18:55:06 -06:00
Mike Primm e77c36c6f3 Make sure format of version-buildnum matches 2011-11-27 19:35:49 -06:00
Mike Primm 1472e96104 Add build number to version.js too 2011-11-27 19:30:57 -06:00
Mike Primm ecf01547cd Try inserting build number into version 2011-11-27 17:21:23 -06:00
Mike Primm 97998892ba Merge remote branch 'upstream/master' 2011-11-27 17:01:25 -06:00
Mike Primm 236ba68cae Switch maven dependencies to ranges 2011-11-27 16:37:43 -06:00
Mike Primm e0925dfc98 Switch maven shader from exclude to include - keep idiot maven from pulling in PEX from shared build server.... 2011-11-27 15:47:51 -06:00
FrozenCow 5e33afd361 Removed redundant declaration. 2011-11-28 01:38:48 +08:00
Mike Primm 91fa58584e Try another update to see if PEX is appeased 2011-11-27 11:25:22 -06:00
FrozenCow 164f6f275c Set worldCopyJump to false, just to be sure of future Leaflet updates. 2011-11-28 00:13:12 +08:00
Mike Primm 48c43c5abb Delay asking for PermissionHandler - might help PEX not freak out 2011-11-27 10:06:23 -06:00
FrozenCow 37a6b2932a Fixed locked on Y-axis (non continuous world). 2011-11-27 23:32:09 +08:00
Mike Primm 0025bd62fb Start leaflet update... 2011-11-25 19:59:01 -06:00
63 changed files with 1718 additions and 2503 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.dynmap</groupId>
<artifactId>dynmap</artifactId>
<version>0.25.1</version>
<version>0.28</version>
<name>dynmap</name>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -17,13 +17,16 @@ public class AsynchronousQueue<T> {
private int accelDequeueThresh;
private int pendingcnt;
private int pendinglimit;
private boolean normalprio;
public AsynchronousQueue(Handler<T> handler, int dequeueTime, int accelDequeueThresh, int accelDequeueTime, int pendinglimit) {
public AsynchronousQueue(Handler<T> handler, int dequeueTime, int accelDequeueThresh, int accelDequeueTime, int pendinglimit, boolean normalprio) {
this.handler = handler;
this.dequeueTime = dequeueTime;
this.accelDequeueTime = accelDequeueTime;
this.accelDequeueThresh = accelDequeueThresh;
if(pendinglimit < 1) pendinglimit = 1;
this.pendinglimit = pendinglimit;
this.normalprio = normalprio;
}
public boolean push(T t) {
@@ -82,7 +85,8 @@ public class AsynchronousQueue<T> {
});
thread.start();
try {
thread.setPriority(Thread.MIN_PRIORITY);
if(!normalprio)
thread.setPriority(Thread.MIN_PRIORITY);
} catch (SecurityException e) {
Log.info("Failed to set minimum priority for worker thread!");
}
+79 -3
View File
@@ -34,11 +34,23 @@ public class Client {
public String channel;
public ChatMessage(String source, String channel, String playerName, String message, String playeraccount) {
this.source = source;
this.playerName = ChatColor.stripColor(playerName);
this.playerName = Client.stripColor(playerName);
this.message = ChatColor.stripColor(message);
this.account = playeraccount;
this.channel = channel;
}
@Override
public boolean equals(Object o) {
if(o instanceof ChatMessage) {
ChatMessage m = (ChatMessage)o;
return m.source.equals(source) && m.playerName.equals(playerName) && m.message.equals(message);
}
return false;
}
@Override
public int hashCode() {
return source.hashCode() ^ playerName.hashCode() ^ message.hashCode();
}
}
public static class PlayerJoinMessage extends Update {
@@ -46,9 +58,21 @@ public class Client {
public String playerName;
public String account;
public PlayerJoinMessage(String playerName, String playeraccount) {
this.playerName = ChatColor.stripColor(playerName);
this.playerName = Client.stripColor(playerName);
this.account = playeraccount;
}
@Override
public boolean equals(Object o) {
if(o instanceof PlayerJoinMessage) {
PlayerJoinMessage m = (PlayerJoinMessage)o;
return m.playerName.equals(playerName);
}
return false;
}
@Override
public int hashCode() {
return playerName.hashCode();
}
}
public static class PlayerQuitMessage extends Update {
@@ -56,9 +80,21 @@ public class Client {
public String playerName;
public String account;
public PlayerQuitMessage(String playerName, String playeraccount) {
this.playerName = ChatColor.stripColor(playerName);
this.playerName = Client.stripColor(playerName);
this.account = playeraccount;
}
@Override
public boolean equals(Object o) {
if(o instanceof PlayerQuitMessage) {
PlayerQuitMessage m = (PlayerQuitMessage)o;
return m.playerName.equals(playerName);
}
return false;
}
@Override
public int hashCode() {
return playerName.hashCode();
}
}
public static class Tile extends Update {
@@ -68,6 +104,18 @@ public class Client {
public Tile(String name) {
this.name = name;
}
@Override
public boolean equals(Object o) {
if(o instanceof Tile) {
Tile m = (Tile)o;
return m.name.equals(name);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode();
}
}
public static class DayNight extends Update {
@@ -77,10 +125,38 @@ public class Client {
public DayNight(boolean isday) {
this.isday = isday;
}
@Override
public boolean equals(Object o) {
if(o instanceof DayNight) {
return true;
}
return false;
}
@Override
public int hashCode() {
return 12345;
}
}
public static class ComponentMessage extends Update {
public String type = "component";
/* Each subclass must provide 'ctype' string for component 'type' */
}
public static String stripColor(String s) {
s = ChatColor.stripColor(s); /* Strip standard color encoding */
/* Handle Essentials nickname encoding too */
int idx = 0;
while((idx = s.indexOf('&', idx)) >= 0) {
char c = s.charAt(idx+1); /* Get next character */
if(c == '&') { /* Another ampersand */
s = s.substring(0, idx) + s.substring(idx+1);
}
else {
s = s.substring(0, idx) + s.substring(idx+2);
}
idx++;
}
return s;
}
}
@@ -14,6 +14,7 @@ public class ClientConfigurationComponent extends Component {
@Override
public void triggered(JSONObject t) {
ConfigurationNode c = plugin.configuration;
s(t, "confighash", plugin.getConfigHashcode());
s(t, "updaterate", c.getFloat("updaterate", 1.0f));
s(t, "showplayerfacesinmenu", c.getBoolean("showplayerfacesinmenu", true));
s(t, "joinmessage", c.getString("joinmessage", "%playername% joined"));
@@ -24,6 +25,8 @@ public class ClientConfigurationComponent extends Component {
s(t, "sidebaropened", c.getString("sidebaropened", "false"));
s(t, "dynmapversion", plugin.getDescription().getVersion());
s(t, "cyrillic", c.getBoolean("cyrillic-support", false));
s(t, "showlayercontrol", c.getBoolean("showlayercontrol", true));
s(t, "grayplayerswhenhidden", c.getBoolean("grayplayerswhenhidden", true));
String sn = plugin.getServer().getServerName();
if(sn.equals("Unknown Server"))
sn = "Minecraft Dynamic Map";
@@ -35,7 +35,9 @@ public class ClientUpdateComponent extends Component {
String worldName = world.getName();
int hideifshadow = configuration.getInteger("hideifshadow", 15);
int hideifunder = configuration.getInteger("hideifundercover", 15);
s(u, "confighash", plugin.getConfigHashcode());
s(u, "servertime", world.getTime() % 24000);
s(u, "hasStorm", world.hasStorm());
s(u, "isThundering", world.isThundering());
@@ -49,7 +51,7 @@ public class ClientUpdateComponent extends Component {
boolean hide = false;
s(jp, "type", "player");
s(jp, "name", ChatColor.stripColor(p.getDisplayName()));
s(jp, "name", Client.stripColor(p.getDisplayName()));
s(jp, "account", p.getName());
if(hideifshadow < 15) {
if(pl.getBlock().getLightLevel() <= hideifshadow)
@@ -98,7 +100,7 @@ public class ClientUpdateComponent extends Component {
for(Player p : hidden) {
JSONObject jp = new JSONObject();
s(jp, "type", "player");
s(jp, "name", ChatColor.stripColor(p.getDisplayName()));
s(jp, "name", Client.stripColor(p.getDisplayName()));
s(jp, "account", p.getName());
s(jp, "world", "-hidden-player-");
s(jp, "x", 0.0);
+213 -17
View File
@@ -21,6 +21,8 @@ import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.CustomEventListener;
import org.bukkit.event.Event;
@@ -81,6 +83,9 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
boolean waterbiomeshading = false;
boolean fencejoin = false;
public CompassMode compassmode = CompassMode.PRE19;
private int config_hashcode; /* Used to signal need to reload web configuration (world changes, config update, etc) */
private int fullrenderplayerlimit; /* Number of online players that will cause fullrender processing to pause */
private boolean didfullpause;
public enum CompassMode {
PRE19, /* Default for 1.8 and earlier (east is Z+) */
@@ -219,7 +224,7 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
if (permissions == null)
permissions = BukkitPermissions.create("dynmap");
if (permissions == null)
permissions = new OpPermissions(new String[] { "fullrender", "cancelrender", "radiusrender", "resetstats", "reload", "purgequeue" });
permissions = new OpPermissions(new String[] { "fullrender", "cancelrender", "radiusrender", "resetstats", "reload", "purgequeue", "pause" });
dataDirectory = this.getDataFolder();
if(dataDirectory.exists() == false)
@@ -271,6 +276,8 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
compassmode = CompassMode.NEWROSE;
else
compassmode = CompassMode.PRE19;
/* Load full render processing player limit */
fullrenderplayerlimit = configuration.getInteger("fullrenderplayerlimit", 0);
loadDebuggers();
@@ -284,9 +291,27 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
PlayerListener pl = new PlayerListener() {
public void onPlayerJoin(PlayerJoinEvent evt) {
playerList.updateOnlinePlayers(null);
if(fullrenderplayerlimit > 0) {
if((getServer().getOnlinePlayers().length+1) >= fullrenderplayerlimit) {
if(getPauseFullRadiusRenders() == false) { /* If not paused, pause it */
setPauseFullRadiusRenders(true);
Log.info("Pause full/radius renders - player limit reached");
didfullpause = true;
}
}
}
}
public void onPlayerQuit(PlayerQuitEvent evt) {
playerList.updateOnlinePlayers(evt.getPlayer());
if(fullrenderplayerlimit > 0) {
if((getServer().getOnlinePlayers().length-1) < fullrenderplayerlimit) {
if(didfullpause) { /* Only unpause if we did the pause */
setPauseFullRadiusRenders(false);
Log.info("Resume full/radius renders - below player limit");
didfullpause = false;
}
}
}
}
};
registerEvent(Type.PLAYER_JOIN, pl);
@@ -297,6 +322,8 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
playerfacemgr = new PlayerFaces(this);
updateConfigHashcode(); /* Initialize/update config hashcode */
loadWebserver();
enabledTriggers.clear();
@@ -327,6 +354,14 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
events.<Object>trigger("initialized", null);
}
public void updateConfigHashcode() {
config_hashcode = (int)System.currentTimeMillis();
}
public int getConfigHashcode() {
return config_hashcode;
}
public void loadWebserver() {
InetAddress bindAddress;
{
@@ -361,7 +396,7 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
Log.verboseinfo("Web server is permitting symbolic links");
else
Log.verboseinfo("Web server is not permitting symbolic links");
webServer = new HttpServer(bindAddress, port, checkbannedips, maxconnections);
webServer = new HttpServer(bindAddress, port, checkbannedips, maxconnections, this);
webServer.handlers.put("/", new FilesystemHandler(getFile(configuration.getString("webpath", "web")), allow_symlinks));
webServer.handlers.put("/tiles/", new FilesystemHandler(tilesDirectory, allow_symlinks));
webServer.handlers.put("/up/configuration", new ClientConfigurationHandler(this));
@@ -632,6 +667,7 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
}
@Override
public void onWorldLoad(WorldLoadEvent event) {
updateConfigHashcode();
mapManager.activateWorld(event.getWorld());
}
};
@@ -730,6 +766,7 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
"triggerstats",
"resetstats",
"sendtoweb",
"pause",
"purgequeue" }));
@Override
@@ -766,23 +803,37 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
}
}
else if(c.equals("radiusrender") && checkPlayerPermission(sender,"radiusrender")) {
if (player != null) {
int radius = 0;
String mapname = null;
if(args.length > 1) {
radius = Integer.parseInt(args[1]); /* Parse radius */
if(radius < 0)
radius = 0;
if(args.length > 2)
mapname = args[2];
int radius = 0;
String mapname = null;
Location loc = null;
if(args.length == 2) { /* Just radius */
radius = Integer.parseInt(args[1]); /* Parse radius */
if(radius < 0)
radius = 0;
if(args.length > 2)
mapname = args[2];
if (player != null)
loc = player.getLocation();
else
sender.sendMessage("Command require <world> <x> <z> <radius> if issued from console.");
}
else if(args.length > 3) { /* <world> <x> <z> */
DynmapWorld w = mapManager.worldsLookup.get(args[1]); /* Look up world */
if(w == null) {
sender.sendMessage("World '" + args[1] + "' not defined/loaded");
}
Location loc = player.getLocation();
if(loc != null)
mapManager.renderWorldRadius(loc, sender, mapname, radius);
}
else {
sender.sendMessage("Command can only be issued by player.");
double x = 0, z = 0;
x = Double.parseDouble(args[2]);
z = Double.parseDouble(args[3]);
if(args.length > 4)
radius = Integer.parseInt(args[4]);
if(args.length > 5)
mapname = args[5];
if(w != null)
loc = new Location(w.world, x, 64.0, z);
}
if(loc != null)
mapManager.renderWorldRadius(loc, sender, mapname, radius);
} else if (c.equals("hide")) {
if (args.length == 1) {
if(player != null && checkPlayerPermission(sender,"hide.self")) {
@@ -865,6 +916,33 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
mapManager.printStats(sender, args[1]);
} else if (c.equals("triggerstats") && checkPlayerPermission(sender, "stats")) {
mapManager.printTriggerStats(sender);
} else if (c.equals("pause") && checkPlayerPermission(sender, "pause")) {
if(args.length == 1) {
}
else if(args[1].equals("full")) {
setPauseFullRadiusRenders(true);
setPauseUpdateRenders(false);
}
else if(args[1].equals("update")) {
setPauseFullRadiusRenders(false);
setPauseUpdateRenders(true);
}
else if(args[1].equals("all")) {
setPauseFullRadiusRenders(true);
setPauseUpdateRenders(true);
}
else {
setPauseFullRadiusRenders(false);
setPauseUpdateRenders(false);
}
if(getPauseFullRadiusRenders())
sender.sendMessage("Full/Radius renders are PAUSED");
else
sender.sendMessage("Full/Radius renders are ACTIVE");
if(getPauseUpdateRenders())
sender.sendMessage("Update renders are PAUSED");
else
sender.sendMessage("Update renders are ACTIVE");
} else if (c.equals("resetstats") && checkPlayerPermission(sender, "resetstats")) {
if(args.length == 1)
mapManager.resetStats(sender, null);
@@ -997,6 +1075,61 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
}
}
/*
* Add in any missing sections to existing file, using resource
*/
public boolean updateUsingDefaultResource(String resourcename, File deffile, String basenode) {
InputStream in = getClass().getResourceAsStream(resourcename);
if(in == null) {
Log.severe("Unable to find resource - " + resourcename);
return false;
}
if(deffile.canRead() == false) { /* Doesn't exist? */
return createDefaultFileFromResource(resourcename, deffile);
}
/* Load default from resource */
YamlConfiguration def_fc = YamlConfiguration.loadConfiguration(in);
/* Load existing from file */
YamlConfiguration fc = YamlConfiguration.loadConfiguration(deffile);
/* Now, get the list associated with the base node default */
List<Map<String,Object>> existing = fc.getMapList(basenode);
Set<String> existing_names = new HashSet<String>();
/* Make map, indexed by 'name' in map */
if(existing != null) {
for(Map<String,Object> m : existing) {
Object name = m.get("name");
if(name instanceof String)
existing_names.add((String)name);
}
}
boolean did_update = false;
/* Now, loop through defaults, and see if any are missing */
List<Map<String,Object>> defmaps = def_fc.getMapList(basenode);
if(defmaps != null) {
for(Map<String,Object> m : defmaps) {
Object name = m.get("name");
if(name instanceof String) {
/* If not an existing one, need to add it */
if(existing_names.contains((String)name) == false) {
existing.add(m);
did_update = true;
}
}
}
}
/* If we did update, save existing */
if(did_update) {
try {
fc.set(basenode, existing);
fc.save(deffile);
} catch (IOException iox) {
Log.severe("Error saving migrated file - " + deffile.getPath());
return false;
}
Log.info("Updated file " + deffile.getPath());
}
return true;
}
private BlockListener ourBlockEventHandler = new BlockListener() {
@@ -1340,4 +1473,67 @@ public class DynmapPlugin extends JavaPlugin implements DynmapAPI {
public void registerMarkerAPI(MarkerAPIImpl api) {
markerapi = api;
}
/*
* Pause full/radius render processing
* @param dopause - true to pause, false to unpause
*/
public void setPauseFullRadiusRenders(boolean dopause) {
mapManager.setPauseFullRadiusRenders(dopause);
}
/*
* Test if full renders are paused
*/
public boolean getPauseFullRadiusRenders() {
return mapManager.getPauseFullRadiusRenders();
}
/*
* Pause update render processing
* @param dopause - true to pause, false to unpause
*/
public void setPauseUpdateRenders(boolean dopause) {
mapManager.setPauseUpdateRenders(dopause);
}
/*
* Test if update renders are paused
*/
public boolean getPauseUpdateRenders() {
return mapManager.getPauseUpdateRenders();
}
/**
* Set player visibility
* @param player - player
* @param is_visible - true if visible, false if hidden
*/
public void setPlayerVisiblity(Player player, boolean is_visible) {
playerList.setVisible(player.getName(), is_visible);
}
/**
* Test if player is visible
* @return true if visible, false if not
*/
public boolean getPlayerVisbility(Player player) {
return playerList.isVisiblePlayer(player);
}
/**
* Post message from player to web
* @param player - player
* @param message - message text
*/
public void postPlayerMessageToWeb(Player player, String message) {
if(mapManager != null)
mapManager.pushUpdate(new Client.ChatMessage("player", "", player.getDisplayName(), message, player.getName()));
}
/**
* Post join/quit message for player to web
* @param player - player
* @param isjoin - if true, join message; if false, quit message
*/
public void postPlayerJoinQuitToWeb(Player player, boolean isjoin) {
if((mapManager != null) && (playerList != null) && (playerList.isVisiblePlayer(player))) {
if(isjoin)
mapManager.pushUpdate(new Client.PlayerJoinMessage(player.getDisplayName(), player.getName()));
else
mapManager.pushUpdate(new Client.PlayerQuitMessage(player.getDisplayName(), player.getName()));
}
}
}
@@ -1,31 +0,0 @@
package org.dynmap;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
/**
* Custom bukkit event, corresponding to the receiving of a web-chat message from a web UI user
*/
public class DynmapWebChatEvent extends Event implements Cancellable {
private String source;
private String name;
private String message;
private boolean cancelled;
public DynmapWebChatEvent(String source, String name, String message) {
super("org.dynmap.DynmapWebChatEvent");
this.source = source;
this.name = name;
this.message = message;
this.cancelled = false;
}
public boolean isCancelled() { return cancelled; }
public void setCancelled(boolean cancel) { cancelled = cancel; }
public String getSource() { return source; }
public String getName() { return name; }
public String getMessage() { return message; }
}
+15 -7
View File
@@ -388,6 +388,7 @@ public class DynmapWorld {
int width = 128, height = 128;
BufferedImage zIm = null;
DynmapBufferedImage kzIm = null;
boolean blank = true;
int[] argb = new int[width*height];
int step = pd.stepsize << pd.zoomlevel;
int ztx = tx;
@@ -415,6 +416,7 @@ public class DynmapWorld {
if(im != null) {
im.getRGB(0, 0, width, height, argb, 0, width); /* Read data */
im.flush();
blank = false;
/* Do binlinear scale to 64x64 */
int off = 0;
for(int y = 0; y < height; y += 2) {
@@ -434,17 +436,15 @@ public class DynmapWorld {
/* blit scaled rendered tile onto zoom-out tile */
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width);
}
else if((pd.background != 0) && (pd.fmt != ImageFormat.FORMAT_PNG)) {
else {
Arrays.fill(argb, pd.background);
/* blit scaled rendered tile onto zoom-out tile */
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width);
}
}
else if((pd.background != 0) && (pd.fmt != ImageFormat.FORMAT_PNG)) {
else {
Arrays.fill(argb, pd.background);
/* blit scaled rendered tile onto zoom-out tile */
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width);
}
/* blit scaled rendered tile onto zoom-out tile */
zIm.setRGB(((i>>1) != 0)?0:width/2, (i & 1) * height/2, width/2, height/2, argb, 0, width);
}
FileLockManager.getWriteLock(zf);
try {
@@ -456,7 +456,15 @@ public class DynmapWorld {
int tilex = ztx/step/2;
int tiley = zty/step/2;
String key = world.getName()+".z"+pd.zoomprefix+pd.baseprefix;
if((!zf.exists()) || (crc != mm.hashman.getImageHashCode(key, null, tilex, tiley))) {
if(blank) {
if(zf.exists()) {
zf.delete();
hashman.updateHashCode(key, null, tilex, tiley, -1);
MapManager.mapman.pushUpdate(this.world, new Client.Tile(zfname));
enqueueZoomOutUpdate(zf, pd.zoomlevel+1);
}
}
else if((!zf.exists()) || (crc != mm.hashman.getImageHashCode(key, null, tilex, tiley))) {
try {
if(!zf.getParentFile().exists())
zf.getParentFile().mkdirs();
@@ -30,6 +30,7 @@ public class JsonFileClientUpdateComponent extends ClientUpdateComponent {
private HashMap<String,String> useralias = new HashMap<String,String>();
private int aliasindex = 1;
private long last_confighash;
private Charset cs_utf8 = Charset.forName("UTF-8");
public JsonFileClientUpdateComponent(final DynmapPlugin plugin, final ConfigurationNode configuration) {
@@ -41,6 +42,8 @@ public class JsonFileClientUpdateComponent extends ClientUpdateComponent {
@Override
public void run() {
currentTimestamp = System.currentTimeMillis();
if(last_confighash != plugin.getConfigHashcode())
writeConfiguration();
writeUpdates();
if (allowwebchat) {
handleWebChat();
@@ -89,6 +92,7 @@ public class JsonFileClientUpdateComponent extends ClientUpdateComponent {
plugin.events.trigger("buildclientconfiguration", clientConfiguration);
outputFile = getStandaloneFile("dynmap_config.json");
outputTempFile = getStandaloneFile("dynmap_config.json.new");
last_confighash = plugin.getConfigHashcode();
int retrycnt = 0;
boolean done = false;
+155 -46
View File
@@ -1,6 +1,7 @@
package org.dynmap;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -18,9 +19,10 @@ import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.command.CommandSender;
@@ -28,7 +30,6 @@ import org.dynmap.DynmapPlugin.CompassMode;
import org.dynmap.DynmapWorld.AutoGenerateOption;
import org.dynmap.debug.Debug;
import org.dynmap.hdmap.HDMapManager;
import org.dynmap.utils.LegacyMapChunkCache;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.NewMapChunkCache;
import org.dynmap.utils.SnapshotCache;
@@ -49,6 +50,10 @@ public class MapManager {
private int progressinterval = 100;
private boolean saverestorepending = true;
private boolean hideores = false;
private boolean usenormalpriority = false;
private boolean pauseupdaterenders = false;
private boolean pausefullrenders = false;
private int zoomout_period = DEFAULT_ZOOMOUT_PERIOD; /* Zoom-out tile processing period, in seconds */
/* Which fullrenders are active */
@@ -58,6 +63,16 @@ public class MapManager {
private Object loadlock = new Object();
private int chunks_in_cur_tick = 0;
private long cur_tick;
/* Chunk load performance numbers */
AtomicInteger chunk_caches_created = new AtomicInteger(0);
AtomicInteger chunk_caches_attempted = new AtomicInteger(0);
AtomicLong total_chunk_cache_loadtime_ns = new AtomicLong(0);
AtomicInteger chunks_read = new AtomicInteger(0);;
AtomicInteger chunks_attempted = new AtomicInteger(0);
AtomicLong total_loadtime_ns = new AtomicLong(0L);
AtomicLong total_exceptions = new AtomicLong(0L);
AtomicInteger ticklistcalls = new AtomicInteger(0);
/* Tile hash manager */
public TileHashManager hashman;
@@ -103,7 +118,8 @@ public class MapManager {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
t.setPriority(Thread.MIN_PRIORITY);
if(!mapman.usenormalpriority)
t.setPriority(Thread.MIN_PRIORITY);
t.setName("Dynmap Render Thread");
return t;
}
@@ -182,6 +198,8 @@ public class MapManager {
String rendertype;
boolean cancelled;
String mapname;
AtomicLong total_render_ns = new AtomicLong(0L);
AtomicInteger rendercalls = new AtomicInteger(0);
/* Full world, all maps render */
FullWorldRenderState(DynmapWorld dworld, Location l, CommandSender sender, String mapname) {
@@ -338,21 +356,39 @@ public class MapManager {
return;
}
if(tile0 == null) { /* Not single tile render */
if(pausefullrenders) { /* Update renders are paused? */
scheduleDelayedJob(this, 20*5); /* Delay 5 seconds and retry */
return;
}
/* If render queue is empty, start next map */
if(renderQueue.isEmpty()) {
if(map_index >= 0) { /* Finished a map? */
double msecpertile = (double)timeaccum / (double)((rendercnt>0)?rendercnt:1)/(double)activemapcnt;
if(activemapcnt > 1)
sendMessage(rendertype + " of maps [" + activemaps + "] of '" +
world.world.getName() + "' completed - " + rendercnt + " tiles rendered each (" + String.format("%.2f", msecpertile) + " msec/map-tile).");
double rendtime = total_render_ns.doubleValue() * 0.000001 / rendercalls.get();
if(activemapcnt > 1)
sendMessage(String.format("%s of maps [%s] of '%s' completed - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.world.getName(), rendercnt, msecpertile, rendtime));
else
sendMessage(rendertype + " of map '" + activemaps + "' of '" +
world.world.getName() + "' completed - " + rendercnt + " tiles rendered (" + String.format("%.2f", msecpertile) + " msec/tile).");
sendMessage(String.format("%s of map '%s' of '%s' completed - %d tiles rendered (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.world.getName(), rendercnt, msecpertile, rendtime));
/* Now, if fullrender, use the render bitmap to purge obsolete tiles */
if(cxmin == Integer.MIN_VALUE) {
if(activemapcnt == 1) {
map.purgeOldTiles(world, rendered);
}
else {
for(MapType mt : map.getMapsSharingRender(world)) {
mt.purgeOldTiles(world, rendered);
}
}
}
}
found.clear();
rendered.clear();
rendercnt = 0;
timeaccum = 0;
total_render_ns.set(0);
rendercalls.set(0);
/* Advance to next unrendered map */
while(map_index < world.maps.size()) {
map_index++; /* Move to next one */
@@ -432,6 +468,10 @@ public class MapManager {
}
}
else { /* Else, single tile render */
if(pauseupdaterenders) {
scheduleDelayedJob(this, 5*20); /* Retry after 5 seconds */
return;
}
tile = tile0;
}
World w = world.world;
@@ -507,12 +547,21 @@ public class MapManager {
if(!good) requiredChunks = Collections.emptyList();
}
/* Fetch chunk cache from server thread */
long clt0 = System.nanoTime();
MapChunkCache cache = createMapChunkCache(world, requiredChunks, tile.isBlockTypeDataNeeded(),
tile.isHightestBlockYDataNeeded(), tile.isBiomeDataNeeded(),
tile.isRawBiomeDataNeeded());
total_chunk_cache_loadtime_ns.addAndGet(System.nanoTime() - clt0);
chunk_caches_attempted.incrementAndGet();
if(cache == null) {
return false; /* Cancelled/aborted */
}
/* Update stats */
chunk_caches_created.incrementAndGet();
chunks_read.addAndGet(cache.getChunksLoaded());
chunks_attempted.addAndGet(cache.getChunkLoadsAttempted());
total_loadtime_ns.addAndGet(cache.getTotalRuntimeNanos());
total_exceptions.addAndGet(cache.getExceptionCount());
if(tile0 != null) { /* Single tile? */
if(cache.isEmpty() == false)
tile.render(cache, null);
@@ -522,13 +571,14 @@ public class MapManager {
tileQueue.remove(tile);
/* Switch to not checking if rendered tile is blank - breaks us on skylands, where tiles can be nominally blank - just work off chunk cache empty */
if (cache.isEmpty() == false) {
long rt0 = System.nanoTime();
tile.render(cache, mapname);
total_render_ns.addAndGet(System.nanoTime()-rt0);
rendercalls.incrementAndGet();
synchronized(lock) {
// found.setFlag(tile.tileOrdinalX(),tile.tileOrdinalY(),false);
rendered.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), true);
for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!found.getFlag(adjTile.tileOrdinalX(),adjTile.tileOrdinalY()) &&
!rendered.getFlag(adjTile.tileOrdinalX(),adjTile.tileOrdinalY())) {
if (!found.getFlag(adjTile.tileOrdinalX(),adjTile.tileOrdinalY())) {
found.setFlag(adjTile.tileOrdinalX(), adjTile.tileOrdinalY(), true);
renderQueue.add(adjTile);
}
@@ -536,18 +586,18 @@ public class MapManager {
}
}
synchronized(lock) {
// found.setFlag(tile.tileOrdinalX(), tile.tileOrdinalY(), false);
if(!cache.isEmpty()) {
rendercnt++;
timeaccum += System.currentTimeMillis() - tstart;
if((rendercnt % progressinterval) == 0) {
double rendtime = total_render_ns.doubleValue() * 0.000001 / rendercalls.get();
double msecpertile = (double)timeaccum / (double)rendercnt / (double)activemapcnt;
if(activemapcnt > 1)
sendMessage(rendertype + " of maps [" + activemaps + "] of '" +
w.getName() + "' in progress - " + rendercnt + " tiles rendered each (" + String.format("%.2f", msecpertile) + " msec/map-tile).");
sendMessage(String.format("%s of maps [%s] of '%s' in progress - %d tiles rendered each (%.2f msec/map-tile, %.2f msec per render)",
rendertype, activemaps, world.world.getName(), rendercnt, msecpertile, rendtime));
else
sendMessage(rendertype + " of map '" + activemaps + "' of '" +
w.getName() + "' in progress - " + rendercnt + " tiles rendered (" + String.format("%.2f", msecpertile) + " msec/tile).");
sendMessage(String.format("%s of map '%s' of '%s' in progress - %d tiles rendered (%.2f msec/tile, %.2f msec per render)",
rendertype, activemaps, world.world.getName(), rendercnt, msecpertile, rendtime));
}
}
}
@@ -617,6 +667,9 @@ public class MapManager {
/* Get block hiding data, if any */
hideores = configuration.getBoolean("hideores", false);
/* See what priority to use */
usenormalpriority = configuration.getBoolean("usenormalthreadpriority", false);
/* Clear color scheme */
ColorScheme.reset();
@@ -642,7 +695,9 @@ public class MapManager {
},
(int) (configuration.getDouble("renderinterval", 0.5) * 1000),
configuration.getInteger("renderacceleratethreshold", 30),
(int)(configuration.getDouble("renderaccelerateinterval", 0.2) * 1000), (Runtime.getRuntime().availableProcessors()+1)/2);
(int)(configuration.getDouble("renderaccelerateinterval", 0.2) * 1000),
configuration.getInteger("tiles-rendered-at-once", (Runtime.getRuntime().availableProcessors()+1)/2),
usenormalpriority);
/* On dedicated thread, so default to no delays */
timeslice_int = (long)(configuration.getDouble("timesliceinterval", 0.0) * 1000);
@@ -1021,6 +1076,10 @@ public class MapManager {
if(saverestorepending)
savePending();
if(sscache != null) {
sscache.cleanup();
sscache = null;
}
}
private HashMap<World, File> worldTileDirectories = new HashMap<World, File>();
@@ -1037,44 +1096,36 @@ public class MapManager {
return new File(worldTileDirectory, tile.getFilename());
}
public void pushUpdate(Object update) {
for(DynmapWorld world : getWorlds()) {
world.updates.pushUpdate(update);
public void pushUpdate(Client.Update update) {
int sz = worlds.size();
for(int i = 0; i < sz; i++) {
worlds.get(i).updates.pushUpdate(update);
}
}
public void pushUpdate(World world, Object update) {
public void pushUpdate(World world, Client.Update update) {
pushUpdate(world.getName(), update);
}
public void pushUpdate(String worldName, Object update) {
public void pushUpdate(String worldName, Client.Update update) {
DynmapWorld world = getWorld(worldName);
if(world != null)
world.updates.pushUpdate(update);
}
public Object[] getWorldUpdates(String worldName, long since) {
public Client.Update[] getWorldUpdates(String worldName, long since) {
DynmapWorld world = getWorld(worldName);
if (world == null)
return new Object[0];
return new Client.Update[0];
return world.updates.getUpdatedObjects(since);
}
private static boolean use_legacy = false;
/**
* Render processor helper - used by code running on render threads to request chunk snapshot cache from server/sync thread
*/
public MapChunkCache createMapChunkCache(DynmapWorld w, List<DynmapChunk> chunks,
boolean blockdata, boolean highesty, boolean biome, boolean rawbiome) {
MapChunkCache c = null;
try {
if(!use_legacy)
c = new NewMapChunkCache();
} catch (NoClassDefFoundError ncdfe) {
use_legacy = true;
}
if(c == null)
c = new LegacyMapChunkCache();
MapChunkCache c = new NewMapChunkCache();
if(w.visibility_limits != null) {
for(MapChunkCache.VisibilityLimit limit: w.visibility_limits) {
c.setVisibleRange(limit);
@@ -1126,18 +1177,16 @@ public class MapManager {
return null;
}
if(delay)
try { Thread.sleep(50); } catch (InterruptedException ix) {}
try { Thread.sleep(25); } catch (InterruptedException ix) {}
}
return c;
}
/**
* Update map tile statistics
*/
public void updateStatistics(MapTile tile, String subtype, boolean rendered, boolean updated, boolean transparent) {
public void updateStatistics(MapTile tile, String prefix, boolean rendered, boolean updated, boolean transparent) {
synchronized(lock) {
String k = tile.getKey();
if(subtype != null)
k += "." + subtype;
String k = tile.getKey(prefix);
MapStats ms = mapstats.get(k);
if(ms == null) {
ms = new MapStats();
@@ -1163,22 +1212,37 @@ public class MapManager {
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);
sender.sendMessage(String.format(" %s: processed=%d, rendered=%d, updated=%d, transparent=%d",
k, ms.loggedcnt, ms.renderedcnt, ms.updatedcnt, 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);
sender.sendMessage(" Cache hit rate: " + sscache.getHitRate() + "%");
sender.sendMessage(" Triggered update queue size: " + tileQueue.size());
sender.sendMessage(String.format(" TOTALS: processed=%d, rendered=%d, updated=%d, transparent=%d",
tot.loggedcnt, tot.renderedcnt, tot.updatedcnt, tot.transparentcnt));
sender.sendMessage(String.format(" Triggered update queue size: %d", tileQueue.size()));
String act = "";
for(String wn : active_renders.keySet())
act += wn + " ";
sender.sendMessage(" Active render jobs: " + act);
sender.sendMessage(String.format(" Active render jobs: %s", act));
/* Chunk load stats */
sender.sendMessage("Chunk Loading Statistics:");
sender.sendMessage(String.format(" Cache hit rate: %.2f%%", sscache.getHitRate()));
int setcnt = chunk_caches_attempted.get();
sender.sendMessage(String.format(" Chunk sets: created=%d, attempted=%d", chunk_caches_created.get(), chunk_caches_attempted.get()));
int readcnt = chunks_read.get();
sender.sendMessage(String.format(" Chunk: loaded=%d, attempted=%d", readcnt, chunks_attempted.get()));
double ns = total_loadtime_ns.doubleValue() * 0.000001; /* Convert to milliseconds */
double chunkloadns = total_chunk_cache_loadtime_ns.doubleValue() * 0.000001;
if(readcnt == 0) readcnt = 1;
if(setcnt == 0) setcnt = 1;
sender.sendMessage(String.format(" Chunk load times: %.2f msec (%.2f msec/chunk)", ns, (ns / readcnt)));
sender.sendMessage(String.format(" Chunk set load times: %.2f msec (%.2f msec/set)", chunkloadns, (chunkloadns / setcnt)));
sender.sendMessage(String.format(" Chunk set delay times: %.2f msec (%.2f msec/set)", chunkloadns-ns, ((chunkloadns-ns) / setcnt)));
sender.sendMessage(String.format(" Chunk set exceptions: %d", total_exceptions.get()));
sender.sendMessage(String.format(" World tick list processing calls: %d", ticklistcalls.get()));
}
/**
* Print trigger statistics command
@@ -1213,6 +1277,14 @@ public class MapManager {
ts.callswithtiles = 0;
ts.tilesqueued = 0;
}
chunk_caches_created.set(0);
chunk_caches_attempted.set(0);
chunks_read.set(0);
chunks_attempted.set(0);
total_loadtime_ns.set(0);
total_chunk_cache_loadtime_ns.set(0);
total_exceptions.set(0);
ticklistcalls.set(0);
}
sscache.resetStats();
sender.sendMessage("Tile Render Statistics reset");
@@ -1251,4 +1323,41 @@ public class MapManager {
}
return id;
}
/*
* Pause full/radius render processing
* @param dopause - true to pause, false to unpause
*/
void setPauseFullRadiusRenders(boolean dopause) {
if(dopause != pausefullrenders) {
pausefullrenders = dopause;
Log.info("Full/radius render pause set to " + dopause);
}
}
/*
* Test if full renders are paused
*/
boolean getPauseFullRadiusRenders() {
return pausefullrenders;
}
/*
* Pause update render processing
* @param dopause - true to pause, false to unpause
*/
void setPauseUpdateRenders(boolean dopause) {
if(dopause != pauseupdaterenders) {
pauseupdaterenders = dopause;
Log.info("Update render pause set to " + dopause);
}
}
/*
* Test if update renders are paused
*/
boolean getPauseUpdateRenders() {
return pauseupdaterenders;
}
public void incExtraTickList() {
ticklistcalls.incrementAndGet();
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ public abstract class MapTile {
@Override
public abstract boolean equals(Object obj);
public abstract String getKey();
public abstract String getKey(String prefix);
public abstract boolean isBiomeDataNeeded();
public abstract boolean isHightestBlockYDataNeeded();
+29
View File
@@ -1,8 +1,11 @@
package org.dynmap;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
import org.bukkit.Location;
import org.dynmap.utils.TileFlags;
import org.json.simple.JSONObject;
public abstract class MapType {
@@ -80,5 +83,31 @@ public abstract class MapType {
* Values correspond to tile X,Y (0), X+step,Y (1), X,Y+step (2), X+step,Y+step (3)
*/
public abstract int[] zoomFileStepSequence();
public void purgeOldTiles(DynmapWorld world, TileFlags rendered) { }
public interface FileCallback {
public void fileFound(File f, File parent, boolean day);
}
protected void walkMapTree(File root, FileCallback cb, boolean day) {
LinkedList<File> dirs = new LinkedList<File>();
String ext = "." + getImageFormat().getFileExt();
dirs.add(root);
while(dirs.isEmpty() == false) {
File dir = dirs.pop();
String[] lst = dir.list();
for(String fn : lst) {
if(fn.equals(".") || fn.equals(".."))
continue;
File f = new File(dir, fn);
if(f.isDirectory()) { /* If directory, add to list to process */
dirs.add(f);
}
else if(fn.endsWith(ext)) { /* Else, if matches suffix */
cb.fileFound(f, dir, day);
}
}
}
}
}
@@ -84,6 +84,7 @@ public class MarkersComponent extends ClientComponent {
offlineset = api.createMarkerSet(OFFLINE_PLAYERS_SETID, configuration.getString("offlinelabel", "Offline"), null, true);
}
offlineset.setHideByDefault(configuration.getBoolean("offlinehidebydefault", true));
offlineset.setMinZoom(configuration.getInteger("offlineminzoom", 0));
offlineicon = api.getMarkerIcon(configuration.getString("offlineicon", "offlineuser"));
+46 -18
View File
@@ -2,6 +2,7 @@ package org.dynmap;
import java.io.File;
import java.io.RandomAccessFile;
import java.util.Arrays;
import java.util.LinkedList;
import java.io.IOException;
import java.util.zip.CRC32;
@@ -48,7 +49,7 @@ public class TileHashManager {
public File getHashFile(File tiledir) {
if(hf == null) {
String k;
int idx = key.indexOf('.'); /* Find first '.' - world name split */
int idx = key.lastIndexOf('.'); /* Find last '.' - world name split (allows dots in world name) */
if(idx > 0)
k = key.substring(0, idx) + File.separatorChar + key.substring(idx+1);
else
@@ -59,26 +60,34 @@ public class TileHashManager {
}
/* Write to file */
public void writeToFile(File tiledir, byte[] crcbuf) {
RandomAccessFile fd = null;
try {
RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "rw");
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());
} finally {
if(fd != null) {
try { fd.close(); } catch (IOException iox) {}
}
}
}
/* Read from file */
public void readFromFile(File tiledir, byte[] crcbuf) {
RandomAccessFile fd = null;
try {
RandomAccessFile fd = new RandomAccessFile(getHashFile(tiledir), "r");
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);
} finally {
if(fd != null) {
try { fd.close(); } catch (IOException iox) {}
}
}
}
/* Read CRC */
@@ -100,8 +109,8 @@ public class TileHashManager {
private static final int MAX_CACHED_TILEHASHFILES = 25;
private Object lock = new Object();
private LRULinkedHashMap<TileHashFile, byte[]> tilehash = new LRULinkedHashMap<TileHashFile, byte[]>(MAX_CACHED_TILEHASHFILES);
private CRC32 crc32 = new CRC32();
private byte[] crcworkbuf = new byte[8192];
private LinkedList<byte[]> crcworkbufs = new LinkedList<byte[]>();
private LinkedList<CRC32> crcs = new LinkedList<CRC32>();
public TileHashManager(File tileroot, boolean enabled) {
tiledir = tileroot;
@@ -130,22 +139,41 @@ public class TileHashManager {
if(!enabled) {
return 0; /* Return value that doesn't match */
}
CRC32 crc32;
byte[] crcworkbuf;
synchronized(lock) {
if(crcworkbuf.length < (4*newbuf.length)){
if(crcworkbufs.isEmpty()) {
crcworkbuf = new byte[4*newbuf.length];
}
for(int i = 0, off = 0; i < newbuf.length; i++) {
int v = newbuf[i];
crcworkbuf[off++] = (byte)v;
crcworkbuf[off++] = (byte)(v>>8);
crcworkbuf[off++] = (byte)(v>>16);
crcworkbuf[off++] = (byte)(v>>24);
else {
crcworkbuf = crcworkbufs.removeFirst();
}
if(crcs.isEmpty()) {
crc32 = new CRC32();
}
else {
crc32 = crcs.removeFirst();
crc32.reset();
}
/* Calculate CRC-32 for buffer */
crc32.reset();
crc32.update(crcworkbuf, 0, 4*newbuf.length);
return crc32.getValue();
}
if(crcworkbuf.length < (4*newbuf.length)){
crcworkbuf = new byte[4*newbuf.length];
}
for(int i = 0, off = 0; i < newbuf.length; i++) {
int v = newbuf[i];
crcworkbuf[off++] = (byte)v;
crcworkbuf[off++] = (byte)(v>>8);
crcworkbuf[off++] = (byte)(v>>16);
crcworkbuf[off++] = (byte)(v>>24);
}
/* Calculate CRC-32 for buffer */
crc32.update(crcworkbuf, 0, 4*newbuf.length);
long v = crc32.getValue();
synchronized(lock) {
crcworkbufs.addFirst(crcworkbuf);
crcs.addFirst(crc32);
}
return v;
}
/* Update hashcode for given tile */
public void updateHashCode(String key, String subtype, int tx, int ty, long newcrc) {
+93 -39
View File
@@ -1,71 +1,125 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.ListIterator;
public class UpdateQueue {
public Object lock = new Object();
private LinkedList<Update> updateQueue = new LinkedList<Update>();
private HashMap<UpdateRec,UpdateRec> updateSet = new HashMap<UpdateRec,UpdateRec>();
private UpdateRec orderedlist = null; /* Oldest to youngest */
private static final long maxUpdateAge = 120000;
private static final long ageOutPeriod = 5000;
private long lastageout = 0;
private static final int maxUpdateAge = 120000;
private static class UpdateRec {
Client.Update u;
UpdateRec next;
UpdateRec prev;
@Override
public boolean equals(Object o) {
if(o instanceof UpdateRec)
return u.equals(((UpdateRec)o).u);
return false;
}
@Override
public int hashCode() {
return u.hashCode();
}
}
private void doAgeOut(long now) {
/* If we're due */
if((now < lastageout) || (now > (lastageout + ageOutPeriod))) {
lastageout = now;
long deadline = now - maxUpdateAge;
while((orderedlist != null) && (orderedlist.u.timestamp < deadline)) {
UpdateRec r = orderedlist;
public void pushUpdate(Object obj) {
updateSet.remove(r); /* Remove record from set */
if(r.next == r) {
orderedlist = null;
}
else {
orderedlist = r.next;
r.next.prev = r.prev;
r.prev.next = r.next;
}
r.next = r.prev = null;
}
}
}
public void pushUpdate(Client.Update obj) {
synchronized (lock) {
/* Do inside lock - prevent delay between time and actual work */
long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge;
ListIterator<Update> i = updateQueue.listIterator(0);
while (i.hasNext()) {
Update u = i.next();
if (u.time < deadline || u.obj == obj)
i.remove();
doAgeOut(now); /* Consider age out */
UpdateRec r = new UpdateRec();
r.u = obj;
UpdateRec oldr = updateSet.remove(r); /* Try to remove redundant event */
if(oldr != null) { /* If found, remove from ordered list too */
if(oldr.next == oldr) { /* Only one? */
orderedlist = null;
}
else {
if(orderedlist == oldr) { /* We're oldest? */
orderedlist = oldr.next;
}
oldr.next.prev = oldr.prev;
oldr.prev.next = oldr.next;
}
oldr.next = oldr.prev = null;
}
updateSet.put(r, r);
/* Add to end of ordered list */
if(orderedlist == null) {
orderedlist = r;
r.next = r.prev = r;
}
else {
r.next = orderedlist;
r.prev = orderedlist.prev;
r.next.prev = r.prev.next = r;
}
updateQueue.addLast(new Update(now, obj));
}
}
private ArrayList<Object> tmpupdates = new ArrayList<Object>();
private ArrayList<Client.Update> tmpupdates = new ArrayList<Client.Update>();
public Object[] getUpdatedObjects(long since) {
Object[] updates;
public Client.Update[] getUpdatedObjects(long since) {
Client.Update[] updates;
synchronized (lock) {
long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge;
doAgeOut(now); /* Consider age out */
tmpupdates.clear();
Iterator<Update> it = updateQueue.descendingIterator();
while (it.hasNext()) {
Update u = it.next();
if (u.time >= since) {
// Tile is new.
tmpupdates.add(u.obj);
} else if (u.time < deadline) {
// Tile is too old, removing this one (will eventually decrease).
it.remove();
break;
} else {
// Tile is old, but not old enough for removal.
break;
if(orderedlist != null) {
UpdateRec r = orderedlist.prev; /* Get newest */
while(r != null) {
if(r.u.timestamp >= since) {
tmpupdates.add(r.u);
if(r == orderedlist)
r = null;
else
r = r.prev;
}
else {
r = null;
}
}
}
// Reverse output.
updates = new Object[tmpupdates.size()];
updates = new Client.Update[tmpupdates.size()];
for (int i = 0; i < updates.length; i++) {
updates[i] = tmpupdates.get(updates.length-1-i);
}
}
return updates;
}
public static class Update {
public long time;
public Object obj;
public Update(long time, Object obj) {
this.time = time;
this.obj = obj;
}
}
}
+7 -7
View File
@@ -303,7 +303,7 @@ public class FlatMap extends MapType {
boolean tile_update = false;
FileLockManager.getWriteLock(outputFile);
try {
if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), null, t.x, t.y))) {
if((!outputFile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(prefix), null, t.x, t.y))) {
/* Wrap buffer as buffered image */
Debug.debug("saving image " + outputFile.getPath());
if(!outputFile.getParentFile().exists())
@@ -316,7 +316,7 @@ public class FlatMap extends MapType {
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);
hashman.updateHashCode(tile.getKey(prefix), null, t.x, t.y, crc);
tile.getDynmapWorld().enqueueZoomOutUpdate(outputFile);
tile_update = true;
}
@@ -327,7 +327,7 @@ public class FlatMap extends MapType {
FileLockManager.releaseWriteLock(outputFile);
DynmapBufferedImage.freeBufferedImage(im);
}
MapManager.mapman.updateStatistics(tile, null, true, tile_update, !rendered);
MapManager.mapman.updateStatistics(tile, prefix, true, tile_update, !rendered);
/* If day too, handle it */
if(night_and_day) {
@@ -335,7 +335,7 @@ public class FlatMap extends MapType {
crc = hashman.calculateTileHash(argb_buf_day);
FileLockManager.getWriteLock(dayfile);
try {
if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(), "day", t.x, t.y))) {
if((!dayfile.exists()) || (crc != hashman.getImageHashCode(tile.getKey(prefix), "day", t.x, t.y))) {
Debug.debug("saving image " + dayfile.getPath());
if(!dayfile.getParentFile().exists())
dayfile.getParentFile().mkdirs();
@@ -347,7 +347,7 @@ public class FlatMap extends MapType {
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);
hashman.updateHashCode(tile.getKey(prefix), "day", t.x, t.y, crc);
tile.getDynmapWorld().enqueueZoomOutUpdate(dayfile);
tile_update = true;
}
@@ -359,7 +359,7 @@ public class FlatMap extends MapType {
FileLockManager.releaseWriteLock(dayfile);
DynmapBufferedImage.freeBufferedImage(im_day);
}
MapManager.mapman.updateStatistics(tile, "day", true, tile_update, !rendered);
MapManager.mapman.updateStatistics(tile, prefix+"_day", true, tile_update, !rendered);
}
return rendered;
@@ -574,7 +574,7 @@ public class FlatMap extends MapType {
}
@Override
public String getKey() {
public String getKey(String prefix) {
return world.world.getName() + "." + map.getPrefix();
}
@@ -21,8 +21,8 @@ public class DefaultHDLighting implements HDLighting {
/* Apply lighting to given pixel colors (1 outcolor if normal, 2 if night/day) */
public void applyLighting(HDPerspectiveState ps, HDShaderState ss, Color incolor, Color[] outcolor) {
for(Color oc: outcolor)
oc.setColor(incolor);
for(int i = 0; i < outcolor.length; i++)
outcolor[i].setColor(incolor);
}
/* Test if Biome Data is needed for this renderer */
@@ -117,8 +117,8 @@ public class DefaultHDShader implements HDShader {
* Reset renderer state for new ray
*/
public void reset(HDPerspectiveState ps) {
for(Color c: color)
c.setTransparent();
for(int i = 0; i < color.length; i++)
color[i].setTransparent();
pixelodd = (ps.getPixelX() & 0x3) + (ps.getPixelY()<<1);
}
@@ -169,8 +169,8 @@ public class DefaultHDShader implements HDShader {
lighting.applyLighting(ps, this, c, tmpcolor);
/* If we got alpha from subblock model, use it instead */
if(subalpha >= 0) {
for(Color clr : tmpcolor)
clr.setAlpha(Math.max(subalpha,clr.getAlpha()));
for(int j = 0; j < tmpcolor.length; j++)
tmpcolor[j].setAlpha(Math.max(subalpha,tmpcolor[j].getAlpha()));
}
/* Blend color with accumulated color (weighted by alpha) */
if(!transparency) { /* No transparency support */
@@ -264,11 +264,19 @@ public class HDBlockModels {
row = new short[16][];
blockmodels[m.blockid] = row;
}
short[] smod = null;
for(int i = 0; i < 16; i++) {
if((m.databits & (1 << i)) != 0) {
if(smod == null) smod = m.getScaledMap(scale);
row[i] = smod;
short[] smod = m.getScaledMap(scale);
/* See if scaled model is full block : much faster to not use it if it is */
if(smod != null) {
boolean keep = false;
for(int i = 0; (!keep) && (i < smod.length); i++) {
if(smod[i] == 0) keep = true;
}
if(keep) {
for(int i = 0; i < 16; i++) {
if((m.databits & (1 << i)) != 0) {
row[i] = smod;
}
}
}
}
}
+67
View File
@@ -7,17 +7,23 @@ import java.io.File;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.Location;
import org.dynmap.Client;
import org.dynmap.ConfigurationNode;
import org.dynmap.DynmapChunk;
import org.dynmap.DynmapPlugin;
import org.dynmap.DynmapWorld;
import org.dynmap.Log;
import org.dynmap.MapManager;
import org.dynmap.MapTile;
import org.dynmap.MapType;
import org.dynmap.debug.Debug;
import org.dynmap.kzedmap.MapTileRenderer;
import org.dynmap.utils.MapChunkCache;
import org.dynmap.utils.TileFlags;
import org.json.simple.JSONObject;
import com.avaje.ebean.text.StringParser;
public class HDMap extends MapType {
private String name;
@@ -116,6 +122,10 @@ public class HDMap extends MapType {
if(c != null) {
bgcolornight = parseColor(c);
}
if(imgformat != ImageFormat.FORMAT_PNG) { /* If JPG, set background color opacity */
bgcolorday |= 0xFF000000;
bgcolornight |= 0xFF000000;
}
}
public HDShader getShader() { return shader; }
@@ -270,4 +280,61 @@ public class HDMap extends MapType {
public int getBackgroundARGBNight() {
return bgcolornight;
}
private HDMapTile fileToTile(DynmapWorld world, File f) {
String n = f.getName();
n = n.substring(0, n.lastIndexOf('.'));
if(n == null) return null;
String[] nt = n.split("_");
if(nt.length != 2) return null;
int xx, zz;
try {
xx = Integer.parseInt(nt[0]);
zz = Integer.parseInt(nt[1]);
} catch (NumberFormatException nfx) {
return null;
}
return new HDMapTile(world, perspective, xx, zz);
}
public void purgeOldTiles(final DynmapWorld world, final TileFlags rendered) {
File basedir = new File(world.worldtilepath, prefix); /* Get base directory for map */
FileCallback cb = new FileCallback() {
public void fileFound(File f, File parent, boolean day) {
String n = f.getName();
if(n.startsWith("z")) { /* If zoom file */
if(n.startsWith("z_")) { /* First tier of zoom? */
File ff = new File(parent, n.substring(2)); /* Make file for render tier, and drive update */
HDMapTile tile = fileToTile(world, ff); /* Parse it */
if(tile == null) return;
if(rendered.getFlag(tile.tx, tile.ty) || rendered.getFlag(tile.tx+1, tile.ty) ||
rendered.getFlag(tile.tx, tile.ty-1) || rendered.getFlag(tile.tx+1, tile.ty-1))
return;
world.enqueueZoomOutUpdate(ff);
}
return;
}
HDMapTile tile = fileToTile(world, f);
if(tile == null) return;
if(rendered.getFlag(tile.tx, tile.ty)) { /* If we rendered this tile, its good */
return;
}
Debug.debug("clean up " + f.getPath());
/* Otherwise, delete tile */
f.delete();
/* Push updates, clear hash code, and signal zoom tile update */
MapManager.mapman.pushUpdate(world.world,
new Client.Tile(day?tile.getDayFilename(prefix, getImageFormat()):tile.getFilename(prefix, getImageFormat())));
MapManager.mapman.hashman.updateHashCode(tile.getKey(prefix), day?"day":null, tile.tx, tile.ty, -1);
world.enqueueZoomOutUpdate(f);
}
};
walkMapTree(basedir, cb, false);
if(lighting.isNightAndDayEnabled()) {
basedir = new File(world.worldtilepath, prefix+"_day");
walkMapTree(basedir, cb, true);
}
}
}

Some files were not shown because too many files have changed in this diff Show More