Made updates in JSON format. Combined chat and tile queues into one UpdateQueue. Fixed UpdateQueue.

This commit is contained in:
FrozenCow 2011-02-06 03:00:51 +01:00
parent 7c257af454
commit 3e398e9124
18 changed files with 208 additions and 239 deletions

View File

@ -1,63 +0,0 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.LinkedList;
import org.bukkit.event.player.PlayerChatEvent;
public class ChatQueue {
public class ChatMessage {
public long time;
public String playerName;
public String message;
public ChatMessage(PlayerChatEvent event) {
time = System.currentTimeMillis();
playerName = event.getPlayer().getName();
message = event.getMessage();
}
}
/* a list of recent chat message */
private LinkedList<ChatMessage> messageQueue;
/* remember up to this old chat messages (ms) */
private static final int maxChatAge = 120000;
public ChatQueue() {
messageQueue = new LinkedList<ChatMessage>();
}
/* put a chat message in the queue */
public void pushChatMessage(PlayerChatEvent event) {
synchronized (MapManager.lock) {
messageQueue.add(new ChatMessage(event));
}
}
public ChatMessage[] getChatMessages(long cutoff) {
ArrayList<ChatMessage> queue = new ArrayList<ChatMessage>();
ArrayList<ChatMessage> updateList = new ArrayList<ChatMessage>();
queue.addAll(messageQueue);
long now = System.currentTimeMillis();
long deadline = now - maxChatAge;
synchronized (MapManager.lock) {
for (ChatMessage message : queue) {
if (message.time < deadline) {
messageQueue.remove(message);
} else if (message.time >= cutoff) {
updateList.add(message);
}
}
}
ChatMessage[] messages = new ChatMessage[updateList.size()];
updateList.toArray(messages);
return messages;
}
}

View File

@ -0,0 +1,43 @@
package org.dynmap;
public class Client {
public static class Update {
public long timestamp;
public long servertime;
public Player[] players;
public Object[] updates;
}
public static class Player {
public String type = "player";
public String name;
public double x, y, z;
public Player(String name, double x, double y, double z) {
this.name = name;
this.x = x;
this.y = y;
this.z = z;
}
}
public static class ChatMessage {
public String type = "chat";
public String playerName;
public String message;
public ChatMessage(String playerName, String message) {
this.playerName = playerName;
this.message = message;
}
}
public static class Tile {
public String type = "tile";
public String name;
public Tile(String name) {
this.name = name;
}
}
}

View File

@ -58,6 +58,6 @@ public class DynmapPlayerListener extends PlayerListener {
* Relevant event details * Relevant event details
*/ */
public void onPlayerChat(PlayerChatEvent event) { public void onPlayerChat(PlayerChatEvent event) {
mgr.addChatEvent(event); mgr.updateQueue.pushUpdate(new Client.ChatMessage(event.getPlayer().getName(), event.getMessage()));
} }
} }

View File

@ -8,9 +8,9 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.event.player.PlayerChatEvent;
import org.bukkit.util.config.ConfigurationNode; import org.bukkit.util.config.ConfigurationNode;
import org.dynmap.debug.Debugger; import org.dynmap.debug.Debugger;
@ -21,7 +21,7 @@ public class MapManager extends Thread {
private Debugger debugger; private Debugger debugger;
private MapType[] maps; private MapType[] maps;
public StaleQueue staleQueue; public StaleQueue staleQueue;
public ChatQueue chatQueue; public UpdateQueue updateQueue;
public PlayerList playerList; public PlayerList playerList;
/* lock for our data structures */ /* lock for our data structures */
@ -65,7 +65,7 @@ public class MapManager extends Thread {
this.world = world; this.world = world;
this.debugger = debugger; this.debugger = debugger;
this.staleQueue = new StaleQueue(); this.staleQueue = new StaleQueue();
this.chatQueue = new ChatQueue(); this.updateQueue = new UpdateQueue();
tileDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("tilespath", "web/tiles")); tileDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("tilespath", "web/tiles"));
webDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("webpath", "web")); webDirectory = combinePaths(DynmapPlugin.dataRoot, configuration.getString("webpath", "web"));
@ -110,7 +110,7 @@ public class MapManager extends Thread {
debugger.debug("renderQueue: " + renderQueue.size() + "/" + found.size()); debugger.debug("renderQueue: " + renderQueue.size() + "/" + found.size());
if (map.render(tile)) { if (map.render(tile)) {
found.remove(tile); found.remove(tile);
staleQueue.onTileUpdated(tile); updateQueue.pushUpdate(new Client.Tile(tile.getName()));
for (MapTile adjTile : map.getAdjecentTiles(tile)) { for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!(found.contains(adjTile) || map.isRendered(adjTile))) { if (!(found.contains(adjTile) || map.isRendered(adjTile))) {
found.add(adjTile); found.add(adjTile);
@ -244,7 +244,7 @@ public class MapManager extends Thread {
debugger.debug("Rendering tile " + t + "..."); debugger.debug("Rendering tile " + t + "...");
boolean isNonEmptyTile = t.getMap().render(t); boolean isNonEmptyTile = t.getMap().render(t);
staleQueue.onTileUpdated(t); updateQueue.pushUpdate(new Client.Tile(t.getName()));
if (isNonEmptyTile) if (isNonEmptyTile)
handleFullMapRender(t); handleFullMapRender(t);
@ -281,8 +281,4 @@ public class MapManager extends Thread {
debugger.debug("Invalidating tile " + tile.getName()); debugger.debug("Invalidating tile " + tile.getName());
staleQueue.pushStaleTile(tile); staleQueue.pushStaleTile(tile);
} }
public void addChatEvent(PlayerChatEvent event) {
chatQueue.pushChatMessage(event);
}
} }

View File

@ -1,10 +1,7 @@
package org.dynmap; package org.dynmap;
import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.ListIterator;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Set; import java.util.Set;
@ -13,16 +10,9 @@ public class StaleQueue {
private LinkedList<MapTile> staleTilesQueue; private LinkedList<MapTile> staleTilesQueue;
private Set<MapTile> staleTiles; private Set<MapTile> staleTiles;
/* this list stores the tile updates */
public LinkedList<TileUpdate> tileUpdates = null;
/* remember up to this old tile updates (ms) */
private static final int maxTileAge = 60000;
public StaleQueue() { public StaleQueue() {
staleTilesQueue = new LinkedList<MapTile>(); staleTilesQueue = new LinkedList<MapTile>();
staleTiles = new HashSet<MapTile>(); staleTiles = new HashSet<MapTile>();
tileUpdates = new LinkedList<TileUpdate>();
} }
public int size() { public int size() {
@ -57,47 +47,4 @@ public class StaleQueue {
} }
} }
} }
public void onTileUpdated(MapTile t) {
long now = System.currentTimeMillis();
long deadline = now - maxTileAge;
synchronized (MapManager.lock) {
ListIterator<TileUpdate> it = tileUpdates.listIterator(0);
while (it.hasNext()) {
TileUpdate tu = it.next();
if (tu.at < deadline || tu.tile == t)
it.remove();
}
tileUpdates.addLast(new TileUpdate(now, t));
}
}
private ArrayList<TileUpdate> tmpupdates = new ArrayList<TileUpdate>();
public TileUpdate[] getTileUpdates(long cutoff) {
long now = System.currentTimeMillis();
long deadline = now - maxTileAge;
TileUpdate[] updates;
synchronized (MapManager.lock) {
tmpupdates.clear();
Iterator<TileUpdate> it = tileUpdates.descendingIterator();
while (it.hasNext()) {
TileUpdate tu = it.next();
if (tu.at >= cutoff) { // Tile is new.
tmpupdates.add(tu);
} else if (tu.at < 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;
}
}
updates = new TileUpdate[tmpupdates.size()];
tmpupdates.toArray(updates);
}
return updates;
}
} }

View File

@ -1,13 +0,0 @@
package org.dynmap;
/* this class stores a tile update */
public class TileUpdate {
public long at;
public MapTile tile;
public TileUpdate(long at, MapTile tile) {
this.at = at;
this.tile = tile;
}
}

View File

@ -0,0 +1,70 @@
package org.dynmap;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
public class UpdateQueue {
public Object lock = new Object();
private LinkedList<Update> updateQueue = new LinkedList<Update>();
private static final int maxUpdateAge = 120000;
public void pushUpdate(Object obj) {
long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge;
synchronized (lock) {
ListIterator<Update> i = updateQueue.listIterator(0);
while (i.hasNext()) {
Update u = i.next();
if (u.time < deadline || u.obj == obj)
i.remove();
}
updateQueue.addLast(new Update(now, obj));
}
}
private ArrayList<Object> tmpupdates = new ArrayList<Object>();
public Object[] getUpdatedObjects(long since) {
long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge;
Object[] updates;
synchronized (lock) {
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;
}
}
// Reverse output.
updates = new Object[tmpupdates.size()];
for (int i = 0; i < updates.length; i++) {
updates[i] = tmpupdates.get(updates.length-1-i);
}
}
return updates;
}
public class Update {
public long time;
public Object obj;
public Update(long time, Object obj) {
this.time = time;
this.obj = obj;
}
}
}

View File

@ -2,6 +2,7 @@ package org.dynmap.kzedmap;
import java.awt.Color; import java.awt.Color;
import java.util.Map; import java.util.Map;
import org.bukkit.World; import org.bukkit.World;
import org.dynmap.debug.Debugger; import org.dynmap.debug.Debugger;

View File

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Scanner; import java.util.Scanner;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.dynmap.DynmapChunk; import org.dynmap.DynmapChunk;

View File

@ -1,6 +1,7 @@
package org.dynmap.kzedmap; package org.dynmap.kzedmap;
import java.util.logging.Logger; import java.util.logging.Logger;
import org.dynmap.MapTile; import org.dynmap.MapTile;
public class KzedMapTile extends MapTile { public class KzedMapTile extends MapTile {

View File

@ -1,6 +1,7 @@
package org.dynmap.kzedmap; package org.dynmap.kzedmap;
import java.awt.image.BufferedImage; import java.awt.image.BufferedImage;
import org.dynmap.MapTile; import org.dynmap.MapTile;
public class KzedZoomedMapTile extends MapTile { public class KzedZoomedMapTile extends MapTile {

View File

@ -6,7 +6,9 @@ import java.awt.image.BufferedImage;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import javax.imageio.ImageIO; import javax.imageio.ImageIO;
import org.dynmap.debug.Debugger; import org.dynmap.debug.Debugger;
public class ZoomedTileRenderer { public class ZoomedTileRenderer {

View File

@ -61,15 +61,16 @@ public class HttpServerConnection extends Thread {
sb.append(response.statusCode); sb.append(response.statusCode);
sb.append(" "); sb.append(" ");
sb.append(response.statusMessage); sb.append(response.statusMessage);
sb.append("\n"); sb.append("\r\n");
for (Entry<String, String> field : response.fields.entrySet()) { for (Entry<String, String> field : response.fields.entrySet()) {
sb.append(field.getKey()); sb.append(field.getKey());
sb.append(": "); sb.append(": ");
sb.append(field.getValue()); sb.append(field.getValue());
sb.append("\n"); sb.append("\r\n");
} }
sb.append("\n"); sb.append("\r\n");
o.write(sb.toString().getBytes()); o.write(sb.toString().getBytes());
o.flush();
} }
public void run() { public void run() {
@ -84,34 +85,41 @@ public class HttpServerConnection extends Thread {
// TODO: Optimize HttpHandler-finding by using a real path-aware // TODO: Optimize HttpHandler-finding by using a real path-aware
// tree. // tree.
HttpResponse response = null; HttpHandler handler = null;
String relativePath = null;
for (Entry<String, HttpHandler> entry : server.handlers.entrySet()) { for (Entry<String, HttpHandler> entry : server.handlers.entrySet()) {
String key = entry.getKey(); String key = entry.getKey();
boolean directoryHandler = key.endsWith("/"); boolean directoryHandler = key.endsWith("/");
if (directoryHandler && request.path.startsWith(entry.getKey()) || !directoryHandler && request.path.equals(entry.getKey())) { if (directoryHandler && request.path.startsWith(entry.getKey()) || !directoryHandler && request.path.equals(entry.getKey())) {
String path = request.path.substring(entry.getKey().length()); relativePath = request.path.substring(entry.getKey().length());
handler = entry.getValue();
response = new HttpResponse(socket.getOutputStream());
entry.getValue().handle(path, request, response);
break; break;
} }
} }
if (response != null) { if (handler == null) {
if (response.fields.get("Content-Length") == null) {
response.fields.put("Content-Length", "0");
OutputStream out = response.getBody();
if (out != null) {
out.close();
}
}
String connection = response.fields.get("Connection");
if (connection == null || connection.equals("close")) {
socket.close(); socket.close();
return; return;
} }
} else {
HttpResponse response = new HttpResponse(socket.getOutputStream());
try {
handler.handle(relativePath, request, response);
} catch (Exception e) {
log.log(Level.SEVERE, "HttpHandler '" + handler + "' has thown an exception", e);
e.printStackTrace();
socket.close();
return;
}
if (response.fields.get("Content-Length") == null) {
response.fields.put("Content-Length", "0");
/* OutputStream out = */response.getBody();
}
String connection = response.fields.get("Connection");
if (connection != null && connection.equals("close")) {
socket.close(); socket.close();
return; return;
} }

View File

@ -6,57 +6,52 @@ import java.util.Date;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.dynmap.ChatQueue; import org.dynmap.Client;
import org.dynmap.MapManager; import org.dynmap.MapManager;
import org.dynmap.PlayerList; import org.dynmap.PlayerList;
import org.dynmap.TileUpdate;
import org.dynmap.web.HttpHandler; import org.dynmap.web.HttpHandler;
import org.dynmap.web.HttpRequest; import org.dynmap.web.HttpRequest;
import org.dynmap.web.HttpResponse; import org.dynmap.web.HttpResponse;
import org.dynmap.web.Json;
public class ClientUpdateHandler implements HttpHandler { public class ClientUpdateHandler implements HttpHandler {
private MapManager mapManager; private MapManager mapManager;
private PlayerList playerList; private PlayerList playerList;
private World world; private World world;
public ClientUpdateHandler(MapManager mapManager, PlayerList playerList, World world) { public ClientUpdateHandler(MapManager mapManager, PlayerList playerList, World world) {
this.mapManager = mapManager; this.mapManager = mapManager;
this.playerList = playerList; this.playerList = playerList;
this.world = world; this.world = world;
} }
@Override @Override
public void handle(String path, HttpRequest request, HttpResponse response) throws IOException { public void handle(String path, HttpRequest request, HttpResponse response) throws IOException {
int current = (int) (System.currentTimeMillis() / 1000); long current = System.currentTimeMillis();
long cutoff = 0; long cutoff = 0;
if (path.length() > 0) { if (path.length() > 0) {
try { try {
cutoff = ((long) Integer.parseInt(path)) * 1000; cutoff = Long.parseLong(path);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }
} }
StringBuilder sb = new StringBuilder(); Client.Update update = new Client.Update();
long relativeTime = world.getTime() % 24000; update.timestamp = current;
sb.append(current + " " + relativeTime + "\n"); update.servertime = world.getTime() % 24000;
Player[] players = playerList.getVisiblePlayers(); Player[] players = playerList.getVisiblePlayers();
for (Player player : players) { update.players = new Client.Player[players.length];
sb.append("player " + player.getName() + " " + player.getLocation().getX() + " " + player.getLocation().getY() + " " + player.getLocation().getZ() + "\n"); for(int i=0;i<players.length;i++) {
Player p = players[i];
update.players[i] = new Client.Player(p.getName(), p.getLocation().getX(), p.getLocation().getY(), p.getLocation().getZ());
} }
TileUpdate[] tileUpdates = mapManager.staleQueue.getTileUpdates(cutoff); update.updates = mapManager.updateQueue.getUpdatedObjects(cutoff);
for (TileUpdate tu : tileUpdates) {
sb.append("tile " + tu.tile.getName() + "\n");
}
ChatQueue.ChatMessage[] messages = mapManager.chatQueue.getChatMessages(cutoff); byte[] bytes = Json.stringifyJson(update).getBytes();
for (ChatQueue.ChatMessage cu : messages) {
sb.append("chat " + cu.playerName + " " + cu.message + "\n");
}
//debugger.debug("Sending " + players.length + " players, " + tileUpdates.length + " tile-updates, and " + messages.length + " chats. " + path + ";" + cutoff);
byte[] bytes = sb.toString().getBytes();
String dateStr = new Date().toString(); String dateStr = new Date().toString();
response.fields.put("Date", dateStr); response.fields.put("Date", dateStr);

View File

@ -66,8 +66,9 @@ public abstract class FileHandler implements HttpHandler {
} }
String extension = getExtension(path); String extension = getExtension(path);
String mimeType = getMimeTypeFromExtension(extension);
response.fields.put("Content-Type", getMimeTypeFromExtension(extension)); response.fields.put("Content-Type", mimeType);
response.fields.put("Connection", "close"); response.fields.put("Connection", "close");
OutputStream out = response.getBody(); OutputStream out = response.getBody();
try { try {

View File

@ -15,7 +15,8 @@ CustomMarker.prototype = new google.maps.OverlayView();
CustomMarker.prototype.draw = function() { CustomMarker.prototype.draw = function() {
var me = this; var me = this;
if (this.removed)
return;
// Check if the div has been created. // Check if the div has been created.
var div = this.div_; var div = this.div_;
if (!div) { if (!div) {
@ -85,5 +86,6 @@ CustomMarker.prototype.remove = function() {
if (this.div_) { if (this.div_) {
this.div_.parentNode.removeChild(this.div_); this.div_.parentNode.removeChild(this.div_);
this.div_ = null; this.div_ = null;
this.removed = true;
} }
}; };

View File

@ -12,6 +12,7 @@
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" type="text/css" href="style.css" media="screen" /> <link rel="stylesheet" type="text/css" href="style.css" media="screen" />
<script type="text/javascript" src="jquery-1.4.4.min.js"></script> <script type="text/javascript" src="jquery-1.4.4.min.js"></script>
<script type="text/javascript" src="jquery.json.js"></script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script> <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>
<script type="text/javascript" src="custommarker.js"></script> <script type="text/javascript" src="custommarker.js"></script>
<script type="text/javascript" src="minecraft.js"></script> <script type="text/javascript" src="minecraft.js"></script>

View File

@ -198,65 +198,42 @@ DynMap.prototype = {
// TODO: is there a better place for this? // TODO: is there a better place for this?
this.cleanPopups(); this.cleanPopups();
$.ajax({ $.getJSON(me.options.updateUrl + me.lasttimestamp, function(update) {
url: me.options.updateUrl + me.lasttimestamp,
success: function(res) {
if (!res) {
me.alertbox
.text('Invalid response')
.show();
}
me.alertbox.hide(); me.alertbox.hide();
var rows = res.split('\n');
var row = splitArgs(rows[0], 'timestamp', 'servertime');
delete rows[0];
me.lasttimestamp = row.timestamp; me.lasttimestamp = update.timestamp;
me.clock.setTime(getMinecraftTime(row.servertime)); me.clock.setTime(getMinecraftTime(update.servertime));
var typeVisibleMap = {}; var typeVisibleMap = {};
var newmarkers = {}; var newmarkers = {};
for(var rowIndex in rows) { $.each(update.players, function(index, player) {
var line = rows[rowIndex];
row = splitArgs(line, 'type', 'name', 'posx', 'posy', 'posz');
if (!row.type) continue;
swtch(row.type, {
tile: function() {
me.onTileUpdated(row.name);
}
, chat: function() {
if (!me.options.showchatballoons)
return;
var chats = line.split(' ');
var message = '';
for (var chatIndex = 2; chatIndex < chats.length; chatIndex++)
{
if (chatIndex > 2) message = message + " ";
message = message + chats[chatIndex];
}
if (message.length > 0)
{
me.onPlayerChat(row.name, message);
}
}
},
function() {
var mi = { var mi = {
id: row.type + '_' + row.name, id: 'player_' + player.name,
text: row.name, text: player.name,
type: row.type, type: 'player',
position: me.map.getProjection().fromWorldToLatLng(parseFloat(row.posx), parseFloat(row.posy), parseFloat(row.posz)), position: me.map.getProjection().fromWorldToLatLng(parseFloat(player.x), parseFloat(player.y), parseFloat(player.z)),
visible: true visible: true
}; };
me.updateMarker(mi); me.updateMarker(mi);
newmarkers[mi.id] = mi; newmarkers[mi.id] = mi;
}); });
$.each(update.updates, function(index, update) {
swtch(update.type, {
tile: function() {
me.onTileUpdated(update.name);
},
chat: function() {
if (!me.options.showchatballoons)
return;
me.onPlayerChat(update.playerName, update.message);
} }
}, function(type) {
console.log('Unknown type ', value, '!');
});
});
for(var m in me.markers) { for(var m in me.markers) {
var marker = me.markers[m]; var marker = me.markers[m];
@ -269,14 +246,13 @@ DynMap.prototype = {
} }
} }
setTimeout(function() { me.update(); }, me.options.updaterate); setTimeout(function() { me.update(); }, me.options.updaterate);
}, }, function(request, statusText, ex) {
error: function(request, statusText, ex) {
me.alertbox me.alertbox
.text('Could not update map') .text('Could not update map')
.show(); .show();
setTimeout(function() { me.update(); }, me.options.updaterate); setTimeout(function() { me.update(); }, me.options.updaterate);
} }
}); );
}, },
cleanPopups: function() { cleanPopups: function() {
var POPUP_LIFE = 8000; var POPUP_LIFE = 8000;