Added multiworld update support along with client-side support.

This commit is contained in:
FrozenCow 2011-02-13 01:19:47 +01:00
parent 5b0171c459
commit 100f3e0590
8 changed files with 124 additions and 18 deletions

View File

@ -47,3 +47,12 @@ web:
- type: KzedMapType - type: KzedMapType
name: cavemap name: cavemap
prefix: ct prefix: ct
# The name of the world shown when opening Dynmap's page.
defaultworld: world
# The worlds shown in the menu.
shownworlds:
- world
- nether
- world_bad

View File

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

View File

@ -21,14 +21,16 @@ public class MapManager {
private MapType[] mapTypes; private MapType[] mapTypes;
public AsynchronousQueue<MapTile> tileQueue; public AsynchronousQueue<MapTile> tileQueue;
public UpdateQueue updateQueue;
public Map<String, UpdateQueue> worldUpdateQueues = new HashMap<String, UpdateQueue>();
public ArrayList<String> worlds = new ArrayList<String>();
public PlayerList playerList; public PlayerList playerList;
/* lock for our data structures */ /* lock for our data structures */
public static final Object lock = new Object(); public static final Object lock = new Object();
public MapManager(ConfigurationNode configuration) { public MapManager(ConfigurationNode configuration) {
this.updateQueue = new UpdateQueue();
this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() { this.tileQueue = new AsynchronousQueue<MapTile>(new Handler<MapTile>() {
@Override @Override
public void handle(MapTile t) { public void handle(MapTile t) {
@ -81,7 +83,6 @@ public class MapManager {
if (render(tile)) { if (render(tile)) {
found.remove(tile); found.remove(tile);
rendered.add(tile); rendered.add(tile);
updateQueue.pushUpdate(new Client.Tile(tile.getFilename()));
for (MapTile adjTile : map.getAdjecentTiles(tile)) { for (MapTile adjTile : map.getAdjecentTiles(tile)) {
if (!found.contains(adjTile) && !rendered.contains(adjTile)) { if (!found.contains(adjTile) && !rendered.contains(adjTile)) {
found.add(adjTile); found.add(adjTile);
@ -157,7 +158,9 @@ public class MapManager {
} }
public boolean render(MapTile tile) { public boolean render(MapTile tile) {
return tile.getMap().render(tile, getTileFile(tile)); boolean result = tile.getMap().render(tile, getTileFile(tile));
pushUpdate(tile.getWorld(), new Client.Tile(tile.getFilename()));
return result;
} }
@ -172,4 +175,31 @@ public class MapManager {
} }
return new File(worldTileDirectory, tile.getFilename()); return new File(worldTileDirectory, tile.getFilename());
} }
public void pushUpdate(Object update) {
for(int i=0;i<worlds.size();i++) {
UpdateQueue queue = worldUpdateQueues.get(worlds.get(i));
queue.pushUpdate(update);
}
}
public void pushUpdate(World world, Object update) {
pushUpdate(world.getName(), update);
}
public void pushUpdate(String world, Object update) {
UpdateQueue updateQueue = worldUpdateQueues.get(world);
if (updateQueue == null) {
worldUpdateQueues.put(world, updateQueue = new UpdateQueue());
worlds.add(world);
}
updateQueue.pushUpdate(update);
}
public Object[] getWorldUpdates(String worldName, long since) {
UpdateQueue queue = worldUpdateQueues.get(worldName);
if (queue == null)
return new Object[0];
return queue.getUpdatedObjects(since);
}
} }

View File

@ -11,6 +11,7 @@ import java.util.HashSet;
import java.util.Scanner; import java.util.Scanner;
import org.bukkit.Server; import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class PlayerList { public class PlayerList {
@ -68,6 +69,21 @@ public class PlayerList {
hide(playerName); hide(playerName);
} }
// TODO: Clean this up... one day
public Player[] getVisiblePlayers(String worldName) {
ArrayList<Player> visiblePlayers = new ArrayList<Player>();
Player[] onlinePlayers = server.getOnlinePlayers();
for (int i = 0; i < onlinePlayers.length; i++) {
Player p = onlinePlayers[i];
if (p.getWorld().getName().equals(worldName) && !hiddenPlayerNames.contains(p.getName())) {
visiblePlayers.add(p);
}
}
Player[] result = new Player[visiblePlayers.size()];
visiblePlayers.toArray(result);
return result;
}
public Player[] getVisiblePlayers() { public Player[] getVisiblePlayers() {
ArrayList<Player> visiblePlayers = new ArrayList<Player>(); ArrayList<Player> visiblePlayers = new ArrayList<Player>();
Player[] onlinePlayers = server.getOnlinePlayers(); Player[] onlinePlayers = server.getOnlinePlayers();

View File

@ -11,7 +11,7 @@ public class UpdateQueue {
private static final int maxUpdateAge = 120000; private static final int maxUpdateAge = 120000;
public void pushUpdate(Object obj) { public synchronized void pushUpdate(Object obj) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge; long deadline = now - maxUpdateAge;
synchronized (lock) { synchronized (lock) {
@ -27,7 +27,7 @@ public class UpdateQueue {
private ArrayList<Object> tmpupdates = new ArrayList<Object>(); private ArrayList<Object> tmpupdates = new ArrayList<Object>();
public Object[] getUpdatedObjects(long since) { public synchronized Object[] getUpdatedObjects(long since) {
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
long deadline = now - maxUpdateAge; long deadline = now - maxUpdateAge;
Object[] updates; Object[] updates;

View File

@ -15,11 +15,9 @@ 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;
import org.dynmap.MapManager;
import org.dynmap.MapTile; import org.dynmap.MapTile;
import org.dynmap.MapType; import org.dynmap.MapType;
import org.dynmap.debug.Debug; import org.dynmap.debug.Debug;
import org.dynmap.debug.Debugger;
public class KzedMap extends MapType { public class KzedMap extends MapType {
protected static final Logger log = Logger.getLogger("Minecraft"); protected static final Logger log = Logger.getLogger("Minecraft");

View File

@ -2,6 +2,9 @@ package org.dynmap.web.handlers;
import java.io.BufferedOutputStream; import java.io.BufferedOutputStream;
import java.util.Date; import java.util.Date;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -24,14 +27,24 @@ public class ClientUpdateHandler implements HttpHandler {
this.world = world; this.world = world;
} }
Pattern updatePathPattern = Pattern.compile("world/([a-zA-Z0-9_]+)/([0-9]*)");
@Override @Override
public void handle(String path, HttpRequest request, HttpResponse response) throws Exception { public void handle(String path, HttpRequest request, HttpResponse response) throws Exception {
Matcher match = updatePathPattern.matcher(path);
if (!match.matches())
return;
String worldName = match.group(1);
String timeKey = match.group(2);
long current = System.currentTimeMillis(); long current = System.currentTimeMillis();
long cutoff = 0; long since = 0;
if (path.length() > 0) { if (path.length() > 0) {
try { try {
cutoff = Long.parseLong(path); since = Long.parseLong(timeKey);
} catch (NumberFormatException e) { } catch (NumberFormatException e) {
} }
} }
@ -41,14 +54,15 @@ public class ClientUpdateHandler implements HttpHandler {
update.servertime = world.getTime() % 24000; update.servertime = world.getTime() % 24000;
Player[] players = playerList.getVisiblePlayers(); Player[] players = playerList.getVisiblePlayers(worldName);
update.players = new Client.Player[players.length]; update.players = new Client.Player[players.length];
for(int i=0;i<players.length;i++) { for(int i=0;i<players.length;i++) {
Player p = players[i]; Player p = players[i];
update.players[i] = new Client.Player(p.getName(), p.getLocation().getX(), p.getLocation().getY(), p.getLocation().getZ()); update.players[i] = new Client.Player(p.getName(), p.getLocation().getX(), p.getLocation().getY(), p.getLocation().getZ());
} }
update.updates = mapManager.updateQueue.getUpdatedObjects(cutoff); update.updates = mapManager.getWorldUpdates(worldName, since);
byte[] bytes = Json.stringifyJson(update).getBytes(); byte[] bytes = Json.stringifyJson(update).getBytes();

View File

@ -91,6 +91,7 @@ DynMap.prototype = {
$.each(me.options.shownmaps, function(index, mapentry) { $.each(me.options.shownmaps, function(index, mapentry) {
me.options.maps[mapentry.name] = maptypes[mapentry.type](mapentry); me.options.maps[mapentry.name] = maptypes[mapentry.type](mapentry);
}); });
me.world = me.options.defaultworld;
}, },
initialize: function() { initialize: function() {
var me = this; var me = this;
@ -131,11 +132,48 @@ DynMap.prototype = {
.addClass('sidebar') .addClass('sidebar')
.appendTo(container); .appendTo(container);
// The world list.
var worldlist = me.worldlist = $('<div/>')
.addClass('worldlist')
.appendTo(sidebar);
$.each(me.options.shownworlds, function(index, name) {
var worldButton;
$('<div/>')
.addClass('worldrow')
.append(worldButton = $('<input/>')
.addClass('worldbutton')
.addClass('world_' + name)
.attr({
type: 'radio',
name: 'world',
value: name
})
.attr('checked', me.options.defaultworld == name ? 'checked' : null)
)
.append($('<label/>')
.attr('for', 'worldbutton_' + name)
.text(name)
)
.click(function() {
$('.worldbutton', worldlist).removeAttr('checked');
map.setMapTypeId('none');
me.world = name;
// Another workaround for GMaps not being able to reload tiles.
window.setTimeout(function() {
map.setMapTypeId(me.options.defaultmap);
}, 1);
worldButton.attr('checked', 'checked');
})
.data('world', name)
.appendTo(worldlist);
});
// The map list. // The map list.
var maplist = me.maplist = $('<div/>') var maplist = me.maplist = $('<div/>')
.addClass('maplist') .addClass('maplist')
.appendTo(sidebar); .appendTo(sidebar);
$.each(me.options.maps, function(name, mapType){ $.each(me.options.maps, function(name, mapType){
mapType.dynmap = me; mapType.dynmap = me;
map.mapTypes.set(name, mapType); map.mapTypes.set(name, mapType);
@ -144,11 +182,12 @@ DynMap.prototype = {
$('<div/>') $('<div/>')
.addClass('maprow') .addClass('maprow')
.append(mapButton = $('<input/>') .append(mapButton = $('<input/>')
.addClass('mapbutton')
.addClass('maptype_' + name) .addClass('maptype_' + name)
.attr({ .attr({
type: 'radio', type: 'radio',
name: 'map', name: 'map',
id: 'maptypebutton_' + name value: name
}) })
.attr('checked', me.options.defaultmap == name ? 'checked' : null) .attr('checked', me.options.defaultmap == name ? 'checked' : null)
) )
@ -199,7 +238,7 @@ DynMap.prototype = {
// TODO: is there a better place for this? // TODO: is there a better place for this?
this.cleanPopups(); this.cleanPopups();
$.getJSON(me.options.updateUrl + me.lasttimestamp, function(update) { $.getJSON(me.options.updateUrl + "world/" + me.world + "/" + me.lasttimestamp, function(update) {
me.alertbox.hide(); me.alertbox.hide();
me.lasttimestamp = update.timestamp; me.lasttimestamp = update.timestamp;
@ -415,9 +454,9 @@ DynMap.prototype = {
var tile = me.registeredTiles[tileName]; var tile = me.registeredTiles[tileName];
if(tile) { if(tile) {
return me.options.tileUrl + tileName + '.png?' + tile.lastseen; return me.options.tileUrl + me.world + '/' + tileName + '.png?' + tile.lastseen;
} else { } else {
return me.options.tileUrl + tileName + '.png?0'; return me.options.tileUrl + me.world + '/' + tileName + '.png?0';
} }
}, },
registerTile: function(mapType, tileName, tile) { registerTile: function(mapType, tileName, tile) {