forked from mirror/BlueMap
Change map-updating to rely on chunks rather than tiles
and trigger the updates on chunk-save-events rather than world-save-events if possible to make it more reliable.
This commit is contained in:
parent
5784491feb
commit
e5a75f25e1
@ -24,7 +24,10 @@
|
||||
*/
|
||||
package de.bluecolored.bluemap.common.plugin;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.UUID;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
@ -35,53 +38,44 @@
|
||||
import de.bluecolored.bluemap.common.MapType;
|
||||
import de.bluecolored.bluemap.common.RenderManager;
|
||||
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
|
||||
import de.bluecolored.bluemap.core.world.World;
|
||||
|
||||
public class MapUpdateHandler implements ServerEventListener {
|
||||
|
||||
private Plugin plugin;
|
||||
|
||||
private Multimap<MapType, Vector2i> updateBuffer;
|
||||
private Multimap<UUID, Vector2i> updateBuffer;
|
||||
private Timer flushTimer;
|
||||
|
||||
public MapUpdateHandler(Plugin plugin) {
|
||||
this.plugin = plugin;
|
||||
updateBuffer = MultimapBuilder.hashKeys().hashSetValues().build();
|
||||
|
||||
flushTimer = new Timer("MapUpdateHandlerTimer", true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWorldSaveToDisk(final UUID world) {
|
||||
RenderManager renderManager = plugin.getRenderManager();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(5000); // wait 5 sec before rendering so saving has finished to avoid render-errors
|
||||
|
||||
synchronized (updateBuffer) {
|
||||
Iterator<MapType> iterator = updateBuffer.keys().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
MapType map = iterator.next();
|
||||
if (map.getWorld().getUUID().equals(world)) {
|
||||
|
||||
//invalidate caches of updated chunks
|
||||
for (Vector2i chunk : updateBuffer.get(map)) {
|
||||
map.getWorld().invalidateChunkCache(chunk);
|
||||
}
|
||||
|
||||
renderManager.createTickets(map, updateBuffer.get(map));
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (InterruptedException ignore) { Thread.currentThread().interrupt(); }
|
||||
|
||||
}).start();
|
||||
// wait 5 sec before rendering so saving has finished
|
||||
flushTimer.schedule(new TimerTask() {
|
||||
@Override public void run() { flushUpdateBufferForWorld(world); }
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChunkSaveToDisk(final UUID world, final Vector2i chunkPos) {
|
||||
|
||||
// wait 5 sec before rendering so saving has finished
|
||||
flushTimer.schedule(new TimerTask() {
|
||||
@Override public void run() { flushUpdateBufferForChunk(world, chunkPos); }
|
||||
}, 5000);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBlockChange(UUID world, Vector3i blockPos) {
|
||||
synchronized (updateBuffer) {
|
||||
updateBlock(world, blockPos);
|
||||
}
|
||||
updateBlock(world, blockPos);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -97,30 +91,22 @@ public void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChunk(UUID world, Vector2i chunkPos) {
|
||||
Vector3i min = new Vector3i(chunkPos.getX() * 16, 0, chunkPos.getY() * 16);
|
||||
Vector3i max = min.add(15, 255, 15);
|
||||
private void updateChunk(UUID worldUUID, Vector2i chunkPos) {
|
||||
World world = plugin.getWorld(worldUUID);
|
||||
if (world == null) return;
|
||||
|
||||
Vector3i xmin = new Vector3i(min.getX(), 0, max.getY());
|
||||
Vector3i xmax = new Vector3i(max.getX(), 255, min.getY());
|
||||
|
||||
//update all corners so we always update all tiles containing this chunk
|
||||
synchronized (updateBuffer) {
|
||||
updateBlock(world, min);
|
||||
updateBlock(world, max);
|
||||
updateBlock(world, xmin);
|
||||
updateBlock(world, xmax);
|
||||
updateBuffer.put(worldUUID, chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateBlock(UUID world, Vector3i pos){
|
||||
private void updateBlock(UUID worldUUID, Vector3i pos){
|
||||
World world = plugin.getWorld(worldUUID);
|
||||
if (world == null) return;
|
||||
|
||||
synchronized (updateBuffer) {
|
||||
for (MapType mapType : plugin.getMapTypes()) {
|
||||
if (mapType.getWorld().getUUID().equals(world)) {
|
||||
Vector2i tile = mapType.getTileRenderer().getHiresModelManager().posToTile(pos);
|
||||
updateBuffer.put(mapType, tile);
|
||||
}
|
||||
}
|
||||
Vector2i chunkPos = world.blockPosToChunkPos(pos);
|
||||
updateBuffer.put(worldUUID, chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
@ -128,21 +114,107 @@ public int getUpdateBufferCount() {
|
||||
return updateBuffer.size();
|
||||
}
|
||||
|
||||
public void flushTileBuffer() {
|
||||
public void flushUpdateBuffer() {
|
||||
RenderManager renderManager = plugin.getRenderManager();
|
||||
|
||||
synchronized (updateBuffer) {
|
||||
for (MapType map : updateBuffer.keySet()) {
|
||||
|
||||
//invalidate caches of updated chunks
|
||||
for (Vector2i chunk : updateBuffer.get(map)) {
|
||||
map.getWorld().invalidateChunkCache(chunk);
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
Collection<Vector2i> chunks = updateBuffer.get(map.getWorld().getUUID());
|
||||
Collection<Vector2i> tiles = new HashSet<>(chunks.size() * 2);
|
||||
|
||||
for (Vector2i chunk : chunks) {
|
||||
Vector3i min = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16);
|
||||
Vector3i max = min.add(15, 255, 15);
|
||||
|
||||
Vector3i xmin = new Vector3i(min.getX(), 0, max.getY());
|
||||
Vector3i xmax = new Vector3i(max.getX(), 255, min.getY());
|
||||
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax));
|
||||
}
|
||||
|
||||
renderManager.createTickets(map, updateBuffer.get(map));
|
||||
//invalidate caches of updated chunks
|
||||
for (Vector2i chunk : chunks) {
|
||||
map.getWorld().invalidateChunkCache(chunk);
|
||||
}
|
||||
|
||||
//create render-tickets
|
||||
renderManager.createTickets(map, tiles);
|
||||
}
|
||||
|
||||
updateBuffer.clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void flushUpdateBufferForWorld(UUID world) {
|
||||
RenderManager renderManager = plugin.getRenderManager();
|
||||
|
||||
synchronized (updateBuffer) {
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
if (!map.getWorld().getUUID().equals(world)) continue;
|
||||
|
||||
Collection<Vector2i> chunks = updateBuffer.get(world);
|
||||
Collection<Vector2i> tiles = new HashSet<>(chunks.size() * 2);
|
||||
|
||||
for (Vector2i chunk : chunks) {
|
||||
Vector3i min = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16);
|
||||
Vector3i max = min.add(15, 255, 15);
|
||||
|
||||
Vector3i xmin = new Vector3i(min.getX(), 0, max.getY());
|
||||
Vector3i xmax = new Vector3i(max.getX(), 255, min.getY());
|
||||
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax));
|
||||
}
|
||||
|
||||
//invalidate caches of updated chunks
|
||||
for (Vector2i chunk : chunks) {
|
||||
map.getWorld().invalidateChunkCache(chunk);
|
||||
}
|
||||
|
||||
//create render-tickets
|
||||
renderManager.createTickets(map, tiles);
|
||||
}
|
||||
|
||||
updateBuffer.removeAll(world);
|
||||
}
|
||||
}
|
||||
|
||||
public void flushUpdateBufferForChunk(UUID world, Vector2i chunkPos) {
|
||||
RenderManager renderManager = plugin.getRenderManager();
|
||||
|
||||
synchronized (updateBuffer) {
|
||||
if (!updateBuffer.containsEntry(world, chunkPos)) return;
|
||||
|
||||
for (MapType map : plugin.getMapTypes()) {
|
||||
if (!map.getWorld().getUUID().equals(world)) continue;
|
||||
|
||||
Collection<Vector2i> tiles = new HashSet<>(4);
|
||||
|
||||
Vector3i min = new Vector3i(chunkPos.getX() * 16, 0, chunkPos.getY() * 16);
|
||||
Vector3i max = min.add(15, 255, 15);
|
||||
|
||||
Vector3i xmin = new Vector3i(min.getX(), 0, max.getY());
|
||||
Vector3i xmax = new Vector3i(max.getX(), 255, min.getY());
|
||||
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(min));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(max));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmin));
|
||||
tiles.add(map.getTileRenderer().getHiresModelManager().posToTile(xmax));
|
||||
|
||||
//invalidate caches of updated chunk
|
||||
map.getWorld().invalidateChunkCache(chunkPos);
|
||||
|
||||
//create render-tickets
|
||||
renderManager.createTickets(map, tiles);
|
||||
}
|
||||
|
||||
updateBuffer.remove(world, chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ public void unload() {
|
||||
if (webServer != null) webServer.close();
|
||||
|
||||
//save render-manager state
|
||||
if (updateHandler != null) updateHandler.flushTileBuffer(); //first write all buffered tiles to the render manager to save them too
|
||||
if (updateHandler != null) updateHandler.flushUpdateBuffer(); //first write all buffered changes to the render manager to save them too
|
||||
if (renderManager != null) {
|
||||
try {
|
||||
saveRenderManagerState();
|
||||
|
@ -35,6 +35,8 @@ public interface ServerEventListener {
|
||||
|
||||
default void onWorldSaveToDisk(UUID world) {};
|
||||
|
||||
default void onChunkSaveToDisk(UUID world, Vector2i chunkPos) {};
|
||||
|
||||
default void onBlockChange(UUID world, Vector3i blockPos) {};
|
||||
|
||||
default void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {};
|
||||
|
@ -42,8 +42,8 @@
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkDataEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
public class ForgeEventForwarder {
|
||||
@ -104,6 +104,21 @@ private synchronized void onBlockChange(BlockEvent evt) {
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onChunkSave(ChunkDataEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
|
||||
|
||||
try {
|
||||
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos);
|
||||
} catch (IOException e) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk
|
||||
@SubscribeEvent
|
||||
public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
@ -115,6 +130,7 @@ public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||
|
@ -42,8 +42,8 @@
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkDataEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
public class ForgeEventForwarder {
|
||||
@ -104,6 +104,21 @@ private synchronized void onBlockChange(BlockEvent evt) {
|
||||
}
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onChunkSave(ChunkDataEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
|
||||
|
||||
try {
|
||||
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos);
|
||||
} catch (IOException e) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk
|
||||
@SubscribeEvent
|
||||
public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
@ -115,6 +130,7 @@ public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||
|
@ -42,8 +42,8 @@
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
|
||||
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
|
||||
import net.minecraftforge.event.world.BlockEvent;
|
||||
import net.minecraftforge.event.world.ChunkDataEvent;
|
||||
import net.minecraftforge.event.world.ChunkEvent;
|
||||
import net.minecraftforge.event.world.WorldEvent;
|
||||
import net.minecraftforge.eventbus.api.SubscribeEvent;
|
||||
|
||||
public class ForgeEventForwarder {
|
||||
@ -103,7 +103,22 @@ private synchronized void onBlockChange(BlockEvent evt) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onChunkSave(ChunkDataEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
|
||||
Vector2i chunkPos = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
|
||||
|
||||
try {
|
||||
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
|
||||
for (ServerEventListener listener : eventListeners) listener.onChunkSaveToDisk(world, chunkPos);
|
||||
} catch (IOException e) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk
|
||||
@SubscribeEvent
|
||||
public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
if (!(evt.getWorld() instanceof ServerWorld)) return;
|
||||
@ -115,6 +130,7 @@ public synchronized void onWorldSave(WorldEvent.Save evt) {
|
||||
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
@SubscribeEvent
|
||||
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
|
||||
|
@ -46,7 +46,7 @@
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.world.ChunkPopulateEvent;
|
||||
import org.bukkit.event.world.WorldSaveEvent;
|
||||
import org.bukkit.event.world.ChunkUnloadEvent;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
import com.flowpowered.math.vector.Vector3i;
|
||||
@ -70,10 +70,20 @@ public synchronized void removeAllListeners() {
|
||||
listeners.clear();
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onChunkSaveToDisk(ChunkUnloadEvent evt) {
|
||||
if (evt.isSaveChunk()) {
|
||||
Vector2i chunkPos = new Vector2i(evt.getChunk().getX(), evt.getChunk().getZ());
|
||||
for (ServerEventListener listener : listeners) listener.onChunkSaveToDisk(evt.getWorld().getUID(), chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
/* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public synchronized void onWorldSaveToDisk(WorldSaveEvent evt) {
|
||||
for (ServerEventListener listener : listeners) listener.onWorldSaveToDisk(evt.getWorld().getUID());
|
||||
}
|
||||
*/
|
||||
|
||||
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
|
||||
public void onBlockChange(BlockPlaceEvent evt) {
|
||||
|
@ -1,5 +1,5 @@
|
||||
dependencies {
|
||||
shadow "org.spongepowered:spongeapi:7.2.0"
|
||||
shadow "org.spongepowered:spongeapi:7.3.0"
|
||||
|
||||
compile group: 'org.bstats', name: 'bstats-sponge-lite', version: '1.5'
|
||||
|
||||
@ -16,7 +16,7 @@ dependencies {
|
||||
|
||||
build.dependsOn shadowJar {
|
||||
destinationDir = file '../../build/release'
|
||||
archiveFileName = "BlueMap-${version}-sponge-7.2.0.jar"
|
||||
archiveFileName = "BlueMap-${version}-sponge-7.3.0.jar"
|
||||
|
||||
relocate 'net.querz.nbt', 'de.bluecolored.shadow.querz.nbt'
|
||||
relocate 'org.apache.commons.io', 'de.bluecolored.shadow.apache.commons.io'
|
||||
|
@ -34,8 +34,8 @@
|
||||
import org.spongepowered.api.event.filter.type.Exclude;
|
||||
import org.spongepowered.api.event.message.MessageChannelEvent;
|
||||
import org.spongepowered.api.event.network.ClientConnectionEvent;
|
||||
import org.spongepowered.api.event.world.SaveWorldEvent;
|
||||
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
|
||||
import org.spongepowered.api.event.world.chunk.SaveChunkEvent;
|
||||
import org.spongepowered.api.world.Location;
|
||||
|
||||
import com.flowpowered.math.vector.Vector2i;
|
||||
@ -52,10 +52,17 @@ public EventForwarder(ServerEventListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
/* Use ChunkSaveToDisk as it is the preferred event to use and more reliable on the chunk actually saved to disk
|
||||
@Listener(order = Order.POST)
|
||||
public void onWorldSaveToDisk(SaveWorldEvent evt) {
|
||||
listener.onWorldSaveToDisk(evt.getTargetWorld().getUniqueId());
|
||||
}
|
||||
*/
|
||||
|
||||
@Listener(order = Order.POST)
|
||||
public void onChunkSaveToDisk(SaveChunkEvent.Pre evt) {
|
||||
listener.onChunkSaveToDisk(evt.getTargetChunk().getWorld().getUniqueId(), evt.getTargetChunk().getPosition().toVector2(true));
|
||||
}
|
||||
|
||||
@Listener(order = Order.POST)
|
||||
@Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class})
|
||||
|
Loading…
Reference in New Issue
Block a user