Intermediate commit: Lots of redesigning with the RenderManager and Render-Tasks

This commit is contained in:
Blue (Lukas Rieger) 2021-04-24 22:11:26 +02:00
parent 5095f0df41
commit 27295cb988
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
84 changed files with 1745 additions and 3737 deletions

@ -1 +1 @@
Subproject commit 4bef101c6ee6ddef23049287cc1a3736d954979e Subproject commit 514659e5dd4c0ad3f51ecb98cc70e5c1248d994d

View File

@ -24,20 +24,17 @@
*/ */
package de.bluecolored.bluemap.common; package de.bluecolored.bluemap.common;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.config.*; import de.bluecolored.bluemap.core.config.*;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.web.WebSettings; import de.bluecolored.bluemap.common.web.WebSettings;
import de.bluecolored.bluemap.core.world.SlicedWorld; import de.bluecolored.bluemap.core.world.SlicedWorld;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
@ -51,12 +48,12 @@
* This is the attempt to generalize as many actions as possible to have CLI and Plugins run on the same general setup-code. * This is the attempt to generalize as many actions as possible to have CLI and Plugins run on the same general setup-code.
*/ */
public class BlueMapService { public class BlueMapService {
private MinecraftVersion minecraftVersion; private final MinecraftVersion minecraftVersion;
private File configFolder; private final File configFolder;
private ThrowingFunction<File, UUID, IOException> worldUUIDProvider; private final ThrowingFunction<File, UUID, IOException> worldUUIDProvider;
private ThrowingFunction<UUID, String, IOException> worldNameProvider; private final ThrowingFunction<UUID, String, IOException> worldNameProvider;
private ConfigManager configManager; private final ConfigManager configManager;
private CoreConfig coreConfig; private CoreConfig coreConfig;
private RenderConfig renderConfig; private RenderConfig renderConfig;
@ -65,7 +62,7 @@ public class BlueMapService {
private ResourcePack resourcePack; private ResourcePack resourcePack;
private Map<UUID, World> worlds; private Map<UUID, World> worlds;
private Map<String, MapType> maps; private Map<String, BmMap> maps;
public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) { public BlueMapService(MinecraftVersion minecraftVersion, File configFolder) {
this.minecraftVersion = minecraftVersion; this.minecraftVersion = minecraftVersion;
@ -106,16 +103,15 @@ public synchronized WebSettings updateWebAppSettings() throws IOException, Inter
WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json")); WebSettings webSettings = new WebSettings(new File(getRenderConfig().getWebRoot(), "data" + File.separator + "settings.json"));
webSettings.set(getRenderConfig().isUseCookies(), "useCookies"); webSettings.set(getRenderConfig().isUseCookies(), "useCookies");
webSettings.setAllMapsEnabled(false); webSettings.setAllMapsEnabled(false);
for (MapType map : getMaps().values()) { for (BmMap map : getMaps().values()) {
webSettings.setMapEnabled(true, map.getId()); webSettings.setMapEnabled(true, map.getId());
webSettings.setFrom(map.getTileRenderer(), map.getId()); webSettings.setFrom(map);
webSettings.setFrom(map.getWorld(), map.getId());
} }
int ordinal = 0; int ordinal = 0;
for (MapConfig map : getRenderConfig().getMapConfigs()) { for (MapConfig map : getRenderConfig().getMapConfigs()) {
if (!getMaps().containsKey(map.getId())) continue; //don't add not loaded maps if (!getMaps().containsKey(map.getId())) continue; //don't add not loaded maps
webSettings.setOrdinal(ordinal++, map.getId()); webSettings.setOrdinal(ordinal++, map.getId());
webSettings.setFrom(map, map.getId()); webSettings.setFrom(map);
} }
webSettings.save(); webSettings.save();
@ -127,7 +123,7 @@ public synchronized Map<UUID, World> getWorlds() throws IOException, Interrupted
return worlds; return worlds;
} }
public synchronized Map<String, MapType> getMaps() throws IOException, InterruptedException { public synchronized Map<String, BmMap> getMaps() throws IOException, InterruptedException {
if (maps == null) loadWorldsAndMaps(); if (maps == null) loadWorldsAndMaps();
return maps; return maps;
} }
@ -135,6 +131,9 @@ public synchronized Map<String, MapType> getMaps() throws IOException, Interrupt
private synchronized void loadWorldsAndMaps() throws IOException, InterruptedException { private synchronized void loadWorldsAndMaps() throws IOException, InterruptedException {
maps = new HashMap<>(); maps = new HashMap<>();
worlds = new HashMap<>(); worlds = new HashMap<>();
ConfigManager configManager = getConfigManager();
configManager.loadResourceConfigs(configFolder, getResourcePack());
for (MapConfig mapConfig : getRenderConfig().getMapConfigs()) { for (MapConfig mapConfig : getRenderConfig().getMapConfigs()) {
String id = mapConfig.getId(); String id = mapConfig.getId();
@ -154,9 +153,6 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
continue; continue;
} }
ConfigManager configManager = getConfigManager();
configManager.loadResourceConfigs(configFolder, getResourcePack());
World world = worlds.get(worldUUID); World world = worlds.get(worldUUID);
if (world == null) { if (world == null) {
try { try {
@ -165,7 +161,7 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
} catch (MissingResourcesException e) { } catch (MissingResourcesException e) {
throw e; // rethrow this to stop loading and display resource-missing message throw e; // rethrow this to stop loading and display resource-missing message
} catch (IOException e) { } catch (IOException e) {
Logger.global.logError("Failed to load map '" + id + "': Failed to read level.dat", e); Logger.global.logError("Failed to load map '" + id + "'!", e);
continue; continue;
} }
} }
@ -179,28 +175,20 @@ private synchronized void loadWorldsAndMaps() throws IOException, InterruptedExc
world, world,
mapConfig.getMin().min(mapConfig.getMin().sub(2, 2, 2)), // protect from int-overflow mapConfig.getMin().min(mapConfig.getMin().sub(2, 2, 2)), // protect from int-overflow
mapConfig.getMax().max(mapConfig.getMax().add(2, 2, 2)) // protect from int-overflow mapConfig.getMax().max(mapConfig.getMax().add(2, 2, 2)) // protect from int-overflow
); );
} }
} }
HiresModelManager hiresModelManager = new HiresModelManager( BmMap map = new BmMap(
getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id).resolve("hires"), id,
name,
world,
getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id),
getResourcePack(), getResourcePack(),
mapConfig, mapConfig
new Vector2i(mapConfig.getHiresTileSize(), mapConfig.getHiresTileSize()) );
);
maps.put(id, map);
LowresModelManager lowresModelManager = new LowresModelManager(
getRenderConfig().getWebRoot().toPath().resolve("data").resolve(id).resolve("lowres"),
new Vector2i(mapConfig.getLowresPointsPerLowresTile(), mapConfig.getLowresPointsPerLowresTile()),
new Vector2i(mapConfig.getLowresPointsPerHiresTile(), mapConfig.getLowresPointsPerHiresTile()),
mapConfig.useGzipCompression()
);
TileRenderer tileRenderer = new TileRenderer(hiresModelManager, lowresModelManager);
MapType mapType = new MapType(id, name, world, tileRenderer);
maps.put(id, mapType);
} }
worlds = Collections.unmodifiableMap(worlds); worlds = Collections.unmodifiableMap(worlds);

View File

@ -1,89 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common;
import com.flowpowered.math.vector.Vector2i;
import com.google.common.base.Preconditions;
import de.bluecolored.bluemap.core.render.TileRenderer;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.world.World;
public class MapType {
private final String id;
private String name;
private World world;
private TileRenderer tileRenderer;
public MapType(String id, String name, World world, TileRenderer tileRenderer) {
Preconditions.checkNotNull(id);
Preconditions.checkNotNull(name);
Preconditions.checkNotNull(world);
Preconditions.checkNotNull(tileRenderer);
this.id = id;
this.name = name;
this.world = world;
this.tileRenderer = tileRenderer;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public World getWorld() {
return world;
}
public TileRenderer getTileRenderer() {
return tileRenderer;
}
public void renderTile(Vector2i tile) {
getTileRenderer().render(new WorldTile(getWorld(), tile));
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof MapType) {
MapType that = (MapType) obj;
return this.id.equals(that.id);
}
return false;
}
}

View File

@ -1,287 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.flowpowered.math.vector.Vector2i;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import de.bluecolored.bluemap.core.logger.Logger;
public class RenderManager {
private volatile boolean running;
private Thread[] renderThreads;
private ArrayDeque<RenderTicket> renderTickets;
private Map<RenderTicket, RenderTicket> renderTicketMap;
private Deque<RenderTask> renderTasks;
public RenderManager(int threadCount) {
running = false;
renderThreads = new Thread[threadCount];
renderTickets = new ArrayDeque<>(1000);
renderTicketMap = new HashMap<>(1000);
renderTasks = new ArrayDeque<>();
}
public synchronized void start() {
stop(); //ensure everything is stopped first
running = true;
for (int i = 0; i < renderThreads.length; i++) {
renderThreads[i] = new Thread(this::renderThread);
renderThreads[i].setPriority(Thread.MIN_PRIORITY);
renderThreads[i].start();
}
}
public synchronized void stop() {
for (int i = 0; i < renderThreads.length; i++) {
if (renderThreads[i] != null) {
renderThreads[i].interrupt();
}
}
running = false;
}
public void addRenderTask(RenderTask task) {
synchronized (renderTasks) {
renderTasks.add(task);
}
}
public RenderTicket createTicket(MapType mapType, Vector2i tile) {
return createTicket(new RenderTicket(mapType, tile));
}
private RenderTicket createTicket(RenderTicket ticket) {
synchronized (renderTickets) {
if (renderTicketMap.putIfAbsent(ticket, ticket) == null) {
renderTickets.add(ticket);
return ticket;
} else {
return renderTicketMap.get(ticket);
}
}
}
public Collection<RenderTicket> createTickets(MapType mapType, Collection<Vector2i> tiles) {
if (tiles.size() < 0) return Collections.emptyList();
Collection<RenderTicket> tickets = new ArrayList<>(tiles.size());
synchronized (renderTickets) {
for (Vector2i tile : tiles) {
tickets.add(createTicket(mapType, tile));
}
}
return tickets;
}
public boolean prioritizeRenderTask(RenderTask renderTask) {
synchronized (renderTasks) {
if (renderTasks.remove(renderTask)) {
//pause first task
RenderTask currentFirst = renderTasks.peek();
if (currentFirst != null) currentFirst.pause();
renderTasks.addFirst(renderTask);
return true;
}
return false;
}
}
public boolean removeRenderTask(RenderTask renderTask) {
synchronized (renderTasks) {
return renderTasks.remove(renderTask);
}
}
private void renderThread() {
RenderTicket ticket = null;
while (running) {
synchronized (renderTickets) {
ticket = renderTickets.poll();
if (ticket != null) renderTicketMap.remove(ticket);
}
if (ticket == null) {
synchronized (renderTasks) {
RenderTask task = renderTasks.peek();
if (task != null) {
ticket = task.poll();
if (task.isFinished()) {
renderTasks.poll();
task.getMapType().getTileRenderer().save();
}
}
}
}
if (ticket != null) {
try {
ticket.render();
} catch (RuntimeException e) {
//catch possible runtime exceptions, display them, and wait a while .. then resurrect this render-thread
Logger.global.logError("Unexpected exception in render-thread!", e);
try {
Thread.sleep(10000);
} catch (InterruptedException interrupt) { Thread.currentThread().interrupt(); break; }
}
} else {
try {
Thread.sleep(1000); // we don't need a super fast response time, so waiting a second is totally fine
} catch (InterruptedException interrupt) { Thread.currentThread().interrupt(); break; }
}
if (Thread.interrupted()) {
Thread.currentThread().interrupt();
break;
}
}
}
public int getQueueSize() {
return renderTickets.size();
}
public int getRenderThreadCount() {
return renderThreads.length;
}
/**
* Returns a copy of the deque with the render tasks in order as array
*/
public RenderTask[] getRenderTasks(){
synchronized (renderTasks) {
return renderTasks.toArray(new RenderTask[renderTasks.size()]);
}
}
public int getRenderTaskCount(){
return renderTasks.size();
}
public RenderTask getCurrentRenderTask() {
return renderTasks.peek();
}
public boolean isRunning() {
return running;
}
public void writeState(DataOutputStream out) throws IOException {
//prepare renderTickets
ListMultimap<MapType, Vector2i> tileMap = MultimapBuilder.hashKeys().arrayListValues().<MapType, Vector2i>build();
synchronized (renderTickets) {
for (RenderTicket ticket : renderTickets) {
tileMap.put(ticket.getMapType(), ticket.getTile());
}
}
//write renderTickets
Set<MapType> maps = tileMap.keySet();
out.writeInt(maps.size());
for (MapType map : maps) {
List<Vector2i> tiles = tileMap.get(map);
out.writeUTF(map.getId());
out.writeInt(tiles.size());
for (Vector2i tile : tiles) {
out.writeInt(tile.getX());
out.writeInt(tile.getY());
}
}
//write tasks
synchronized (renderTasks) {
out.writeInt(renderTasks.size());
for (RenderTask task : renderTasks) {
task.write(out);
}
}
}
public void readState(DataInputStream in, Collection<MapType> mapTypes) throws IOException {
//read renderTickets
int mapCount = in.readInt();
for (int i = 0; i < mapCount; i++) {
String mapId = in.readUTF();
MapType mapType = null;
for (MapType map : mapTypes) {
if (map.getId().equals(mapId)) {
mapType = map;
break;
}
}
if (mapType == null) {
Logger.global.logWarning("Some render-tickets can not be loaded because the map (id: '" + mapId + "') does not exist anymore. They will be discarded.");
}
int tileCount = in.readInt();
List<Vector2i> tiles = new ArrayList<>();
for (int j = 0; j < tileCount; j++) {
int x = in.readInt();
int y = in.readInt();
Vector2i tile = new Vector2i(x, y);
tiles.add(tile);
}
if (mapType != null) createTickets(mapType, tiles);
}
//read tasks
int taskCount = in.readInt();
for (int i = 0; i < taskCount; i++) {
try {
RenderTask task = RenderTask.read(in, mapTypes);
addRenderTask(task);
} catch (IOException ex) {
Logger.global.logWarning("A render-task can not be loaded. It will be discared. (Error message: " + ex.toString() + ")");
}
}
}
}

View File

@ -1,217 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common;
import com.flowpowered.math.vector.Vector2d;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.*;
public class RenderTask {
private final UUID uuid;
private final String name;
private final MapType mapType;
private final Deque<Vector2i> renderTiles;
private long firstTileTime;
private long additionalRunTime;
private int renderedTiles;
public RenderTask(String name, MapType mapType) {
this.uuid = UUID.randomUUID();
this.name = name;
this.mapType = mapType;
this.renderTiles = new ArrayDeque<>();
this.firstTileTime = -1;
this.additionalRunTime = 0;
this.renderedTiles = 0;
}
public void optimizeQueue(Vector2i centerBlockPos) {
//Find a good grid size to match the MCAWorlds chunk-cache size of 500
Vector2d sortGridSize = new Vector2d(20, 20).div(mapType.getTileRenderer().getHiresModelManager().getTileSize().toDouble().div(16)).ceil().max(1, 1);
Vector2i centerTile = mapType.getTileRenderer().getHiresModelManager().posToTile(new Vector3i(centerBlockPos.getX(), 0, centerBlockPos.getY()));
synchronized (renderTiles) {
ArrayList<Vector2i> tileList = new ArrayList<>(renderTiles);
tileList.sort((v1, v2) -> {
v1 = v1.sub(centerTile);
v2 = v2.sub(centerTile);
Vector2i v1SortGridPos = v1.toDouble().div(sortGridSize).floor().toInt();
Vector2i v2SortGridPos = v2.toDouble().div(sortGridSize).floor().toInt();
if (v1SortGridPos != v2SortGridPos){
int v1Dist = v1SortGridPos.distanceSquared(Vector2i.ZERO);
int v2Dist = v2SortGridPos.distanceSquared(Vector2i.ZERO);
if (v1Dist < v2Dist) return -1;
if (v1Dist > v2Dist) return 1;
if (v1SortGridPos.getY() < v2SortGridPos.getY()) return -1;
if (v1SortGridPos.getY() > v2SortGridPos.getY()) return 1;
if (v1SortGridPos.getX() < v2SortGridPos.getX()) return -1;
if (v1SortGridPos.getX() > v2SortGridPos.getX()) return 1;
}
if (v1.getY() < v2.getY()) return -1;
if (v1.getY() > v2.getY()) return 1;
if (v1.getX() < v2.getX()) return -1;
if (v1.getX() > v2.getX()) return 1;
return 0;
});
renderTiles.clear();
renderTiles.addAll(tileList);
}
}
public void addTile(Vector2i tile) {
synchronized (renderTiles) {
renderTiles.add(tile);
}
}
public void addTiles(Collection<Vector2i> tiles) {
synchronized (renderTiles) {
renderTiles.addAll(tiles);
}
}
public RenderTicket poll() {
synchronized (renderTiles) {
Vector2i tile = renderTiles.poll();
if (tile != null) {
renderedTiles++;
if (firstTileTime < 0) firstTileTime = System.currentTimeMillis();
return new RenderTicket(mapType, tile);
} else {
return null;
}
}
}
/**
* Pauses the render-time counter.
* So if the rendering gets paused, the statistics remain correct.
* It will resume as soon as a new ticket gets polled
*/
public void pause() {
if (firstTileTime < 0) return;
synchronized (renderTiles) {
additionalRunTime += System.currentTimeMillis() - firstTileTime;
firstTileTime = -1;
}
}
public long getActiveTime() {
if (firstTileTime < 0) return additionalRunTime;
return (System.currentTimeMillis() - firstTileTime) + additionalRunTime;
}
public UUID getUuid() {
return uuid;
}
public String getName() {
return name;
}
public MapType getMapType() {
return mapType;
}
public int getRenderedTileCount() {
return renderedTiles;
}
public int getRemainingTileCount() {
return renderTiles.size();
}
public boolean isFinished() {
return renderTiles.isEmpty();
}
public void write(DataOutputStream out) throws IOException {
synchronized (renderTiles) {
pause();
out.writeUTF(name);
out.writeUTF(mapType.getId());
out.writeLong(additionalRunTime);
out.writeInt(renderedTiles);
out.writeInt(renderTiles.size());
for (Vector2i tile : renderTiles) {
out.writeInt(tile.getX());
out.writeInt(tile.getY());
}
}
}
public static RenderTask read(DataInputStream in, Collection<MapType> mapTypes) throws IOException {
String name = in.readUTF();
String mapId = in.readUTF();
MapType mapType = null;
for (MapType map : mapTypes) {
if (map.getId().equals(mapId)) {
mapType = map;
break;
}
}
if (mapType == null) throw new IOException("Map type with id '" + mapId + "' does not exist!");
RenderTask task = new RenderTask(name, mapType);
task.additionalRunTime = in.readLong();
task.renderedTiles = in.readInt();
int tileCount = in.readInt();
List<Vector2i> tiles = new ArrayList<>();
for (int i = 0; i < tileCount; i++) {
int x = in.readInt();
int y = in.readInt();
Vector2i tile = new Vector2i(x, y);
tiles.add(tile);
}
task.addTiles(tiles);
return task;
}
}

View File

@ -1,67 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common;
import java.util.Objects;
import com.flowpowered.math.vector.Vector2i;
public class RenderTicket {
private final MapType map;
private final Vector2i tile;
public RenderTicket(MapType map, Vector2i tile) {
this.map = map;
this.tile = tile;
}
public synchronized void render() {
map.renderTile(tile);
}
public MapType getMapType() {
return map;
}
public Vector2i getTile() {
return tile;
}
@Override
public int hashCode() {
return Objects.hash(map.getId(), tile);
}
@Override
public boolean equals(Object other) {
if (!(other instanceof RenderTicket)) return false;
RenderTicket ticket = (RenderTicket) other;
if (!ticket.tile.equals(tile)) return false;
return ticket.map.getId().equals(map.getId());
}
}

View File

@ -27,7 +27,7 @@
import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapAPI;
import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapMap;
import de.bluecolored.bluemap.api.BlueMapWorld; import de.bluecolored.bluemap.api.BlueMapWorld;
import de.bluecolored.bluemap.common.MapType; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.common.api.marker.MarkerAPIImpl; import de.bluecolored.bluemap.common.api.marker.MarkerAPIImpl;
import de.bluecolored.bluemap.common.api.render.RenderAPIImpl; import de.bluecolored.bluemap.common.api.render.RenderAPIImpl;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
@ -68,7 +68,7 @@ public BlueMapAPIImpl(Plugin plugin) {
} }
maps = new HashMap<>(); maps = new HashMap<>();
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
BlueMapMapImpl m = new BlueMapMapImpl(this, map); BlueMapMapImpl m = new BlueMapMapImpl(this, map);
maps.put(m.getId(), m); maps.put(m.getId(), m);
} }

View File

@ -27,14 +27,14 @@
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapMap;
import de.bluecolored.bluemap.common.MapType; import de.bluecolored.bluemap.core.map.BmMap;
public class BlueMapMapImpl implements BlueMapMap { public class BlueMapMapImpl implements BlueMapMap {
private BlueMapAPIImpl api; private BlueMapAPIImpl api;
private MapType delegate; private BmMap delegate;
protected BlueMapMapImpl(BlueMapAPIImpl api, MapType delegate) { protected BlueMapMapImpl(BlueMapAPIImpl api, BmMap delegate) {
this.api = api; this.api = api;
this.delegate = delegate; this.delegate = delegate;
} }
@ -56,15 +56,15 @@ public BlueMapWorldImpl getWorld() {
@Override @Override
public Vector2i getTileSize() { public Vector2i getTileSize() {
return delegate.getTileRenderer().getHiresModelManager().getTileSize(); return delegate.getHiresModelManager().getTileGrid().getGridSize();
} }
@Override @Override
public Vector2i getTileOffset() { public Vector2i getTileOffset() {
return delegate.getTileRenderer().getHiresModelManager().getGridOrigin(); return delegate.getHiresModelManager().getTileGrid().getOffset();
} }
public MapType getMapType() { public BmMap getMapType() {
return delegate; return delegate;
} }

View File

@ -24,16 +24,15 @@
*/ */
package de.bluecolored.bluemap.common.api.render; package de.bluecolored.bluemap.common.api.render;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapMap;
import de.bluecolored.bluemap.api.renderer.RenderAPI; import de.bluecolored.bluemap.api.renderer.RenderAPI;
import de.bluecolored.bluemap.common.RenderManager;
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
import de.bluecolored.bluemap.common.api.BlueMapMapImpl; import de.bluecolored.bluemap.common.api.BlueMapMapImpl;
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
import java.util.UUID;
public class RenderAPIImpl implements RenderAPI { public class RenderAPIImpl implements RenderAPI {
@ -68,18 +67,19 @@ public void render(BlueMapMap map, Vector2i tile) {
} else { } else {
cmap = api.getMapForId(map.getId()); cmap = api.getMapForId(map.getId());
} }
renderManager.createTicket(cmap.getMapType(), tile); //TODO
//renderManager.createTicket(cmap.getMapType(), tile);
} }
@Override @Override
public int renderQueueSize() { public int renderQueueSize() {
return renderManager.getQueueSize(); return renderManager.getScheduledRenderTasks().size();
} }
@Override @Override
public int renderThreadCount() { public int renderThreadCount() {
return renderManager.getRenderThreadCount(); return renderManager.getWorkerThreadCount();
} }
@Override @Override
@ -89,7 +89,7 @@ public boolean isRunning() {
@Override @Override
public void start() { public void start() {
if (!isRunning()) renderManager.start(); if (!isRunning()) renderManager.start(api.plugin.getCoreConfig().getRenderThreadCount());
} }
@Override @Override

View File

@ -1,220 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common.plugin;
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;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Multimap;
import com.google.common.collect.MultimapBuilder;
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<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) {
// 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) {
updateBlock(world, blockPos);
}
@Override
public void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {
int x = chunkPos.getX();
int z = chunkPos.getY();
// also update the chunks around, because they might be modified or not rendered yet due to finalizations
for (int dx = -1; dx <= 1; dx++) {
for (int dz = -1; dz <= 1; dz++) {
updateChunk(world, new Vector2i(x + dx, z + dz));
}
}
}
private void updateChunk(UUID worldUUID, Vector2i chunkPos) {
World world = plugin.getWorld(worldUUID);
if (world == null) return;
synchronized (updateBuffer) {
updateBuffer.put(worldUUID, chunkPos);
}
}
private void updateBlock(UUID worldUUID, Vector3i pos){
World world = plugin.getWorld(worldUUID);
if (world == null) return;
synchronized (updateBuffer) {
Vector2i chunkPos = world.blockPosToChunkPos(pos);
updateBuffer.put(worldUUID, chunkPos);
}
}
public int getUpdateBufferCount() {
return updateBuffer.size();
}
public void flushUpdateBuffer() {
RenderManager renderManager = plugin.getRenderManager();
synchronized (updateBuffer) {
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));
}
//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);
}
}
}

View File

@ -24,30 +24,33 @@
*/ */
package de.bluecolored.bluemap.common.plugin; package de.bluecolored.bluemap.common.plugin;
import de.bluecolored.bluemap.common.*; import de.bluecolored.bluemap.common.BlueMapService;
import de.bluecolored.bluemap.common.InterruptableReentrantLock;
import de.bluecolored.bluemap.common.MissingResourcesException;
import de.bluecolored.bluemap.common.api.BlueMapAPIImpl; import de.bluecolored.bluemap.common.api.BlueMapAPIImpl;
import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler; import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater; import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.config.CoreConfig; import de.bluecolored.bluemap.core.config.CoreConfig;
import de.bluecolored.bluemap.core.config.RenderConfig; import de.bluecolored.bluemap.core.config.RenderConfig;
import de.bluecolored.bluemap.core.config.WebServerConfig; import de.bluecolored.bluemap.core.config.WebServerConfig;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.metrics.Metrics; import de.bluecolored.bluemap.core.metrics.Metrics;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.FileUtils;
import de.bluecolored.bluemap.core.web.FileRequestHandler; import de.bluecolored.bluemap.common.web.FileRequestHandler;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
import java.io.*; import java.io.File;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class Plugin { public class Plugin {
@ -58,23 +61,26 @@ public class Plugin {
private final MinecraftVersion minecraftVersion; private final MinecraftVersion minecraftVersion;
private final String implementationType; private final String implementationType;
private ServerInterface serverInterface; private final ServerInterface serverInterface;
private BlueMapService blueMap; private BlueMapService blueMap;
private BlueMapAPIImpl api; private BlueMapAPIImpl api;
private CoreConfig coreConfig;
private RenderConfig renderConfig;
private WebServerConfig webServerConfig;
private PluginConfig pluginConfig;
private Map<UUID, World> worlds; private Map<UUID, World> worlds;
private Map<String, MapType> maps; private Map<String, BmMap> maps;
private RenderManager renderManager; private RenderManager renderManager;
private WebServer webServer; private WebServer webServer;
private final Timer daemonTimer; private final Timer daemonTimer;
private TimerTask periodicalSaveTask; private TimerTask saveTask;
private TimerTask metricsTask; private TimerTask metricsTask;
private PluginConfig pluginConfig;
private MapUpdateHandler updateHandler;
private PlayerSkinUpdater skinUpdater; private PlayerSkinUpdater skinUpdater;
private boolean loaded = false; private boolean loaded = false;
@ -98,9 +104,9 @@ public void load() throws IOException, ParseResourceException {
blueMap = new BlueMapService(minecraftVersion, serverInterface); blueMap = new BlueMapService(minecraftVersion, serverInterface);
//load configs //load configs
CoreConfig coreConfig = blueMap.getCoreConfig(); coreConfig = blueMap.getCoreConfig();
RenderConfig renderConfig = blueMap.getRenderConfig(); renderConfig = blueMap.getRenderConfig();
WebServerConfig webServerConfig = blueMap.getWebServerConfig(); webServerConfig = blueMap.getWebServerConfig();
//load plugin config //load plugin config
pluginConfig = new PluginConfig(blueMap.getConfigManager().loadOrCreate( pluginConfig = new PluginConfig(blueMap.getConfigManager().loadOrCreate(
@ -154,45 +160,8 @@ public void load() throws IOException, ParseResourceException {
} }
//initialize render manager //initialize render manager
renderManager = new RenderManager(coreConfig.getRenderThreadCount()); renderManager = new RenderManager();
renderManager.start(); renderManager.start(coreConfig.getRenderThreadCount());
//load render-manager state
try {
File saveFile = getRenderManagerSaveFile();
if (saveFile.exists()) {
try (DataInputStream in = new DataInputStream(new GZIPInputStream(new FileInputStream(saveFile)))) {
renderManager.readState(in, getMapTypes());
}
}
} catch (IOException ex) {
Logger.global.logError("Failed to load render-manager state!", ex);
}
//do periodical saves
periodicalSaveTask = new TimerTask() {
@Override
public void run() {
try {
saveRenderManagerState();
//clean up caches
for (World world : blueMap.getWorlds().values()) {
world.cleanUpChunkCache();
}
} catch (IOException ex) {
Logger.global.logError("Failed to save render-manager state!", ex);
} catch (InterruptedException ex) {
this.cancel();
Thread.currentThread().interrupt();
}
}
};
daemonTimer.schedule(periodicalSaveTask, TimeUnit.MINUTES.toMillis(5), TimeUnit.MINUTES.toMillis(5));
//start map updater
this.updateHandler = new MapUpdateHandler(this);
serverInterface.registerListener(updateHandler);
//update webapp and settings //update webapp and settings
blueMap.createOrUpdateWebApp(false); blueMap.createOrUpdateWebApp(false);
@ -206,6 +175,20 @@ public void run() {
); );
serverInterface.registerListener(skinUpdater); serverInterface.registerListener(skinUpdater);
} }
//periodically save
saveTask = new TimerTask() {
@Override
public void run() {
synchronized (Plugin.this) {
if (maps == null) return;
for (BmMap map : maps.values()) {
map.save();
}
}
}
};
daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(10));
//metrics //metrics
metricsTask = new TimerTask() { metricsTask = new TimerTask() {
@ -222,6 +205,8 @@ public void run() {
//enable api //enable api
this.api = new BlueMapAPIImpl(this); this.api = new BlueMapAPIImpl(this);
this.api.register(); this.api.register();
//
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
@ -246,26 +231,15 @@ public void unload() {
//stop scheduled threads //stop scheduled threads
metricsTask.cancel(); metricsTask.cancel();
periodicalSaveTask.cancel();
//stop services //stop services
if (renderManager != null) renderManager.stop(); if (renderManager != null) renderManager.stop();
if (webServer != null) webServer.close(); if (webServer != null) webServer.close();
//save render-manager state
if (updateHandler != null) updateHandler.flushUpdateBuffer(); //first write all buffered changes to the render manager to save them too
if (renderManager != null) {
try {
saveRenderManagerState();
} catch (IOException ex) {
Logger.global.logError("Failed to save render-manager state!", ex);
}
}
//save renders //save renders
if (maps != null) { if (maps != null) {
for (MapType map : maps.values()) { for (BmMap map : maps.values()) {
map.getTileRenderer().save(); map.save();
} }
} }
@ -275,7 +249,8 @@ public void unload() {
maps = null; maps = null;
renderManager = null; renderManager = null;
webServer = null; webServer = null;
updateHandler = null;
pluginConfig = null; pluginConfig = null;
loaded = false; loaded = false;
@ -285,39 +260,32 @@ public void unload() {
loadingLock.unlock(); loadingLock.unlock();
} }
} }
public void saveRenderManagerState() throws IOException {
File saveFile = getRenderManagerSaveFile();
if (saveFile.exists()) FileUtils.delete(saveFile);
FileUtils.createFile(saveFile);
try (DataOutputStream out = new DataOutputStream(new GZIPOutputStream(new FileOutputStream(saveFile)))) {
renderManager.writeState(out);
}
}
public void reload() throws IOException, ParseResourceException { public void reload() throws IOException, ParseResourceException {
unload(); unload();
load(); load();
} }
public boolean flushWorldUpdates(UUID worldUUID) throws IOException {
return serverInterface.persistWorldChanges(worldUUID);
}
public ServerInterface getServerInterface() { public ServerInterface getServerInterface() {
return serverInterface; return serverInterface;
} }
public CoreConfig getCoreConfig() throws IOException { public CoreConfig getCoreConfig() {
return blueMap.getCoreConfig(); return coreConfig;
} }
public RenderConfig getRenderConfig() throws IOException { public RenderConfig getRenderConfig() {
return blueMap.getRenderConfig(); return renderConfig;
} }
public WebServerConfig getWebServerConfig() throws IOException { public WebServerConfig getWebServerConfig() {
return blueMap.getWebServerConfig(); return webServerConfig;
} }
public PluginConfig getPluginConfig() { public PluginConfig getPluginConfig() {
return pluginConfig; return pluginConfig;
} }
@ -330,7 +298,7 @@ public Collection<World> getWorlds(){
return worlds.values(); return worlds.values();
} }
public Collection<MapType> getMapTypes(){ public Collection<BmMap> getMapTypes(){
return maps.values(); return maps.values();
} }
@ -338,24 +306,6 @@ public RenderManager getRenderManager() {
return renderManager; return renderManager;
} }
public File getRenderManagerSaveFile() throws IOException {
if (blueMap == null) return null;
return new File(blueMap.getCoreConfig().getDataFolder(), "rmstate");
}
public MapUpdateHandler getUpdateHandler() {
return updateHandler;
}
public boolean flushWorldUpdates(UUID worldUUID) throws IOException {
if (serverInterface.persistWorldChanges(worldUUID)) {
updateHandler.onWorldSaveToDisk(worldUUID);
return true;
}
return false;
}
public WebServer getWebServer() { public WebServer getWebServer() {
return webServer; return webServer;
} }

View File

@ -24,29 +24,16 @@
*/ */
package de.bluecolored.bluemap.common.plugin.commands; package de.bluecolored.bluemap.common.plugin.commands;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringJoiner;
import java.util.function.Predicate;
import com.flowpowered.math.vector.Vector2l;
import org.apache.commons.lang3.time.DurationFormatUtils;
import com.flowpowered.math.GenericMath;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.common.MapType;
import de.bluecolored.bluemap.common.RenderManager;
import de.bluecolored.bluemap.common.RenderTask;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource;
import de.bluecolored.bluemap.common.plugin.text.Text; import de.bluecolored.bluemap.common.plugin.text.Text;
import de.bluecolored.bluemap.common.plugin.text.TextColor; import de.bluecolored.bluemap.common.plugin.text.TextColor;
import de.bluecolored.bluemap.common.plugin.text.TextFormat; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
import java.util.ArrayList;
import java.util.List;
import java.util.StringJoiner;
public class CommandHelper { public class CommandHelper {
private Plugin plugin; private Plugin plugin;
@ -57,119 +44,12 @@ public CommandHelper(Plugin plugin) {
public List<Text> createStatusMessage(){ public List<Text> createStatusMessage(){
List<Text> lines = new ArrayList<>(); List<Text> lines = new ArrayList<>();
RenderManager renderer = plugin.getRenderManager();
lines.add(Text.of());
lines.add(Text.of(TextColor.BLUE, "Tile-Updates:"));
if (renderer.isRunning()) { //TODO
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.GREEN, "running")
.setHoverText(Text.of("click to pause rendering"))
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap pause"),
TextColor.GRAY, "!"));
} else {
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ",
Text.of(TextColor.RED, "paused")
.setHoverText(Text.of("click to resume rendering"))
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap resume"),
TextColor.GRAY, "!"));
}
lines.add(Text.of(
TextColor.WHITE, " Scheduled tile-updates: ",
TextColor.GOLD, renderer.getQueueSize()).setHoverText(
Text.of(
TextColor.WHITE, "Tiles waiting for a free render-thread: ", TextColor.GOLD, renderer.getQueueSize(),
TextColor.WHITE, "\n\nChunks marked as changed: ", TextColor.GOLD, plugin.getUpdateHandler().getUpdateBufferCount(),
TextColor.GRAY, TextFormat.ITALIC, "\n(Changed chunks will be rendered as soon as they are saved back to the world-files)"
)
)
);
RenderTask[] tasks = renderer.getRenderTasks();
if (tasks.length > 0) {
RenderTask task = tasks[0];
long time = task.getActiveTime();
String durationString = DurationFormatUtils.formatDurationWords(time, true, true);
double pct = (double)task.getRenderedTileCount() / (double)(task.getRenderedTileCount() + task.getRemainingTileCount());
long ert = (long)((time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
double tps = task.getRenderedTileCount() / (time / 1000.0);
lines.add(Text.of(TextColor.BLUE, "Current task:"));
lines.add(Text.of(" ", createCancelTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName()))));
lines.add(Text.of(TextColor.WHITE, " rendered ", TextColor.GOLD, task.getRenderedTileCount(), TextColor.WHITE, " tiles ", TextColor.GRAY, "(" + (Math.round(pct * 1000)/10.0) + "% | " + GenericMath.round(tps, 1) + "t/s)", TextColor.WHITE, " in ", TextColor.GOLD, durationString));
lines.add(Text.of(TextColor.WHITE, " with ", TextColor.GOLD, task.getRemainingTileCount(), TextColor.WHITE, " tiles to go. ETA: ", TextColor.GOLD, ertDurationString));
}
if (tasks.length > 1) {
lines.add(Text.of(TextColor.BLUE, "Waiting tasks:"));
for (int i = 1; i < tasks.length; i++) {
RenderTask task = tasks[i];
lines.add(Text.of(" ", createCancelTaskText(task), createPrioritizeTaskText(task), TextColor.WHITE, " Task ", TextColor.GOLD, task.getName(), TextColor.WHITE, " for map ", Text.of(TextColor.GOLD, task.getMapType().getName()).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GOLD, task.getMapType().getWorld().getName())), TextColor.GRAY, " (" + task.getRemainingTileCount() + " tiles)"));
}
}
return lines; return lines;
} }
private Text createCancelTaskText(RenderTask task) {
return Text.of(TextColor.RED, "[X]").setHoverText(Text.of(TextColor.GRAY, "click to cancel this render-task")).setClickAction(Text.ClickAction.RUN_COMMAND,"/bluemap render cancel " + task.getUuid());
}
private Text createPrioritizeTaskText(RenderTask task) {
return Text.of(TextColor.GREEN, "[^]").setHoverText(Text.of(TextColor.GRAY, "click to prioritize this render-task")).setClickAction(Text.ClickAction.RUN_COMMAND,"/bluemap render prioritize " + task.getUuid());
}
public void createWorldRenderTask(CommandSource source, World world, Vector2i center, long blockRadius) {
for (MapType map : plugin.getMapTypes()) {
if (!map.getWorld().getUUID().equals(world.getUUID())) continue;
createMapRenderTask(source, map, center, blockRadius);
}
source.sendMessage(Text.of(TextColor.GREEN, "All render tasks created! Use /bluemap to view the progress!"));
}
public void createMapRenderTask(CommandSource source, MapType map, Vector2i center, long blockRadius) {
source.sendMessage(Text.of(TextColor.GOLD, "Creating render-task for map: " + map.getId()));
source.sendMessage(Text.of(TextColor.GOLD, "Collecting chunks..."));
String taskName = "world-render";
Vector2i renderCenter = map.getWorld().getSpawnPoint().toVector2(true);
Predicate<Vector2i> filter;
if (center == null || blockRadius < 0) {
filter = c -> true;
} else {
Vector2l centerL = center.toLong(); //use longs to avoid int-overflow
filter = c -> c.toLong().mul(16).distanceSquared(centerL) <= blockRadius * blockRadius;
taskName = "radius-render";
renderCenter = center;
}
Collection<Vector2i> chunks = map.getWorld().getChunkList(filter);
source.sendMessage(Text.of(TextColor.GREEN, chunks.size() + " chunks found!"));
source.sendMessage(Text.of(TextColor.GOLD, "Collecting tiles..."));
HiresModelManager hmm = map.getTileRenderer().getHiresModelManager();
Collection<Vector2i> tiles = hmm.getTilesForChunks(chunks);
RenderTask task = new RenderTask(taskName, map);
task.addTiles(tiles);
task.optimizeQueue(renderCenter);
plugin.getRenderManager().addRenderTask(task);
source.sendMessage(Text.of(TextColor.GREEN, tiles.size() + " tiles found! Task created."));
}
public Text worldHelperHover() { public Text worldHelperHover() {
StringJoiner joiner = new StringJoiner("\n"); StringJoiner joiner = new StringJoiner("\n");
for (World world : plugin.getWorlds()) { for (World world : plugin.getWorlds()) {
@ -181,27 +61,10 @@ public Text worldHelperHover() {
public Text mapHelperHover() { public Text mapHelperHover() {
StringJoiner joiner = new StringJoiner("\n"); StringJoiner joiner = new StringJoiner("\n");
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
joiner.add(map.getId()); joiner.add(map.getId());
} }
return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString())); return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
} }
public boolean checkLoaded(CommandSource source) {
if (!plugin.isLoaded()) {
source.sendMessage(Text.of(TextColor.RED, "BlueMap is not loaded!", TextColor.GRAY, "(Try /bluemap reload)"));
return false;
}
return true;
}
public boolean checkPermission(CommandSource source, String permission) {
if (source.hasPermission(permission)) return true;
source.sendMessage(Text.of(TextColor.RED, "You don't have the permissions to use this command!"));
return false;
}
} }

View File

@ -24,18 +24,6 @@
*/ */
package de.bluecolored.bluemap.common.plugin.commands; package de.bluecolored.bluemap.common.plugin.commands;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion;
import org.apache.commons.io.FileUtils;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
@ -49,25 +37,34 @@
import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode; import com.mojang.brigadier.tree.LiteralCommandNode;
import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapAPI;
import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapMap;
import de.bluecolored.bluemap.api.marker.MarkerAPI; import de.bluecolored.bluemap.api.marker.MarkerAPI;
import de.bluecolored.bluemap.api.marker.MarkerSet; import de.bluecolored.bluemap.api.marker.MarkerSet;
import de.bluecolored.bluemap.api.marker.POIMarker; import de.bluecolored.bluemap.api.marker.POIMarker;
import de.bluecolored.bluemap.common.MapType;
import de.bluecolored.bluemap.common.RenderTask;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource; import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource;
import de.bluecolored.bluemap.common.plugin.text.Text; import de.bluecolored.bluemap.common.plugin.text.Text;
import de.bluecolored.bluemap.common.plugin.text.TextColor; import de.bluecolored.bluemap.common.plugin.text.TextColor;
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.Chunk; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.mca.MCAChunk;
import de.bluecolored.bluemap.core.mca.ChunkAnvil112; import de.bluecolored.bluemap.core.mca.ChunkAnvil112;
import de.bluecolored.bluemap.core.mca.MCAWorld; import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import java.util.function.Predicate;
public class Commands<S> { public class Commands<S> {
@ -172,23 +169,6 @@ public void init() {
.then(argument("radius", IntegerArgumentType.integer()) .then(argument("radius", IntegerArgumentType.integer())
.executes(this::renderCommand))))) // /bluemap render <world|map> <x> <z> <radius> .executes(this::renderCommand))))) // /bluemap render <world|map> <x> <z> <radius>
.build(); .build();
LiteralCommandNode<S> prioRenderCommand =
literal("prioritize")
.requires(requirements("bluemap.render"))
.then(argument("uuid", StringArgumentType.string())
.executes(this::prioritizeRenderTaskCommand))
.build();
LiteralCommandNode<S> cancelRenderCommand =
literal("cancel")
.requires(requirements("bluemap.render"))
.executes(this::cancelLastRenderTaskCommand)
.then(argument("uuid", StringArgumentType.string())
.executes(this::cancelRenderTaskCommand))
.build();
LiteralCommandNode<S> purgeCommand = LiteralCommandNode<S> purgeCommand =
literal("purge") literal("purge")
@ -247,8 +227,6 @@ public void init() {
baseCommand.addChild(resumeCommand); baseCommand.addChild(resumeCommand);
baseCommand.addChild(renderCommand); baseCommand.addChild(renderCommand);
baseCommand.addChild(purgeCommand); baseCommand.addChild(purgeCommand);
renderCommand.addChild(prioRenderCommand);
renderCommand.addChild(cancelRenderCommand);
baseCommand.addChild(worldsCommand); baseCommand.addChild(worldsCommand);
baseCommand.addChild(mapsCommand); baseCommand.addChild(mapsCommand);
baseCommand.addChild(markerCommand); baseCommand.addChild(markerCommand);
@ -296,8 +274,8 @@ private Optional<World> parseWorld(String worldName) {
return Optional.empty(); return Optional.empty();
} }
private Optional<MapType> parseMap(String mapId) { private Optional<BmMap> parseMap(String mapId) {
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
if (map.getId().equalsIgnoreCase(mapId)) { if (map.getId().equalsIgnoreCase(mapId)) {
return Optional.of(map); return Optional.of(map);
} }
@ -334,7 +312,7 @@ public int versionCommand(CommandContext<S> context) {
int renderThreadCount = 0; int renderThreadCount = 0;
if (plugin.isLoaded()) { if (plugin.isLoaded()) {
renderThreadCount = plugin.getRenderManager().getRenderThreadCount(); renderThreadCount = plugin.getRenderManager().getWorkerThreadCount();
} }
source.sendMessage(Text.of(TextFormat.BOLD, TextColor.BLUE, "Version: ", TextColor.WHITE, BlueMap.VERSION)); source.sendMessage(Text.of(TextFormat.BOLD, TextColor.BLUE, "Version: ", TextColor.WHITE, BlueMap.VERSION));
@ -504,7 +482,7 @@ public int debugBlockCommand(CommandContext<S> context) {
String blockBelowIdMeta = ""; String blockBelowIdMeta = "";
if (world instanceof MCAWorld) { if (world instanceof MCAWorld) {
Chunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos)); MCAChunk chunk = ((MCAWorld) world).getChunk(MCAWorld.blockToChunk(blockPos));
if (chunk instanceof ChunkAnvil112) { if (chunk instanceof ChunkAnvil112) {
blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")"; blockIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos) + ")";
blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")"; blockBelowIdMeta = " (" + ((ChunkAnvil112) chunk).getBlockIdMeta(blockPos.add(0, -1, 0)) + ")";
@ -512,7 +490,6 @@ public int debugBlockCommand(CommandContext<S> context) {
} }
source.sendMessages(Lists.newArrayList( source.sendMessages(Lists.newArrayList(
Text.of(TextColor.GOLD, "Is generated: ", TextColor.WHITE, world.isChunkGenerated(world.blockPosToChunkPos(blockPos))),
Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta), Text.of(TextColor.GOLD, "Block at you: ", TextColor.WHITE, block, TextColor.GRAY, blockIdMeta),
Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta) Text.of(TextColor.GOLD, "Block below you: ", TextColor.WHITE, blockBelow, TextColor.GRAY, blockBelowIdMeta)
)); ));
@ -538,7 +515,7 @@ public int resumeCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource()); CommandSource source = commandSourceInterface.apply(context.getSource());
if (!plugin.getRenderManager().isRunning()) { if (!plugin.getRenderManager().isRunning()) {
plugin.getRenderManager().start(); plugin.getRenderManager().start(plugin.getCoreConfig().getRenderThreadCount());
source.sendMessage(Text.of(TextColor.GREEN, "BlueMap renders resumed!")); source.sendMessage(Text.of(TextColor.GREEN, "BlueMap renders resumed!"));
return 1; return 1;
} else { } else {
@ -554,7 +531,7 @@ public int renderCommand(CommandContext<S> context) {
Optional<String> worldOrMap = getOptionalArgument(context, "world|map", String.class); Optional<String> worldOrMap = getOptionalArgument(context, "world|map", String.class);
final World world; final World world;
final MapType map; final BmMap map;
if (worldOrMap.isPresent()) { if (worldOrMap.isPresent()) {
world = parseWorld(worldOrMap.get()).orElse(null); world = parseWorld(worldOrMap.get()).orElse(null);
@ -605,10 +582,10 @@ public int renderCommand(CommandContext<S> context) {
try { try {
if (world != null) { if (world != null) {
plugin.getServerInterface().persistWorldChanges(world.getUUID()); plugin.getServerInterface().persistWorldChanges(world.getUUID());
helper.createWorldRenderTask(source, world, center, radius); //TODO: helper.createWorldRenderTask(source, world, center, radius);
} else { } else {
plugin.getServerInterface().persistWorldChanges(map.getWorld().getUUID()); plugin.getServerInterface().persistWorldChanges(map.getWorld().getUUID());
helper.createMapRenderTask(source, map, center, radius); //TODO: helper.createMapRenderTask(source, map, center, radius);
} }
} catch (IOException ex) { } catch (IOException ex) {
source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to save the world. Please check the console for more details...")); source.sendMessage(Text.of(TextColor.RED, "There was an unexpected exception trying to save the world. Please check the console for more details..."));
@ -644,68 +621,6 @@ public int purgeCommand(CommandContext<S> context) {
return 1; return 1;
} }
public int prioritizeRenderTaskCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
String uuidString = context.getArgument("uuid", String.class);
Optional<UUID> taskUUID = parseUUID(uuidString);
if (!taskUUID.isPresent()) {
source.sendMessage(Text.of(TextColor.RED, "Not a valid UUID: " + uuidString));
return 0;
}
for (RenderTask task : plugin.getRenderManager().getRenderTasks()) {
if (task.getUuid().equals(taskUUID.get())) {
plugin.getRenderManager().prioritizeRenderTask(task);
source.sendMessages(helper.createStatusMessage());
return 1;
}
}
source.sendMessage(Text.of(TextColor.RED, "There is no render-task with this UUID: " + uuidString));
return 0;
}
public int cancelLastRenderTaskCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
RenderTask[] tasks = plugin.getRenderManager().getRenderTasks();
if (tasks.length == 0) {
source.sendMessage(Text.of(TextColor.RED, "There is currently no render task scheduled!"));
return 0;
}
RenderTask task = tasks[tasks.length - 1];
plugin.getRenderManager().removeRenderTask(task);
source.sendMessage(Text.of(TextColor.GREEN, "The render-task '" + task.getName() + "' has been canceled!"));
return 1;
}
public int cancelRenderTaskCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
String uuidString = context.getArgument("uuid", String.class);
Optional<UUID> taskUUID = parseUUID(uuidString);
if (!taskUUID.isPresent()) {
source.sendMessage(Text.of(TextColor.RED, "Not a valid UUID: " + uuidString));
return 0;
}
for (RenderTask task : plugin.getRenderManager().getRenderTasks()) {
if (task.getUuid().equals(taskUUID.get())) {
plugin.getRenderManager().removeRenderTask(task);
source.sendMessages(helper.createStatusMessage());
return 1;
}
}
source.sendMessage(Text.of(TextColor.RED, "There is no render-task with this UUID: " + uuidString));
return 0;
}
public int worldsCommand(CommandContext<S> context) { public int worldsCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource()); CommandSource source = commandSourceInterface.apply(context.getSource());
@ -721,7 +636,7 @@ public int mapsCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource()); CommandSource source = commandSourceInterface.apply(context.getSource());
source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:")); source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")").setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName()))); source.sendMessage(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, map.getId(), TextColor.GRAY, " (" + map.getName() + ")").setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName())));
} }
@ -738,7 +653,7 @@ public int createMarkerCommand(CommandContext<S> context) {
// parse world/map argument // parse world/map argument
String mapString = context.getArgument("map", String.class); String mapString = context.getArgument("map", String.class);
MapType map = parseMap(mapString).orElse(null); BmMap map = parseMap(mapString).orElse(null);
if (map == null) { if (map == null) {
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString)); source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));

View File

@ -27,7 +27,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import de.bluecolored.bluemap.common.MapType; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
public class MapSuggestionProvider<S> extends AbstractSuggestionProvider<S> { public class MapSuggestionProvider<S> extends AbstractSuggestionProvider<S> {
@ -42,7 +42,7 @@ public MapSuggestionProvider(Plugin plugin) {
public Collection<String> getPossibleValues() { public Collection<String> getPossibleValues() {
Collection<String> values = new HashSet<>(); Collection<String> values = new HashSet<>();
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
values.add(map.getId()); values.add(map.getId());
} }

View File

@ -27,7 +27,7 @@
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import de.bluecolored.bluemap.common.MapType; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.common.plugin.Plugin; import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.core.world.World; import de.bluecolored.bluemap.core.world.World;
@ -47,7 +47,7 @@ public Collection<String> getPossibleValues() {
values.add(world.getName()); values.add(world.getName());
} }
for (MapType map : plugin.getMapTypes()) { for (BmMap map : plugin.getMapTypes()) {
values.add(map.getId()); values.add(map.getId());
} }

View File

@ -24,23 +24,15 @@
*/ */
package de.bluecolored.bluemap.common.plugin.serverinterface; package de.bluecolored.bluemap.common.plugin.serverinterface;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.text.Text; import de.bluecolored.bluemap.common.plugin.text.Text;
import java.util.UUID;
public interface ServerEventListener { public interface ServerEventListener {
default void onWorldSaveToDisk(UUID world) {};
default void onChunkSaveToDisk(UUID world, Vector2i chunkPos) {};
default void onBlockChange(UUID world, Vector3i blockPos) {}; default void onBlockChange(UUID world, Vector3i blockPos) {};
default void onChunkFinishedGeneration(UUID world, Vector2i chunkPos) {};
default void onPlayerJoin(UUID playerUuid) {}; default void onPlayerJoin(UUID playerUuid) {};
default void onPlayerLeave(UUID playerUuid) {}; default void onPlayerLeave(UUID playerUuid) {};

View File

@ -95,7 +95,7 @@ default boolean isMetricsEnabled(boolean configValue) {
/** /**
* Returns the state of the player with that UUID if present<br> * Returns the state of the player with that UUID if present<br>
* this method is only guaranteed to return a {@link PlayerState} if the player is currently online. * this method is only guaranteed to return a {@link Player} if the player is currently online.
*/ */
Optional<Player> getPlayer(UUID uuid); Optional<Player> getPlayer(UUID uuid);

View File

@ -0,0 +1,57 @@
package de.bluecolored.bluemap.common.rendermanager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class CombinedRenderTask<T extends RenderTask> implements RenderTask {
private final List<T> tasks;
private int currentTaskIndex;
public CombinedRenderTask(Collection<T> tasks) {
this.tasks = new ArrayList<>();
this.tasks.addAll(tasks);
this.currentTaskIndex = 0;
}
@Override
public void doWork() throws Exception {
T task;
synchronized (this.tasks) {
if (!hasMoreWork()) return;
task = this.tasks.get(this.currentTaskIndex);
if (!task.hasMoreWork()){
this.currentTaskIndex++;
return;
}
}
task.doWork();
}
@Override
public boolean hasMoreWork() {
return this.currentTaskIndex < this.tasks.size();
}
@Override
public double estimateProgress() {
synchronized (this.tasks) {
if (!hasMoreWork()) return 1;
double total = currentTaskIndex;
total += this.tasks.get(this.currentTaskIndex).estimateProgress();
return total / tasks.size();
}
}
@Override
public void cancel() {
for (T task : tasks) task.cancel();
}
}

View File

@ -0,0 +1,253 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.common.rendermanager;
import de.bluecolored.bluemap.core.logger.Logger;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
public class RenderManager {
private static final AtomicInteger nextRenderManagerIndex = new AtomicInteger(0);
private final int id;
private volatile boolean running;
private final AtomicInteger nextWorkerThreadIndex;
private final Collection<WorkerThread> workerThreads;
private final AtomicInteger busyCount;
private final LinkedList<RenderTask> renderTasks;
private final Set<RenderTask> renderTaskSet;
public RenderManager() {
this.id = nextRenderManagerIndex.getAndIncrement();
this.nextWorkerThreadIndex = new AtomicInteger(0);
this.running = false;
this.workerThreads = new ConcurrentLinkedDeque<>();
this.busyCount = new AtomicInteger(0);
this.renderTasks = new LinkedList<>();
this.renderTaskSet = new HashSet<>();
}
public void start(int threadCount) throws IllegalStateException {
if (threadCount <= 0) throw new IllegalArgumentException("threadCount has to be 1 or more!");
synchronized (this.workerThreads) {
if (isRunning()) throw new IllegalStateException("RenderManager is already running!");
this.workerThreads.clear();
this.busyCount.set(0);
this.running = true;
for (int i = 0; i < threadCount; i++) {
WorkerThread worker = new WorkerThread();
this.workerThreads.add(worker);
worker.start();
}
}
}
public void stop() {
synchronized (this.workerThreads) {
this.running = false;
for (WorkerThread worker : workerThreads) worker.interrupt();
}
}
public boolean isIdle() {
return busyCount.get() == 0;
}
public boolean isRunning() {
synchronized (this.workerThreads) {
for (WorkerThread worker : workerThreads) {
if (worker.isAlive()) return true;
}
return false;
}
}
public void awaitShutdown() throws InterruptedException {
synchronized (this.workerThreads) {
while (isRunning())
this.workerThreads.wait(10000);
}
}
public boolean scheduleRenderTask(RenderTask task) {
synchronized (this.renderTasks) {
if (renderTaskSet.add(task)) {
renderTasks.addLast(task);
renderTasks.notifyAll();
return true;
} else {
return false;
}
}
}
public boolean scheduleRenderTaskNext(RenderTask task) {
synchronized (this.renderTasks) {
if (renderTasks.size() <= 1) return scheduleRenderTask(task);
if (renderTaskSet.add(task)) {
renderTasks.add(1, task);
renderTasks.notifyAll();
return true;
} else {
return false;
}
}
}
public void reorderRenderTasks(Comparator<RenderTask> taskComparator) {
synchronized (this.renderTasks) {
if (renderTasks.size() <= 2) return;
RenderTask currentTask = renderTasks.removeFirst();
renderTasks.sort(taskComparator);
renderTasks.addFirst(currentTask);
}
}
public boolean removeTask(RenderTask task) {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return false;
// cancel the task if it is currently processed
RenderTask first = renderTasks.getFirst();
if (first.equals(task)) {
first.cancel();
return true;
}
// else remove it
return renderTaskSet.remove(task) && renderTasks.remove(task);
}
}
public void removeAllTasks() {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return;
RenderTask first = renderTasks.removeFirst();
first.cancel();
renderTasks.clear();
renderTasks.addFirst(first);
}
}
public List<RenderTask> getScheduledRenderTasks() {
return Collections.unmodifiableList(renderTasks);
}
public int getWorkerThreadCount() {
return workerThreads.size();
}
private void doWork() throws Exception {
RenderTask task;
synchronized (this.renderTasks) {
while (this.renderTasks.isEmpty())
this.renderTasks.wait(10000);
task = this.renderTasks.getFirst();
// the following is making sure every render-thread is done working on this task (no thread is "busy")
// before continuing working on the next RenderTask
if (!task.hasMoreWork()) {
if (busyCount.get() <= 0) {
this.renderTaskSet.remove(this.renderTasks.removeFirst());
busyCount.set(0);
} else {
this.renderTasks.wait(10000);
}
return;
}
this.busyCount.incrementAndGet();
}
try {
task.doWork();
} finally {
synchronized (renderTasks) {
this.busyCount.decrementAndGet();
this.renderTasks.notifyAll();
}
}
}
public class WorkerThread extends Thread {
private final int id;
private WorkerThread() {
this.id = RenderManager.this.nextWorkerThreadIndex.getAndIncrement();
this.setName("RenderManager-" + RenderManager.this.id + "-" + this.id);
}
@Override
@SuppressWarnings("BusyWait")
public void run() {
try {
while (RenderManager.this.running) {
try {
RenderManager.this.doWork();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
Logger.global.logError(
"RenderManager(" + RenderManager.this.id + "): WorkerThread(" + this.id +
"): Exception while doing some work!", e);
try {
// on error, wait a few seconds before resurrecting this render-thread
// if something goes wrong, this prevents running into the same error on all render-threads
// with full-speed over and over again :D
Thread.sleep(10000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
} finally {
synchronized (RenderManager.this.workerThreads) {
RenderManager.this.workerThreads.remove(this);
RenderManager.this.workerThreads.notifyAll();
}
}
}
}
}

View File

@ -0,0 +1,25 @@
package de.bluecolored.bluemap.common.rendermanager;
public interface RenderTask {
void doWork() throws Exception;
/**
* Whether this task is requesting more calls to its {@link #doWork()} method.<br>
* This can be false because the task is finished, OR because the task got cancelled and decides to interrupt.
*/
boolean hasMoreWork();
/**
* The estimated progress made so far, from 0 to 1.
*/
default double estimateProgress() {
return 0d;
}
/**
* Requests to cancel this task. The task then self-decides what to do with this request.
*/
void cancel();
}

View File

@ -0,0 +1,136 @@
package de.bluecolored.bluemap.common.rendermanager;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.Region;
import java.util.Collection;
import java.util.TreeSet;
public class WorldRegionRenderTask implements RenderTask {
private final BmMap map;
private final Vector2i worldRegion;
private final boolean force;
private TreeSet<Vector2i> tiles;
private int tileCount;
private long startTime;
private volatile int atWork;
private volatile boolean cancelled;
public WorldRegionRenderTask(BmMap map, Vector2i worldRegion) {
this(map, worldRegion, false);
}
public WorldRegionRenderTask(BmMap map, Vector2i worldRegion, boolean force) {
this.map = map;
this.worldRegion = worldRegion;
this.force = force;
this.tiles = null;
this.tileCount = -1;
this.startTime = -1;
this.atWork = 0;
this.cancelled = false;
}
private synchronized void init() {
tiles = new TreeSet<>();
startTime = System.currentTimeMillis();
long changesSince = 0;
if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion);
Region region = map.getWorld().getRegion(worldRegion.getX(), worldRegion.getY());
Collection<Vector2i> chunks = region.listChunks(changesSince);
Grid tileGrid = map.getHiresModelManager().getTileGrid();
Grid chunkGrid = map.getWorld().getChunkGrid();
for (Vector2i chunk : chunks) {
Vector2i tileMin = chunkGrid.getCellMin(chunk, tileGrid);
Vector2i tileMax = chunkGrid.getCellMax(chunk, tileGrid);
for (int x = tileMin.getX(); x < tileMax.getX(); x++) {
for (int z = tileMin.getY(); z < tileMax.getY(); z++) {
tiles.add(new Vector2i(x, z));
}
}
}
this.tileCount = tiles.size();
if (tiles.isEmpty()) complete();
}
@Override
public void doWork() {
if (cancelled) return;
Vector2i tile;
synchronized (this) {
if (tiles == null) init();
if (tiles.isEmpty()) return;
tile = tiles.pollFirst();
this.atWork++;
}
map.renderTile(tile); // <- actual work
synchronized (this) {
this.atWork--;
if (atWork <= 0 && tiles.isEmpty() && !cancelled) {
complete();
}
}
}
private void complete() {
map.getRenderState().setRenderTime(worldRegion, startTime);
}
@Override
public boolean hasMoreWork() {
return !cancelled && !tiles.isEmpty();
}
@Override
public double estimateProgress() {
if (tiles == null) return 0;
if (tileCount == 0) return 1;
double remainingTiles = tiles.size();
return remainingTiles / this.tileCount;
}
@Override
public void cancel() {
this.cancelled = true;
synchronized (this) {
if (tiles != null) this.tiles.clear();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
WorldRegionRenderTask that = (WorldRegionRenderTask) o;
return force == that.force && map.equals(that.map) && worldRegion.equals(that.worldRegion);
}
@Override
public int hashCode() {
return worldRegion.hashCode();
}
}

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.web; package de.bluecolored.bluemap.common.web;
import de.bluecolored.bluemap.core.webserver.HttpRequest; import de.bluecolored.bluemap.core.webserver.HttpRequest;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;

View File

@ -22,15 +22,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.web; package de.bluecolored.bluemap.common.web;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3f;
import de.bluecolored.bluemap.core.config.MapConfig; import de.bluecolored.bluemap.core.config.MapConfig;
import de.bluecolored.bluemap.core.render.TileRenderer; import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.FileUtils;
import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.MathUtils;
import de.bluecolored.bluemap.core.world.World;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
import ninja.leaping.configurate.gson.GsonConfigurationLoader; import ninja.leaping.configurate.gson.GsonConfigurationLoader;
import ninja.leaping.configurate.loader.ConfigurationLoader; import ninja.leaping.configurate.loader.ConfigurationLoader;
@ -92,7 +91,7 @@ public double getDouble(Object... path) {
} }
public Collection<String> getMapIds() { public Collection<String> getMapIds() {
return rootNode.getNode("maps").getChildrenMap().keySet().stream().map(o -> o.toString()).collect(Collectors.toSet()); return rootNode.getNode("maps").getChildrenMap().keySet().stream().map(Object::toString).collect(Collectors.toSet());
} }
public void setAllMapsEnabled(boolean enabled) { public void setAllMapsEnabled(boolean enabled) {
@ -105,51 +104,49 @@ public void setMapEnabled(boolean enabled, String mapId) {
set(enabled, "maps", mapId, "enabled"); set(enabled, "maps", mapId, "enabled");
} }
public void setFrom(TileRenderer tileRenderer, String mapId) { public void setFrom(BmMap map) {
Vector2i hiresTileSize = tileRenderer.getHiresModelManager().getTileSize(); Vector2i hiresTileSize = map.getHiresModelManager().getTileGrid().getGridSize();
Vector2i gridOrigin = tileRenderer.getHiresModelManager().getGridOrigin(); Vector2i gridOrigin = map.getHiresModelManager().getTileGrid().getOffset();
Vector2i lowresTileSize = tileRenderer.getLowresModelManager().getTileSize(); Vector2i lowresTileSize = map.getLowresModelManager().getTileSize();
Vector2i lowresPointsPerHiresTile = tileRenderer.getLowresModelManager().getPointsPerHiresTile(); Vector2i lowresPointsPerHiresTile = map.getLowresModelManager().getPointsPerHiresTile();
set(hiresTileSize.getX(), "maps", mapId, "hires", "tileSize", "x"); set(hiresTileSize.getX(), "maps", map.getId(), "hires", "tileSize", "x");
set(hiresTileSize.getY(), "maps", mapId, "hires", "tileSize", "z"); set(hiresTileSize.getY(), "maps", map.getId(), "hires", "tileSize", "z");
set(1, "maps", mapId, "hires", "scale", "x"); set(1, "maps", map.getId(), "hires", "scale", "x");
set(1, "maps", mapId, "hires", "scale", "z"); set(1, "maps", map.getId(), "hires", "scale", "z");
set(gridOrigin.getX(), "maps", mapId, "hires", "translate", "x"); set(gridOrigin.getX(), "maps", map.getId(), "hires", "translate", "x");
set(gridOrigin.getY(), "maps", mapId, "hires", "translate", "z"); set(gridOrigin.getY(), "maps", map.getId(), "hires", "translate", "z");
Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile); Vector2i pointSize = hiresTileSize.div(lowresPointsPerHiresTile);
Vector2i tileSize = pointSize.mul(lowresTileSize); Vector2i tileSize = pointSize.mul(lowresTileSize);
set(tileSize.getX(), "maps", mapId, "lowres", "tileSize", "x"); set(tileSize.getX(), "maps", map.getId(), "lowres", "tileSize", "x");
set(tileSize.getY(), "maps", mapId, "lowres", "tileSize", "z"); set(tileSize.getY(), "maps", map.getId(), "lowres", "tileSize", "z");
set(pointSize.getX(), "maps", mapId, "lowres", "scale", "x"); set(pointSize.getX(), "maps", map.getId(), "lowres", "scale", "x");
set(pointSize.getY(), "maps", mapId, "lowres", "scale", "z"); set(pointSize.getY(), "maps", map.getId(), "lowres", "scale", "z");
set(pointSize.getX() / 2, "maps", mapId, "lowres", "translate", "x"); set(pointSize.getX() / 2, "maps", map.getId(), "lowres", "translate", "x");
set(pointSize.getY() / 2, "maps", mapId, "lowres", "translate", "z"); set(pointSize.getY() / 2, "maps", map.getId(), "lowres", "translate", "z");
}
public void setFrom(World world, String mapId) { set(map.getWorld().getSpawnPoint().getX(), "maps", map.getId(), "startPos", "x");
set(world.getSpawnPoint().getX(), "maps", mapId, "startPos", "x"); set(map.getWorld().getSpawnPoint().getZ(), "maps", map.getId(), "startPos", "z");
set(world.getSpawnPoint().getZ(), "maps", mapId, "startPos", "z"); set(map.getWorld().getUUID().toString(), "maps", map.getId(), "world");
set(world.getUUID().toString(), "maps", mapId, "world");
} }
public void setFrom(MapConfig mapConfig, String mapId) { public void setFrom(MapConfig mapConfig) {
Vector2i startPos = mapConfig.getStartPos(); Vector2i startPos = mapConfig.getStartPos();
if (startPos != null) { if (startPos != null) {
set(startPos.getX(), "maps", mapId, "startPos", "x"); set(startPos.getX(), "maps", mapConfig.getId(), "startPos", "x");
set(startPos.getY(), "maps", mapId, "startPos", "z"); set(startPos.getY(), "maps", mapConfig.getId(), "startPos", "z");
} }
Vector3f skyColor = MathUtils.color3FromInt(mapConfig.getSkyColor()); Vector3f skyColor = MathUtils.color3FromInt(mapConfig.getSkyColor());
set(skyColor.getX(), "maps", mapId, "skyColor", "r"); set(skyColor.getX(), "maps", mapConfig.getId(), "skyColor", "r");
set(skyColor.getY(), "maps", mapId, "skyColor", "g"); set(skyColor.getY(), "maps", mapConfig.getId(), "skyColor", "g");
set(skyColor.getZ(), "maps", mapId, "skyColor", "b"); set(skyColor.getZ(), "maps", mapConfig.getId(), "skyColor", "b");
set(mapConfig.getAmbientLight(), "maps", mapId, "ambientLight"); set(mapConfig.getAmbientLight(), "maps", mapConfig.getId(), "ambientLight");
setName(mapConfig.getName(), mapId); setName(mapConfig.getName(), mapConfig.getId());
} }
public void setOrdinal(int ordinal, String mapId) { public void setOrdinal(int ordinal, String mapId) {

View File

@ -8,7 +8,11 @@ dependencies {
compile 'org.spongepowered:configurate-gson:3.7.1' compile 'org.spongepowered:configurate-gson:3.7.1'
compile 'com.github.Querz:NBT:4.0' compile 'com.github.Querz:NBT:4.0'
testCompile 'junit:junit:4.12' testImplementation 'org.junit.jupiter:junit-jupiter:5.4.2'
}
test {
useJUnitPlatform()
} }
processResources { processResources {

View File

@ -24,17 +24,17 @@
*/ */
package de.bluecolored.bluemap.core.config; package de.bluecolored.bluemap.core.config;
import java.io.IOException;
import java.util.regex.Pattern;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.map.MapSettings;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.util.ConfigUtils; import de.bluecolored.bluemap.core.util.ConfigUtils;
import ninja.leaping.configurate.ConfigurationNode; import ninja.leaping.configurate.ConfigurationNode;
public class MapConfig implements RenderSettings { import java.io.IOException;
import java.util.regex.Pattern;
public class MapConfig implements MapSettings {
private static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]+"); private static final Pattern VALID_ID_PATTERN = Pattern.compile("[a-zA-Z0-9_]+");
private String id; private String id;
@ -86,12 +86,12 @@ public MapConfig(ConfigurationNode node) throws IOException {
this.renderCaves = node.getNode("renderCaves").getBoolean(false); this.renderCaves = node.getNode("renderCaves").getBoolean(false);
//bounds //bounds
int minX = node.getNode("minX").getInt(RenderSettings.super.getMin().getX()); int minX = node.getNode("minX").getInt(MapSettings.super.getMin().getX());
int maxX = node.getNode("maxX").getInt(RenderSettings.super.getMax().getX()); int maxX = node.getNode("maxX").getInt(MapSettings.super.getMax().getX());
int minZ = node.getNode("minZ").getInt(RenderSettings.super.getMin().getZ()); int minZ = node.getNode("minZ").getInt(MapSettings.super.getMin().getZ());
int maxZ = node.getNode("maxZ").getInt(RenderSettings.super.getMax().getZ()); int maxZ = node.getNode("maxZ").getInt(MapSettings.super.getMax().getZ());
int minY = node.getNode("minY").getInt(RenderSettings.super.getMin().getY()); int minY = node.getNode("minY").getInt(MapSettings.super.getMin().getY());
int maxY = node.getNode("maxY").getInt(RenderSettings.super.getMax().getY()); int maxY = node.getNode("maxY").getInt(MapSettings.super.getMax().getY());
this.min = new Vector3i(minX, minY, minZ); this.min = new Vector3i(minX, minY, minZ);
this.max = new Vector3i(maxX, maxY, maxZ); this.max = new Vector3i(maxX, maxY, maxZ);
@ -146,15 +146,18 @@ public boolean isRenderCaves() {
public boolean isIgnoreMissingLightData() { public boolean isIgnoreMissingLightData() {
return ignoreMissingLightData; return ignoreMissingLightData;
} }
@Override
public int getHiresTileSize() { public int getHiresTileSize() {
return hiresTileSize; return hiresTileSize;
} }
@Override
public int getLowresPointsPerHiresTile() { public int getLowresPointsPerHiresTile() {
return lowresPointsPerHiresTile; return lowresPointsPerHiresTile;
} }
@Override
public int getLowresPointsPerLowresTile() { public int getLowresPointsPerLowresTile() {
return lowresPointsPerLowresTile; return lowresPointsPerLowresTile;
} }

View File

@ -0,0 +1,143 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.map;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.hires.HiresModel;
import de.bluecolored.bluemap.core.map.hires.HiresModelManager;
import de.bluecolored.bluemap.core.map.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.World;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Objects;
public class BmMap {
private final String id;
private final String name;
private final World world;
private final Path fileRoot;
private final MapRenderState renderState;
private final HiresModelManager hiresModelManager;
private final LowresModelManager lowresModelManager;
public BmMap(String id, String name, World world, Path fileRoot, ResourcePack resourcePack, MapSettings settings) throws IOException {
this.id = Objects.requireNonNull(id);
this.name = Objects.requireNonNull(name);
this.world = Objects.requireNonNull(world);
this.fileRoot = Objects.requireNonNull(fileRoot);
Objects.requireNonNull(resourcePack);
Objects.requireNonNull(settings);
this.renderState = new MapRenderState();
File rstateFile = getRenderStateFile();
if (rstateFile.exists()) {
this.renderState.load(rstateFile);
}
this.hiresModelManager = new HiresModelManager(
fileRoot.resolve("hires"),
resourcePack,
settings,
new Grid(settings.getHiresTileSize(), 2)
);
this.lowresModelManager = new LowresModelManager(
fileRoot.resolve("lowres"),
new Vector2i(settings.getLowresPointsPerLowresTile(), settings.getLowresPointsPerLowresTile()),
new Vector2i(settings.getLowresPointsPerHiresTile(), settings.getLowresPointsPerHiresTile()),
settings.useGzipCompression()
);
}
public void renderTile(Vector2i tile) {
HiresModel hiresModel = hiresModelManager.render(world, tile);
lowresModelManager.render(hiresModel);
}
public synchronized void save() {
lowresModelManager.save();
try {
this.renderState.save(getRenderStateFile());
} catch (IOException ex){
Logger.global.logError("Failed to save render-state for map: '" + this.id + "'!", ex);
}
}
public File getRenderStateFile() {
return fileRoot.resolve(".rstate").toFile();
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public World getWorld() {
return world;
}
public MapRenderState getRenderState() {
return renderState;
}
public HiresModelManager getHiresModelManager() {
return hiresModelManager;
}
public LowresModelManager getLowresModelManager() {
return lowresModelManager;
}
@Override
public int hashCode() {
return id.hashCode();
}
@Override
public boolean equals(Object obj) {
if (obj instanceof BmMap) {
BmMap that = (BmMap) obj;
return this.id.equals(that.id);
}
return false;
}
}

View File

@ -0,0 +1,76 @@
package de.bluecolored.bluemap.core.map;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.util.FileUtils;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class MapRenderState {
private final Map<Vector2i, Long> regionRenderTimes;
public MapRenderState() {
regionRenderTimes = new HashMap<>();
}
public synchronized void setRenderTime(Vector2i regionPos, long renderTime) {
regionRenderTimes.put(regionPos, renderTime);
}
public synchronized long getRenderTime(Vector2i regionPos) {
Long renderTime = regionRenderTimes.get(regionPos);
if (renderTime == null) return -1;
else return renderTime;
}
public synchronized void save(File file) throws IOException {
FileUtils.delete(file);
FileUtils.createFile(file);
try (
FileOutputStream fOut = new FileOutputStream(file);
GZIPOutputStream gOut = new GZIPOutputStream(fOut);
DataOutputStream dOut = new DataOutputStream(gOut)
) {
dOut.writeInt(regionRenderTimes.size());
for (Map.Entry<Vector2i, Long> entry : regionRenderTimes.entrySet()) {
Vector2i regionPos = entry.getKey();
long renderTime = entry.getValue();
dOut.writeInt(regionPos.getX());
dOut.writeInt(regionPos.getY());
dOut.writeLong(renderTime);
}
dOut.flush();
}
}
public synchronized void load(File file) throws IOException {
regionRenderTimes.clear();
try (
FileInputStream fIn = new FileInputStream(file);
GZIPInputStream gIn = new GZIPInputStream(fIn);
DataInputStream dIn = new DataInputStream(gIn)
) {
int size = dIn.readInt();
for (int i = 0; i < size; i++) {
Vector2i regionPos = new Vector2i(
dIn.readInt(),
dIn.readInt()
);
long renderTime = dIn.readLong();
regionRenderTimes.put(regionPos, renderTime);
}
}
}
}

View File

@ -0,0 +1,13 @@
package de.bluecolored.bluemap.core.map;
import de.bluecolored.bluemap.core.map.hires.RenderSettings;
public interface MapSettings extends RenderSettings {
int getHiresTileSize();
int getLowresPointsPerLowresTile();
int getLowresPointsPerHiresTile();
}

View File

@ -22,31 +22,27 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires; package de.bluecolored.bluemap.core.map.hires;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f; import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.ExtendedModel; import de.bluecolored.bluemap.core.model.ExtendedModel;
import java.util.UUID;
/** /**
* A model, containing additional information about the tile it represents * A model, containing additional information about the tile it represents
*/ */
public class HiresModel extends ExtendedModel { public class HiresModel extends ExtendedModel {
private UUID world; private UUID world;
private Vector2i tile;
private Vector3i blockMin, blockMax, blockSize; private Vector3i blockMin, blockMax, blockSize;
private int[][] heights; private int[][] heights;
private Vector4f[][] colors; private Vector4f[][] colors;
public HiresModel(UUID world, Vector2i tile, Vector3i blockMin, Vector3i blockMax) { public HiresModel(UUID world, Vector3i blockMin, Vector3i blockMax) {
this.world = world; this.world = world;
this.tile = tile;
this.blockMin = blockMin; this.blockMin = blockMin;
this.blockMax = blockMax; this.blockMax = blockMax;
this.blockSize = blockMax.sub(blockMin).add(Vector3i.ONE); this.blockSize = blockMax.sub(blockMin).add(Vector3i.ONE);
@ -89,8 +85,4 @@ public Vector3i getBlockSize(){
return blockSize; return blockSize;
} }
public Vector2i getTile(){
return tile;
}
} }

View File

@ -22,17 +22,16 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires; package de.bluecolored.bluemap.core.map.hires;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3d; import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.RenderSettings;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.FileUtils;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.World;
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -44,24 +43,20 @@
public class HiresModelManager { public class HiresModelManager {
private Path fileRoot; private final Path fileRoot;
private HiresModelRenderer renderer; private final HiresModelRenderer renderer;
private final Grid tileGrid;
private Vector2i tileSize; private final boolean useGzip;
private Vector2i gridOrigin;
public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Grid tileGrid) {
private boolean useGzip; this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileGrid, renderSettings.useGzipCompression());
public HiresModelManager(Path fileRoot, ResourcePack resourcePack, RenderSettings renderSettings, Vector2i tileSize) {
this(fileRoot, new HiresModelRenderer(resourcePack, renderSettings), tileSize, new Vector2i(2, 2), renderSettings.useGzipCompression());
} }
public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i tileSize, Vector2i gridOrigin, boolean useGzip) { public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Grid tileGrid, boolean useGzip) {
this.fileRoot = fileRoot; this.fileRoot = fileRoot;
this.renderer = renderer; this.renderer = renderer;
this.tileSize = tileSize; this.tileGrid = tileGrid;
this.gridOrigin = gridOrigin;
this.useGzip = useGzip; this.useGzip = useGzip;
} }
@ -69,19 +64,25 @@ public HiresModelManager(Path fileRoot, HiresModelRenderer renderer, Vector2i ti
/** /**
* Renders the given world tile with the provided render-settings * Renders the given world tile with the provided render-settings
*/ */
public HiresModel render(WorldTile tile) { public HiresModel render(World world, Vector2i tile) {
HiresModel model = renderer.render(tile, getTileRegion(tile)); Vector2i tileMin = tileGrid.getCellMin(tile);
save(model); Vector2i tileMax = tileGrid.getCellMax(tile);
Vector3i modelMin = new Vector3i(tileMin.getX(), world.getMinY(), tileMin.getY());
Vector3i modelMax = new Vector3i(tileMax.getX(), world.getMaxY(), tileMax.getY());
HiresModel model = renderer.render(world, modelMin, modelMax);
save(model, tile);
return model; return model;
} }
private void save(final HiresModel model) { private void save(final HiresModel model, Vector2i tile) {
final String modelJson = model.toBufferGeometry().toJson(); final String modelJson = model.toBufferGeometry().toJson();
save(model, modelJson); save(modelJson, tile);
} }
private void save(HiresModel model, String modelJson){ private void save(String modelJson, Vector2i tile){
File file = getFile(model.getTile(), useGzip); File file = getFile(tile, useGzip);
try { try {
FileUtils.createFile(file); FileUtils.createFile(file);
@ -104,14 +105,15 @@ private void save(HiresModel model, String modelJson){
/** /**
* Returns all tiles that the provided chunks are intersecting * Returns all tiles that the provided chunks are intersecting
*/ */
@Deprecated
public Collection<Vector2i> getTilesForChunks(Iterable<Vector2i> chunks){ public Collection<Vector2i> getTilesForChunks(Iterable<Vector2i> chunks){
Set<Vector2i> tiles = new HashSet<>(); Set<Vector2i> tiles = new HashSet<>();
for (Vector2i chunk : chunks) { for (Vector2i chunk : chunks) {
Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16); Vector3i minBlockPos = new Vector3i(chunk.getX() * 16, 0, chunk.getY() * 16);
//loop to cover the case that a tile is smaller then a chunk, should normally only add one tile (at 0, 0) //loop to cover the case that a tile is smaller then a chunk, should normally only add one tile (at 0, 0)
for (int x = 0; x < 15; x += getTileSize().getX()) { for (int x = 0; x < 15; x += tileGrid.getGridSize().getX()) {
for (int z = 0; z < 15; z += getTileSize().getY()) { for (int z = 0; z < 15; z += tileGrid.getGridSize().getY()) {
tiles.add(posToTile(minBlockPos.add(x, 0, z))); tiles.add(posToTile(minBlockPos.add(x, 0, z)));
} }
} }
@ -125,52 +127,24 @@ public Collection<Vector2i> getTilesForChunks(Iterable<Vector2i> chunks){
} }
/** /**
* Returns the region of blocks that a tile includes * Returns the tile-grid
*/ */
public AABB getTileRegion(WorldTile tile) { public Grid getTileGrid() {
Vector3i min = new Vector3i( return tileGrid;
tile.getTile().getX() * tileSize.getX() + gridOrigin.getX(),
0,
tile.getTile().getY() * tileSize.getY() + gridOrigin.getY()
);
Vector3i max = min.add(
tileSize.getX() - 1,
255,
tileSize.getY() - 1
);
return new AABB(min, max);
}
/**
* Returns the tile-size
*/
public Vector2i getTileSize() {
return tileSize;
}
/**
* Returns the grid-origin
*/
public Vector2i getGridOrigin() {
return gridOrigin;
} }
/** /**
* Converts a block-position to a map-tile-coordinate * Converts a block-position to a map-tile-coordinate
*/ */
public Vector2i posToTile(Vector3i pos){ public Vector2i posToTile(Vector3i pos){
return posToTile(pos.toDouble()); return tileGrid.getCell(pos.toVector2(true));
} }
/** /**
* Converts a block-position to a map-tile-coordinate * Converts a block-position to a map-tile-coordinate
*/ */
public Vector2i posToTile(Vector3d pos){ public Vector2i posToTile(Vector3d pos){
pos = pos.sub(new Vector3d(gridOrigin.getX(), 0.0, gridOrigin.getY())); return tileGrid.getCell(new Vector2i(pos.getFloorX(), pos.getFloorZ()));
return Vector2i.from(
(int) Math.floor(pos.getX() / getTileSize().getX()),
(int) Math.floor(pos.getZ() / getTileSize().getY())
);
} }
/** /**

View File

@ -22,19 +22,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires; package de.bluecolored.bluemap.core.map.hires;
import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3f;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import com.flowpowered.math.vector.Vector4f; import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModel;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.blockmodel.BlockStateModelFactory;
import de.bluecolored.bluemap.core.render.WorldTile;
import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModel;
import de.bluecolored.bluemap.core.render.hires.blockmodel.BlockStateModelFactory;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;
import de.bluecolored.bluemap.core.resourcepack.ResourcePack; import de.bluecolored.bluemap.core.resourcepack.ResourcePack;
import de.bluecolored.bluemap.core.util.AABB;
import de.bluecolored.bluemap.core.util.MathUtils; import de.bluecolored.bluemap.core.util.MathUtils;
import de.bluecolored.bluemap.core.world.Block; import de.bluecolored.bluemap.core.world.Block;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
@ -61,16 +57,11 @@ public HiresModelRenderer(ResourcePack resourcePack, RenderSettings renderSettin
} }
} }
public HiresModel render(WorldTile tile, AABB region) { public HiresModel render(World world, Vector3i modelMin, Vector3i modelMax) {
Vector3i modelMin = region.getMin();
Vector3i modelMax = region.getMax();
Vector3i min = modelMin.max(renderSettings.getMin()); Vector3i min = modelMin.max(renderSettings.getMin());
Vector3i max = modelMax.min(renderSettings.getMax()); Vector3i max = modelMax.min(renderSettings.getMax());
World world = tile.getWorld(); HiresModel model = new HiresModel(world.getUUID(), modelMin, modelMax);
HiresModel model = new HiresModel(tile.getWorld().getUUID(), tile.getTile(), modelMin, modelMax);
for (int x = min.getX(); x <= max.getX(); x++){ for (int x = min.getX(); x <= max.getX(); x++){
for (int z = min.getZ(); z <= max.getZ(); z++){ for (int z = min.getZ(); z <= max.getZ(); z++){
@ -104,7 +95,7 @@ public HiresModel render(WorldTile tile, AABB region) {
color = MathUtils.overlayColors(blockModel.getMapColor(), color); color = MathUtils.overlayColors(blockModel.getMapColor(), color);
} }
//TODO: quick hack to random offset grass //quick hack to random offset grass
if (block.getBlockState().getFullId().equals(grassId)){ if (block.getBlockState().getFullId().equals(grassId)){
float dx = (MathUtils.hashToFloat(x, y, z, 123984) - 0.5f) * 0.75f; float dx = (MathUtils.hashToFloat(x, y, z, 123984) - 0.5f) * 0.75f;
float dz = (MathUtils.hashToFloat(x, y, z, 345542) - 0.5f) * 0.75f; float dz = (MathUtils.hashToFloat(x, y, z, 345542) - 0.5f) * 0.75f;

View File

@ -22,14 +22,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render; package de.bluecolored.bluemap.core.map.hires;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
public interface RenderSettings { public interface RenderSettings {
static final Vector3i DEFAULT_MIN = Vector3i.from(Integer.MIN_VALUE); Vector3i DEFAULT_MIN = Vector3i.from(Integer.MIN_VALUE);
static final Vector3i DEFAULT_MAX = Vector3i.from(Integer.MAX_VALUE); Vector3i DEFAULT_MAX = Vector3i.from(Integer.MAX_VALUE);
/** /**
* Whether faces that have a sky-light-value of 0 will be rendered or not. * Whether faces that have a sky-light-value of 0 will be rendered or not.
@ -68,13 +68,4 @@ default boolean useGzipCompression() {
return true; return true;
} }
default RenderSettings copy() {
return new StaticRenderSettings(
isExcludeFacesWithoutSunlight(),
getMin(),
getMax(),
isRenderEdges()
);
}
} }

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires.blockmodel; package de.bluecolored.bluemap.core.map.hires.blockmodel;
import com.flowpowered.math.vector.Vector4f; import com.flowpowered.math.vector.Vector4f;

View File

@ -22,9 +22,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires.blockmodel; package de.bluecolored.bluemap.core.map.hires.blockmodel;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator;
import de.bluecolored.bluemap.core.resourcepack.BlockStateResource; import de.bluecolored.bluemap.core.resourcepack.BlockStateResource;
import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException; import de.bluecolored.bluemap.core.resourcepack.NoSuchResourceException;

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires.blockmodel; package de.bluecolored.bluemap.core.map.hires.blockmodel;
import java.util.HashSet; import java.util.HashSet;
@ -35,7 +35,7 @@
import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.model.ExtendedFace; import de.bluecolored.bluemap.core.model.ExtendedFace;
import de.bluecolored.bluemap.core.model.ExtendedModel; import de.bluecolored.bluemap.core.model.ExtendedModel;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator;
import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource;
import de.bluecolored.bluemap.core.resourcepack.Texture; import de.bluecolored.bluemap.core.resourcepack.Texture;

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.hires.blockmodel; package de.bluecolored.bluemap.core.map.hires.blockmodel;
import com.flowpowered.math.TrigMath; import com.flowpowered.math.TrigMath;
import com.flowpowered.math.imaginary.Complexf; import com.flowpowered.math.imaginary.Complexf;
@ -34,7 +34,7 @@
import com.flowpowered.math.vector.Vector4f; import com.flowpowered.math.vector.Vector4f;
import de.bluecolored.bluemap.core.model.ExtendedFace; import de.bluecolored.bluemap.core.model.ExtendedFace;
import de.bluecolored.bluemap.core.render.RenderSettings; import de.bluecolored.bluemap.core.map.hires.RenderSettings;
import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator; import de.bluecolored.bluemap.core.resourcepack.BlockColorCalculator;
import de.bluecolored.bluemap.core.resourcepack.BlockModelResource; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource;
import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Rotation; import de.bluecolored.bluemap.core.resourcepack.BlockModelResource.Element.Rotation;
@ -171,7 +171,7 @@ private void createElementFace(BlockStateModel model, TransformedBlockModelResou
Quaternionf rot = Quaternionf.fromAxesAnglesDeg(rotation.getX(), rotation.getY(), 0); Quaternionf rot = Quaternionf.fromAxesAnglesDeg(rotation.getX(), rotation.getY(), 0);
uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat()); uvLockAngle = (int) rot.getAxesAnglesDeg().dot(faceDir.toVector().toFloat());
//TODO: my math has stopped working, there has to be a more consistent solution //my math has stopped working, there has to be a more consistent solution for this...
if (rotation.getX() >= 180 && rotation.getY() != 90 && rotation.getY() != 270) uvLockAngle += 180; if (rotation.getX() >= 180 && rotation.getY() != 90 && rotation.getY() != 270) uvLockAngle += 180;
} }

View File

@ -22,7 +22,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.lowres; package de.bluecolored.bluemap.core.map.lowres;
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3f; import com.flowpowered.math.vector.Vector3f;
@ -33,21 +33,15 @@
import java.io.*; import java.io.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
public class LowresModel { public class LowresModel {
private UUID world; private final BufferGeometry model;
private Vector2i tilePos;
private BufferGeometry model;
private Map<Vector2i, LowresPoint> changes; private Map<Vector2i, LowresPoint> changes;
private boolean hasUnsavedChanges; private boolean hasUnsavedChanges;
@ -56,17 +50,13 @@ public class LowresModel {
fileLock = new Object(), fileLock = new Object(),
modelLock = new Object(); modelLock = new Object();
public LowresModel(UUID world, Vector2i tilePos, Vector2i gridSize) { public LowresModel(Vector2i gridSize) {
this( this(
world,
tilePos,
ModelUtils.makeGrid(gridSize).toBufferGeometry() ModelUtils.makeGrid(gridSize).toBufferGeometry()
); );
} }
public LowresModel(UUID world, Vector2i tilePos, BufferGeometry model) { public LowresModel(BufferGeometry model) {
this.world = world;
this.tilePos = tilePos;
this.model = model; this.model = model;
this.changes = new ConcurrentHashMap<>(); this.changes = new ConcurrentHashMap<>();
@ -134,7 +124,7 @@ public void flush(){
if (changes.isEmpty()) return; if (changes.isEmpty()) return;
Map<Vector2i, LowresPoint> points = changes; Map<Vector2i, LowresPoint> points = changes;
changes = new HashMap<>(); changes = new ConcurrentHashMap<>();
float[] position = model.attributes.get("position").values(); float[] position = model.attributes.get("position").values();
float[] color = model.attributes.get("color").values(); float[] color = model.attributes.get("color").values();
@ -178,36 +168,12 @@ public BufferGeometry getBufferGeometry(){
return model; return model;
} }
public UUID getWorld(){
return world;
}
public Vector2i getTile(){
return tilePos;
}
@Override
public int hashCode() {
return Objects.hash(world, tilePos);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof LowresModel){
LowresModel other = (LowresModel) obj;
if (!other.world.equals(world)) return false;
if (other.tilePos.equals(tilePos)) return true;
}
return false;
}
/** /**
* a point on this lowres-model-grid * a point on this lowres-model-grid
*/ */
public class LowresPoint { public static class LowresPoint {
private float height; private final float height;
private Vector3f color; private final Vector3f color;
public LowresPoint(float height, Vector3f color) { public LowresPoint(float height, Vector3f color) {
this.height = height; this.height = height;

View File

@ -22,11 +22,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE. * THE SOFTWARE.
*/ */
package de.bluecolored.bluemap.core.render.lowres; package de.bluecolored.bluemap.core.map.lowres;
import com.flowpowered.math.vector.*; import com.flowpowered.math.vector.*;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.render.hires.HiresModel; import de.bluecolored.bluemap.core.map.hires.HiresModel;
import de.bluecolored.bluemap.core.threejs.BufferGeometry; import de.bluecolored.bluemap.core.threejs.BufferGeometry;
import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.FileUtils;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
@ -47,19 +47,17 @@
public class LowresModelManager { public class LowresModelManager {
private Path fileRoot; private final Path fileRoot;
private final Vector2i pointsPerLowresTile;
private Vector2i gridSize; private final Vector2i pointsPerHiresTile;
private Vector2i pointsPerHiresTile; private final boolean useGzip;
private Map<File, CachedModel> models; private final Map<File, CachedModel> models;
private boolean useGzip;
public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHiresTile, boolean useGzip) { public LowresModelManager(Path fileRoot, Vector2i pointsPerLowresTile, Vector2i pointsPerHiresTile, boolean useGzip) {
this.fileRoot = fileRoot; this.fileRoot = fileRoot;
this.gridSize = gridSize; this.pointsPerLowresTile = pointsPerLowresTile;
this.pointsPerHiresTile = pointsPerHiresTile; this.pointsPerHiresTile = pointsPerHiresTile;
models = new ConcurrentHashMap<>(); models = new ConcurrentHashMap<>();
@ -68,7 +66,7 @@ public LowresModelManager(Path fileRoot, Vector2i gridSize, Vector2i pointsPerHi
} }
/** /**
* Renders all points from the given highres-model onto the lowres-grid * Renders all points from the given hires-model onto the lowres-grid
*/ */
public void render(HiresModel hiresModel) { public void render(HiresModel hiresModel) {
Vector3i min = hiresModel.getBlockMin(); Vector3i min = hiresModel.getBlockMin();
@ -124,15 +122,15 @@ public void render(HiresModel hiresModel) {
* Saves all unsaved changes to the models to disk * Saves all unsaved changes to the models to disk
*/ */
public synchronized void save(){ public synchronized void save(){
for (CachedModel model : models.values()){ for (Entry<File, CachedModel> entry : models.entrySet()){
saveModel(model); saveModel(entry.getKey(), entry.getValue());
} }
tidyUpModelCache(); tidyUpModelCache();
} }
/** /**
* Updates a point on the lowresmodel-grid * Updates a point on the lowres-model-grid
*/ */
public void update(UUID world, Vector2i point, float height, Vector3f color) { public void update(UUID world, Vector2i point, float height, Vector3f color) {
Vector2i tile = pointToTile(point); Vector2i tile = pointToTile(point);
@ -186,7 +184,7 @@ private LowresModel getModel(UUID world, Vector2i tile) {
String json = IOUtils.toString(is, StandardCharsets.UTF_8); String json = IOUtils.toString(is, StandardCharsets.UTF_8);
model = new CachedModel(world, tile, BufferGeometry.fromJson(json)); model = new CachedModel(BufferGeometry.fromJson(json));
} catch (IllegalArgumentException | IOException ex){ } catch (IllegalArgumentException | IOException ex){
Logger.global.logError("Failed to load lowres model: " + modelFile, ex); Logger.global.logError("Failed to load lowres model: " + modelFile, ex);
@ -199,7 +197,7 @@ private LowresModel getModel(UUID world, Vector2i tile) {
} }
if (model == null){ if (model == null){
model = new CachedModel(world, tile, gridSize); model = new CachedModel(pointsPerLowresTile);
} }
models.put(modelFile, model); models.put(modelFile, model);
@ -227,18 +225,17 @@ public synchronized void tidyUpModelCache() {
int size = entries.size(); int size = entries.size();
for (Entry<File, CachedModel> e : entries) { for (Entry<File, CachedModel> e : entries) {
if (size > 10) { if (size > 10) {
saveAndRemoveModel(e.getValue()); saveAndRemoveModel(e.getKey(), e.getValue());
continue; continue;
} }
if (e.getValue().getCacheTime() > 120000) { if (e.getValue().getCacheTime() > 120000) {
saveModel(e.getValue()); saveModel(e.getKey(), e.getValue());
} }
} }
} }
private synchronized void saveAndRemoveModel(CachedModel model) { private synchronized void saveAndRemoveModel(File modelFile, CachedModel model) {
File modelFile = getFile(model.getTile(), useGzip);
models.remove(modelFile); models.remove(modelFile);
try { try {
model.save(modelFile, false, useGzip); model.save(modelFile, false, useGzip);
@ -248,8 +245,7 @@ private synchronized void saveAndRemoveModel(CachedModel model) {
} }
} }
private void saveModel(CachedModel model) { private void saveModel(File modelFile, CachedModel model) {
File modelFile = getFile(model.getTile(), useGzip);
try { try {
model.save(modelFile, false, useGzip); model.save(modelFile, false, useGzip);
//logger.logDebug("Saved lowres tile: " + model.getTile()); //logger.logDebug("Saved lowres tile: " + model.getTile());
@ -263,35 +259,35 @@ private void saveModel(CachedModel model) {
private Vector2i pointToTile(Vector2i point){ private Vector2i pointToTile(Vector2i point){
return point return point
.toDouble() .toDouble()
.div(gridSize.toDouble()) .div(pointsPerLowresTile.toDouble())
.floor() .floor()
.toInt(); .toInt();
} }
private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){ private Vector2i getPointRelativeToTile(Vector2i tile, Vector2i point){
return point.sub(tile.mul(gridSize)); return point.sub(tile.mul(pointsPerLowresTile));
} }
public Vector2i getTileSize() { public Vector2i getTileSize() {
return gridSize; return pointsPerLowresTile;
} }
public Vector2i getPointsPerHiresTile() { public Vector2i getPointsPerHiresTile() {
return pointsPerHiresTile; return pointsPerHiresTile;
} }
private class CachedModel extends LowresModel { private static class CachedModel extends LowresModel {
private long cacheTime; private long cacheTime;
public CachedModel(UUID world, Vector2i tilePos, BufferGeometry model) { public CachedModel(BufferGeometry model) {
super(world, tilePos, model); super(model);
cacheTime = System.currentTimeMillis(); cacheTime = System.currentTimeMillis();
} }
public CachedModel(UUID world, Vector2i tilePos, Vector2i gridSize) { public CachedModel(Vector2i gridSize) {
super(world, tilePos, gridSize); super(gridSize);
cacheTime = System.currentTimeMillis(); cacheTime = System.currentTimeMillis();
} }

View File

@ -24,10 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper; import de.bluecolored.bluemap.core.mca.mapping.BlockIdMapper;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
@ -38,9 +35,13 @@
import net.querz.nbt.NumberTag; import net.querz.nbt.NumberTag;
import net.querz.nbt.mca.MCAUtil; import net.querz.nbt.mca.MCAUtil;
public class ChunkAnvil112 extends Chunk { import java.util.Arrays;
private BlockIdMapper blockIdMapper; import java.util.function.IntFunction;
private BiomeMapper biomeIdMapper;
public class ChunkAnvil112 extends MCAChunk {
private final BiomeMapper biomeIdMapper;
private final BlockIdMapper blockIdMapper;
private final IntFunction<String> forgeBlockIdMapper;
private boolean isGenerated; private boolean isGenerated;
private boolean hasLight; private boolean hasLight;
@ -48,11 +49,12 @@ public class ChunkAnvil112 extends Chunk {
private byte[] biomes; private byte[] biomes;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ChunkAnvil112(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { public ChunkAnvil112(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper, BlockIdMapper blockIdMapper, IntFunction<String> forgeBlockIdMapper) {
super(world, chunkTag); super(chunkTag);
blockIdMapper = getWorld().getBlockIdMapper(); this.blockIdMapper = blockIdMapper;
biomeIdMapper = getWorld().getBiomeIdMapper(); this.biomeIdMapper = biomeIdMapper;
this.forgeBlockIdMapper = forgeBlockIdMapper;
CompoundTag levelData = chunkTag.getCompoundTag("Level"); CompoundTag levelData = chunkTag.getCompoundTag("Level");
@ -118,9 +120,9 @@ public LightData getLightData(Vector3i pos) {
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) x = x & 0xF; // Math.floorMod(pos.getX(), 16)
int z = pos.getZ() & 0xF; z = z & 0xF;
int biomeByteIndex = z * 16 + x; int biomeByteIndex = z * 16 + x;
return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF); return biomeIdMapper.get(biomes[biomeByteIndex] & 0xFF);
@ -168,7 +170,7 @@ public BlockState getBlockState(Vector3i pos) {
int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf); int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf);
String forgeIdMapping = getWorld().getForgeBlockIdMapping(blockId); String forgeIdMapping = forgeBlockIdMapper.apply(blockId);
if (forgeIdMapping != null) { if (forgeIdMapping != null) {
return blockIdMapper.get(forgeIdMapping, blockId, blockData); return blockIdMapper.get(forgeIdMapping, blockId, blockData);
} else { } else {
@ -191,7 +193,7 @@ public String getBlockIdMeta(Vector3i pos) {
} }
int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf); int blockData = getByteHalf(this.data[blockHalfByteIndex], largeHalf);
String forgeIdMapping = getWorld().getForgeBlockIdMapping(blockId); String forgeIdMapping = forgeBlockIdMapper.apply(blockId);
return blockId + ":" + blockData + " " + forgeIdMapping; return blockId + ":" + blockData + " " + forgeIdMapping;
} }

View File

@ -24,13 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
@ -39,7 +33,12 @@
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil; import net.querz.nbt.mca.MCAUtil;
public class ChunkAnvil113 extends Chunk { import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class ChunkAnvil113 extends MCAChunk {
private BiomeMapper biomeIdMapper; private BiomeMapper biomeIdMapper;
private boolean isGenerated; private boolean isGenerated;
@ -48,16 +47,16 @@ public class ChunkAnvil113 extends Chunk {
private int[] biomes; private int[] biomes;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ChunkAnvil113(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { public ChunkAnvil113(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) {
super(world, chunkTag); super(chunkTag);
biomeIdMapper = getWorld().getBiomeIdMapper(); this.biomeIdMapper = biomeIdMapper;
CompoundTag levelData = chunkTag.getCompoundTag("Level"); CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status"); String status = levelData.getString("Status");
isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world this.isGenerated = status.equals("full");
hasLight = isGenerated; this.hasLight = isGenerated;
if (!isGenerated && ignoreMissingLightData) { if (!isGenerated && ignoreMissingLightData) {
isGenerated = !status.equals("empty"); isGenerated = !status.equals("empty");
@ -121,9 +120,9 @@ public LightData getLightData(Vector3i pos) {
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
int x = pos.getX() & 0xF; // Math.floorMod(pos.getX(), 16) x = x & 0xF; // Math.floorMod(pos.getX(), 16)
int z = pos.getZ() & 0xF; z = z & 0xF;
int biomeIntIndex = z * 16 + x; int biomeIntIndex = z * 16 + x;
return biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(biomes[biomeIntIndex]);

View File

@ -24,13 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
@ -39,7 +33,12 @@
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil; import net.querz.nbt.mca.MCAUtil;
public class ChunkAnvil115 extends Chunk { import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class ChunkAnvil115 extends MCAChunk {
private BiomeMapper biomeIdMapper; private BiomeMapper biomeIdMapper;
private boolean isGenerated; private boolean isGenerated;
@ -48,21 +47,21 @@ public class ChunkAnvil115 extends Chunk {
private int[] biomes; private int[] biomes;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { public ChunkAnvil115(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) {
super(world, chunkTag); super(chunkTag);
biomeIdMapper = getWorld().getBiomeIdMapper(); this.biomeIdMapper = biomeIdMapper;
CompoundTag levelData = chunkTag.getCompoundTag("Level"); CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status"); String status = levelData.getString("Status");
isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world this.isGenerated = status.equals("full");
hasLight = isGenerated; this.hasLight = isGenerated;
if (!isGenerated && ignoreMissingLightData) { if (!isGenerated && ignoreMissingLightData) {
isGenerated = !status.equals("empty"); isGenerated = !status.equals("empty");
} }
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe? sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
@ -81,7 +80,7 @@ public ChunkAnvil115(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
} }
} }
else if (tag instanceof IntArrayTag) { else if (tag instanceof IntArrayTag) {
biomes = ((IntArrayTag) tag).getValue(); biomes = ((IntArrayTag) tag).getValue();
} }
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
@ -121,16 +120,16 @@ public LightData getLightData(Vector3i pos) {
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16) x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
int z = (pos.getZ() & 0xF) / 4; z = (z & 0xF) / 4;
int y = pos.getY() / 4; y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x; int biomeIntIndex = y * 16 + z * 4 + x;
return biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(biomes[biomeIntIndex]);
} }
private class Section { private static class Section {
private static final String AIR_ID = "minecraft:air"; private static final String AIR_ID = "minecraft:air";
private int sectionY; private int sectionY;

View File

@ -24,13 +24,7 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper; import de.bluecolored.bluemap.core.mca.mapping.BiomeMapper;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
@ -39,7 +33,12 @@
import net.querz.nbt.*; import net.querz.nbt.*;
import net.querz.nbt.mca.MCAUtil; import net.querz.nbt.mca.MCAUtil;
public class ChunkAnvil116 extends Chunk { import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
public class ChunkAnvil116 extends MCAChunk {
private BiomeMapper biomeIdMapper; private BiomeMapper biomeIdMapper;
private boolean isGenerated; private boolean isGenerated;
@ -48,22 +47,22 @@ public class ChunkAnvil116 extends Chunk {
private int[] biomes; private int[] biomes;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) { public ChunkAnvil116(CompoundTag chunkTag, boolean ignoreMissingLightData, BiomeMapper biomeIdMapper) {
super(world, chunkTag); super(chunkTag);
biomeIdMapper = getWorld().getBiomeIdMapper(); this.biomeIdMapper = biomeIdMapper;
CompoundTag levelData = chunkTag.getCompoundTag("Level"); CompoundTag levelData = chunkTag.getCompoundTag("Level");
String status = levelData.getString("Status"); String status = levelData.getString("Status");
isGenerated = status.equals("full") || status.equals("spawn"); // full is normal fully generated and spawn seems to be converted from old format but not yet loaded if you optimized your world this.isGenerated = status.equals("full");
hasLight = isGenerated; this.hasLight = isGenerated;
if (!isGenerated && ignoreMissingLightData) { if (!isGenerated && ignoreMissingLightData) {
isGenerated = !status.equals("empty"); isGenerated = !status.equals("empty");
} }
sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe? this.sections = new Section[32]; //32 supports a max world-height of 512 which is the max that the hightmaps of Minecraft V1.13+ can store with 9 bits, i believe?
if (levelData.containsKey("Sections")) { if (levelData.containsKey("Sections")) {
for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) { for (CompoundTag sectionTag : ((ListTag<CompoundTag>) levelData.getListTag("Sections"))) {
Section section = new Section(sectionTag); Section section = new Section(sectionTag);
@ -74,22 +73,22 @@ public ChunkAnvil116(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissing
Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array Tag<?> tag = levelData.get("Biomes"); //tag can be byte-array or int-array
if (tag instanceof ByteArrayTag) { if (tag instanceof ByteArrayTag) {
byte[] bs = ((ByteArrayTag) tag).getValue(); byte[] bs = ((ByteArrayTag) tag).getValue();
biomes = new int[bs.length]; this.biomes = new int[bs.length];
for (int i = 0; i < bs.length; i++) { for (int i = 0; i < bs.length; i++) {
biomes[i] = bs[i] & 0xFF; biomes[i] = bs[i] & 0xFF;
} }
} }
else if (tag instanceof IntArrayTag) { else if (tag instanceof IntArrayTag) {
biomes = ((IntArrayTag) tag).getValue(); this.biomes = ((IntArrayTag) tag).getValue();
} }
if (biomes == null || biomes.length == 0) { if (biomes == null || biomes.length == 0) {
biomes = new int[1024]; this.biomes = new int[1024];
} }
if (biomes.length < 1024) { if (biomes.length < 1024) {
biomes = Arrays.copyOf(biomes, 1024); this.biomes = Arrays.copyOf(biomes, 1024);
} }
} }
@ -121,16 +120,16 @@ public LightData getLightData(Vector3i pos) {
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
int x = (pos.getX() & 0xF) / 4; // Math.floorMod(pos.getX(), 16) x = (x & 0xF) / 4; // Math.floorMod(pos.getX(), 16)
int z = (pos.getZ() & 0xF) / 4; z = (z & 0xF) / 4;
int y = pos.getY() / 4; y = y / 4;
int biomeIntIndex = y * 16 + z * 4 + x; int biomeIntIndex = y * 16 + z * 4 + x;
return biomeIdMapper.get(biomes[biomeIntIndex]); return biomeIdMapper.get(biomes[biomeIntIndex]);
} }
private class Section { private static class Section {
private static final String AIR_ID = "minecraft:air"; private static final String AIR_ID = "minecraft:air";
private int sectionY; private int sectionY;

View File

@ -26,16 +26,13 @@
import com.flowpowered.math.vector.Vector2i; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.LightData;
public class EmptyChunk extends Chunk { public class EmptyChunk extends MCAChunk {
protected EmptyChunk(MCAWorld world, Vector2i chunkPos) { public static final MCAChunk INSTANCE = new EmptyChunk();
super(world, chunkPos);
}
@Override @Override
public boolean isGenerated() { public boolean isGenerated() {
@ -53,7 +50,7 @@ public LightData getLightData(Vector3i pos) {
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
return Biome.DEFAULT; return Biome.DEFAULT;
} }

View File

@ -24,53 +24,30 @@
*/ */
package de.bluecolored.bluemap.core.mca; package de.bluecolored.bluemap.core.mca;
import java.io.IOException;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.world.Biome; import de.bluecolored.bluemap.core.world.Biome;
import de.bluecolored.bluemap.core.world.BlockState; import de.bluecolored.bluemap.core.world.BlockState;
import de.bluecolored.bluemap.core.world.Chunk;
import de.bluecolored.bluemap.core.world.LightData; import de.bluecolored.bluemap.core.world.LightData;
import net.querz.nbt.CompoundTag; import net.querz.nbt.CompoundTag;
public abstract class Chunk { import java.io.IOException;
private final MCAWorld world; public abstract class MCAChunk implements Chunk {
private final Vector2i chunkPos;
private final int dataVersion; private final int dataVersion;
protected Chunk(MCAWorld world, Vector2i chunkPos) { protected MCAChunk() {
this.world = world;
this.chunkPos = chunkPos;
this.dataVersion = -1; this.dataVersion = -1;
} }
protected Chunk(MCAWorld world, CompoundTag chunkTag) { protected MCAChunk(CompoundTag chunkTag) {
this.world = world;
CompoundTag levelData = chunkTag.getCompoundTag("Level");
chunkPos = new Vector2i(
levelData.getInt("xPos"),
levelData.getInt("zPos")
);
dataVersion = chunkTag.getInt("DataVersion"); dataVersion = chunkTag.getInt("DataVersion");
} }
@Override
public abstract boolean isGenerated(); public abstract boolean isGenerated();
public Vector2i getChunkPos() {
return chunkPos;
}
public MCAWorld getWorld() {
return world;
}
public int getDataVersion() { public int getDataVersion() {
return dataVersion; return dataVersion;
} }
@ -79,19 +56,19 @@ public int getDataVersion() {
public abstract LightData getLightData(Vector3i pos); public abstract LightData getLightData(Vector3i pos);
public abstract Biome getBiome(Vector3i pos); public abstract Biome getBiome(int x, int y, int z);
public static Chunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException { public static MCAChunk create(MCAWorld world, CompoundTag chunkTag, boolean ignoreMissingLightData) throws IOException {
int version = chunkTag.getInt("DataVersion"); int version = chunkTag.getInt("DataVersion");
if (version < 1400) return new ChunkAnvil112(world, chunkTag, ignoreMissingLightData); if (version < 1400) return new ChunkAnvil112(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper(), world.getBlockIdMapper(), world::getForgeBlockIdMapping);
if (version < 2200) return new ChunkAnvil113(world, chunkTag, ignoreMissingLightData); if (version < 2200) return new ChunkAnvil113(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper());
if (version < 2500) return new ChunkAnvil115(world, chunkTag, ignoreMissingLightData); if (version < 2500) return new ChunkAnvil115(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper());
return new ChunkAnvil116(world, chunkTag, ignoreMissingLightData); return new ChunkAnvil116(chunkTag, ignoreMissingLightData, world.getBiomeIdMapper());
} }
public static Chunk empty(MCAWorld world, Vector2i chunkPos) { public static MCAChunk empty() {
return new EmptyChunk(world, chunkPos); return EmptyChunk.INSTANCE;
} }
} }

View File

@ -0,0 +1,108 @@
package de.bluecolored.bluemap.core.mca;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.world.Region;
import net.querz.nbt.CompoundTag;
import net.querz.nbt.Tag;
import net.querz.nbt.mca.CompressionType;
import java.io.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class MCARegion implements Region {
private final MCAWorld world;
private final File regionFile;
private final Vector2i regionPos;
public MCARegion(MCAWorld world, File regionFile) throws IllegalArgumentException {
this.world = world;
this.regionFile = regionFile;
String[] filenameParts = regionFile.getName().split("\\.");
int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]);
this.regionPos = new Vector2i(rX, rZ);
}
@Override
public MCAChunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException {
try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) {
int xzChunk = Math.floorMod(chunkZ, 32) * 32 + Math.floorMod(chunkX, 32);
raf.seek(xzChunk * 4);
int offset = raf.read() << 16;
offset |= (raf.read() & 0xFF) << 8;
offset |= raf.read() & 0xFF;
offset *= 4096;
int size = raf.readByte() * 4096;
if (size == 0) {
return MCAChunk.empty();
}
raf.seek(offset + 4); // +4 skip chunk size
byte compressionTypeByte = raf.readByte();
CompressionType compressionType = CompressionType.getFromID(compressionTypeByte);
if (compressionType == null) {
throw new IOException("Invalid compression type " + compressionTypeByte);
}
DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))));
Tag<?> tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH);
if (tag instanceof CompoundTag) {
return MCAChunk.create(world, (CompoundTag) tag, ignoreMissingLightData);
} else {
throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
}
} catch (RuntimeException e) {
throw new IOException(e);
}
}
@Override
public Collection<Vector2i> listChunks(long modifiedSince) {
List<Vector2i> chunks = new ArrayList<>(1024); //1024 = 32 x 32 chunks per region-file
try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) {
for (int x = 0; x < 32; x++) {
for (int z = 0; z < 32; z++) {
Vector2i chunk = new Vector2i(regionPos.getX() * 32 + x, regionPos.getY() * 32 + z);
int xzChunk = z * 32 + x;
raf.seek(xzChunk * 4 + 3);
int size = raf.readByte() * 4096;
if (size == 0) continue;
raf.seek(xzChunk * 4 + 4096);
int timestamp = raf.read() << 24;
timestamp |= (raf.read() & 0xFF) << 16;
timestamp |= (raf.read() & 0xFF) << 8;
timestamp |= raf.read() & 0xFF;
if (timestamp >= (modifiedSince / 1000)) {
chunks.add(chunk);
}
}
}
} catch (RuntimeException | IOException ex) {
Logger.global.logWarning("Failed to read .mca file: " + regionFile.getAbsolutePath() + " (" + ex.toString() + ")");
}
return chunks;
}
@Override
public File getRegionFile() {
return regionFile;
}
}

View File

@ -42,26 +42,28 @@
import net.querz.nbt.ListTag; import net.querz.nbt.ListTag;
import net.querz.nbt.NBTUtil; import net.querz.nbt.NBTUtil;
import net.querz.nbt.Tag; import net.querz.nbt.Tag;
import net.querz.nbt.mca.CompressionType;
import net.querz.nbt.mca.MCAUtil;
import java.io.*; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
public class MCAWorld implements World { public class MCAWorld implements World {
private static final Grid CHUNK_GRID = new Grid(16);
private static final Grid REGION_GRID = new Grid(32).multiply(CHUNK_GRID);
private final UUID uuid; private final UUID uuid;
private final Path worldFolder; private final Path worldFolder;
private final MinecraftVersion minecraftVersion; private final MinecraftVersion minecraftVersion;
private String name; private String name;
private int seaLevel;
private Vector3i spawnPoint; private Vector3i spawnPoint;
private final LoadingCache<Vector2i, Chunk> chunkCache; private final LoadingCache<Vector2i, MCARegion> regionCache;
private final LoadingCache<Vector2i, MCAChunk> chunkCache;
private BlockIdMapper blockIdMapper; private BlockIdMapper blockIdMapper;
private BlockPropertiesMapper blockPropertiesMapper; private BlockPropertiesMapper blockPropertiesMapper;
private BiomeMapper biomeMapper; private BiomeMapper biomeMapper;
@ -70,15 +72,13 @@ public class MCAWorld implements World {
private boolean ignoreMissingLightData; private boolean ignoreMissingLightData;
private Map<Integer, String> forgeBlockMappings; private final Map<Integer, String> forgeBlockMappings;
private MCAWorld( private MCAWorld(
Path worldFolder, Path worldFolder,
UUID uuid, UUID uuid,
MinecraftVersion minecraftVersion, MinecraftVersion minecraftVersion,
String name, String name,
int worldHeight,
int seaLevel,
Vector3i spawnPoint, Vector3i spawnPoint,
BlockIdMapper blockIdMapper, BlockIdMapper blockIdMapper,
BlockPropertiesMapper blockPropertiesMapper, BlockPropertiesMapper blockPropertiesMapper,
@ -89,7 +89,6 @@ private MCAWorld(
this.worldFolder = worldFolder; this.worldFolder = worldFolder;
this.minecraftVersion = minecraftVersion; this.minecraftVersion = minecraftVersion;
this.name = name; this.name = name;
this.seaLevel = seaLevel;
this.spawnPoint = spawnPoint; this.spawnPoint = spawnPoint;
this.blockIdMapper = blockIdMapper; this.blockIdMapper = blockIdMapper;
@ -114,30 +113,33 @@ private MCAWorld(
registerBlockStateExtension(new DoublePlantExtension(minecraftVersion)); registerBlockStateExtension(new DoublePlantExtension(minecraftVersion));
registerBlockStateExtension(new DoubleChestExtension()); registerBlockStateExtension(new DoubleChestExtension());
this.regionCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL)
.maximumSize(100)
.expireAfterWrite(1, TimeUnit.MINUTES)
.build(this::loadRegion);
this.chunkCache = Caffeine.newBuilder() this.chunkCache = Caffeine.newBuilder()
.executor(BlueMap.THREAD_POOL) .executor(BlueMap.THREAD_POOL)
.maximumSize(500) .maximumSize(500)
.expireAfterWrite(1, TimeUnit.MINUTES) .expireAfterWrite(1, TimeUnit.MINUTES)
.build(chunkPos -> this.loadChunkOrEmpty(chunkPos, 2, 1000)); .build(this::loadChunk);
} }
public BlockState getBlockState(Vector3i pos) { public BlockState getBlockState(Vector3i pos) {
Vector2i chunkPos = blockToChunk(pos); return getChunk(blockToChunk(pos)).getBlockState(pos);
Chunk chunk = getChunk(chunkPos);
return chunk.getBlockState(pos);
} }
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(int x, int y, int z) {
if (pos.getY() < getMinY()) { if (y < getMinY()) {
pos = new Vector3i(pos.getX(), getMinY(), pos.getZ()); y = getMinY();
} else if (pos.getY() > getMaxY()) { } else if (y > getMaxY()) {
pos = new Vector3i(pos.getX(), getMaxY(), pos.getZ()); y = getMaxY();
} }
Vector2i chunkPos = blockToChunk(pos); MCAChunk chunk = getChunk(x >> 4, z >> 4);
Chunk chunk = getChunk(chunkPos); return chunk.getBiome(x, y, z);
return chunk.getBiome(pos);
} }
@Override @Override
@ -147,17 +149,16 @@ public Block getBlock(Vector3i pos) {
} else if (pos.getY() > getMaxY()) { } else if (pos.getY() > getMaxY()) {
return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos); return new Block(this, BlockState.AIR, LightData.SKY, Biome.DEFAULT, BlockProperties.TRANSPARENT, pos);
} }
Vector2i chunkPos = blockToChunk(pos); MCAChunk chunk = getChunk(blockToChunk(pos));
Chunk chunk = getChunk(chunkPos);
BlockState blockState = getExtendedBlockState(chunk, pos); BlockState blockState = getExtendedBlockState(chunk, pos);
LightData lightData = chunk.getLightData(pos); LightData lightData = chunk.getLightData(pos);
Biome biome = chunk.getBiome(pos); Biome biome = chunk.getBiome(pos.getX(), pos.getY(), pos.getZ());
BlockProperties properties = blockPropertiesMapper.get(blockState); BlockProperties properties = blockPropertiesMapper.get(blockState);
return new Block(this, blockState, lightData, biome, properties, pos); return new Block(this, blockState, lightData, biome, properties, pos);
} }
private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) { private BlockState getExtendedBlockState(MCAChunk chunk, Vector3i pos) {
BlockState blockState = chunk.getBlockState(pos); BlockState blockState = chunk.getBlockState(pos);
if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions if (chunk instanceof ChunkAnvil112) { // only use extensions if old format chunk (1.12) in the new format block-states are saved with extensions
@ -168,137 +169,42 @@ private BlockState getExtendedBlockState(Chunk chunk, Vector3i pos) {
return blockState; return blockState;
} }
public Chunk getChunk(Vector2i chunkPos) {
try {
Chunk chunk = chunkCache.get(chunkPos);
return chunk;
} catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) Thread.currentThread().interrupt();
throw e;
}
}
private Chunk loadChunkOrEmpty(Vector2i chunkPos, int tries, long tryInterval) {
Exception loadException = null;
for (int i = 0; i < tries; i++) {
try {
return loadChunk(chunkPos);
} catch (IOException | RuntimeException e) {
if (loadException != null) e.addSuppressed(loadException);
loadException = e;
if (tryInterval > 0 && i+1 < tries) {
try {
Thread.sleep(tryInterval);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Logger.global.logDebug("Unexpected exception trying to load chunk (" + chunkPos + "):" + loadException);
return Chunk.empty(this, chunkPos);
}
private Chunk loadChunk(Vector2i chunkPos) throws IOException {
Vector2i regionPos = chunkToRegion(chunkPos);
Path regionPath = getMCAFilePath(regionPos);
File regionFile = regionPath.toFile();
if (!regionFile.exists() || regionFile.length() <= 0) return Chunk.empty(this, chunkPos);
try (RandomAccessFile raf = new RandomAccessFile(regionFile, "r")) {
int xzChunk = Math.floorMod(chunkPos.getY(), 32) * 32 + Math.floorMod(chunkPos.getX(), 32);
raf.seek(xzChunk * 4);
int offset = raf.read() << 16;
offset |= (raf.read() & 0xFF) << 8;
offset |= raf.read() & 0xFF;
offset *= 4096;
int size = raf.readByte() * 4096;
if (size == 0) {
return Chunk.empty(this, chunkPos);
}
raf.seek(offset + 4); // +4 skip chunk size
byte compressionTypeByte = raf.readByte();
CompressionType compressionType = CompressionType.getFromID(compressionTypeByte);
if (compressionType == null) {
throw new IOException("Invalid compression type " + compressionTypeByte);
}
DataInputStream dis = new DataInputStream(new BufferedInputStream(compressionType.decompress(new FileInputStream(raf.getFD()))));
Tag<?> tag = Tag.deserialize(dis, Tag.DEFAULT_MAX_DEPTH);
if (tag instanceof CompoundTag) {
return Chunk.create(this, (CompoundTag) tag, ignoreMissingLightData);
} else {
throw new IOException("Invalid data tag: " + (tag == null ? "null" : tag.getClass().getName()));
}
} catch (RuntimeException e) {
throw new IOException(e);
}
}
@Override @Override
public boolean isChunkGenerated(Vector2i chunkPos) { public MCAChunk getChunk(int x, int z) {
Chunk chunk = getChunk(chunkPos); return getChunk(new Vector2i(x, z));
return chunk.isGenerated();
} }
public MCAChunk getChunk(Vector2i pos) {
return chunkCache.get(pos);
}
@Override @Override
public Collection<Vector2i> getChunkList(long modifiedSinceMillis, Predicate<Vector2i> filter){ public MCARegion getRegion(int x, int z) {
List<Vector2i> chunks = new ArrayList<>(10000); return regionCache.get(new Vector2i(x, z));
}
if (!getRegionFolder().toFile().isDirectory()) return Collections.emptyList();
@Override
for (File file : getRegionFolder().toFile().listFiles()) { public Collection<Vector2i> listRegions() {
File[] regionFiles = getRegionFolder().toFile().listFiles();
if (regionFiles == null) return Collections.emptyList();
List<Vector2i> regions = new ArrayList<>(regionFiles.length);
for (File file : regionFiles) {
if (!file.getName().endsWith(".mca")) continue; if (!file.getName().endsWith(".mca")) continue;
if (file.length() <= 0) continue; if (file.length() <= 0) continue;
try (RandomAccessFile raf = new RandomAccessFile(file, "r")) {
try {
String[] filenameParts = file.getName().split("\\."); String[] filenameParts = file.getName().split("\\.");
int rX = Integer.parseInt(filenameParts[1]); int rX = Integer.parseInt(filenameParts[1]);
int rZ = Integer.parseInt(filenameParts[2]); int rZ = Integer.parseInt(filenameParts[2]);
for (int x = 0; x < 32; x++) { regions.add(new Vector2i(rX, rZ));
for (int z = 0; z < 32; z++) { } catch (NumberFormatException ignore) {}
Vector2i chunk = new Vector2i(rX * 32 + x, rZ * 32 + z);
if (filter.test(chunk)) {
int xzChunk = z * 32 + x;
raf.seek(xzChunk * 4 + 3);
int size = raf.readByte() * 4096;
if (size == 0) continue;
raf.seek(xzChunk * 4 + 4096);
int timestamp = raf.read() << 24;
timestamp |= (raf.read() & 0xFF) << 16;
timestamp |= (raf.read() & 0xFF) << 8;
timestamp |= raf.read() & 0xFF;
if (timestamp >= (modifiedSinceMillis / 1000)) {
chunks.add(chunk);
}
}
}
}
} catch (RuntimeException | IOException ex) {
Logger.global.logWarning("Failed to read .mca file: " + file.getAbsolutePath() + " (" + ex.toString() + ")");
}
} }
return chunks; return regions;
} }
@Override @Override
@ -318,7 +224,27 @@ public Path getSaveFolder() {
@Override @Override
public int getSeaLevel() { public int getSeaLevel() {
return seaLevel; return 63;
}
@Override
public int getMinY() {
return 0;
}
@Override
public int getMaxY() {
return 255;
}
@Override
public Grid getChunkGrid() {
return CHUNK_GRID;
}
@Override
public Grid getRegionGrid() {
return REGION_GRID;
} }
@Override @Override
@ -332,8 +258,8 @@ public void invalidateChunkCache() {
} }
@Override @Override
public void invalidateChunkCache(Vector2i chunk) { public void invalidateChunkCache(int x, int z) {
chunkCache.invalidate(chunk); chunkCache.invalidate(new Vector2i(x, z));
} }
@Override @Override
@ -381,8 +307,8 @@ private Path getRegionFolder() {
return worldFolder.resolve("region"); return worldFolder.resolve("region");
} }
private Path getMCAFilePath(Vector2i region) { private File getMCAFile(int regionX, int regionZ) {
return getRegionFolder().resolve(MCAUtil.createNameFromRegionLocation(region.getX(), region.getY())); return getRegionFolder().resolve("r." + regionX + "." + regionZ + ".mca").toFile();
} }
private void registerBlockStateExtension(BlockStateExtension extension) { private void registerBlockStateExtension(BlockStateExtension extension) {
@ -390,7 +316,48 @@ private void registerBlockStateExtension(BlockStateExtension extension) {
this.blockStateExtensions.put(id, extension); this.blockStateExtensions.put(id, extension);
} }
} }
private MCARegion loadRegion(Vector2i regionPos) {
return loadRegion(regionPos.getX(), regionPos.getY());
}
private MCARegion loadRegion(int x, int z) {
File regionPath = getMCAFile(x, z);
return new MCARegion(this, regionPath);
}
private MCAChunk loadChunk(Vector2i chunkPos) {
return loadChunk(chunkPos.getX(), chunkPos.getY());
}
private MCAChunk loadChunk(int x, int z) {
final int tries = 3;
final int tryInterval = 1000;
Exception loadException = null;
for (int i = 0; i < tries; i++) {
try {
return getRegion(x >> 5, z >> 5)
.loadChunk(x, z, ignoreMissingLightData);
} catch (IOException | RuntimeException e) {
if (loadException != null) e.addSuppressed(loadException);
loadException = e;
if (i + 1 < tries) {
try {
Thread.sleep(tryInterval);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
}
}
Logger.global.logDebug("Unexpected exception trying to load chunk (x:" + x + ", z:" + z + "):" + loadException);
return MCAChunk.empty();
}
public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion version, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, BiomeMapper biomeIdMapper) throws IOException { public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion version, BlockIdMapper blockIdMapper, BlockPropertiesMapper blockPropertiesMapper, BiomeMapper biomeIdMapper) throws IOException {
return load(worldFolder, uuid, version, blockIdMapper, blockPropertiesMapper, biomeIdMapper, null, false); return load(worldFolder, uuid, version, blockIdMapper, blockPropertiesMapper, biomeIdMapper, null, false);
} }
@ -422,9 +389,7 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
if (name == null) { if (name == null) {
name = levelData.getString("LevelName") + subDimensionName; name = levelData.getString("LevelName") + subDimensionName;
} }
int worldHeight = 255;
int seaLevel = 63;
Vector3i spawnPoint = new Vector3i( Vector3i spawnPoint = new Vector3i(
levelData.getInt("SpawnX"), levelData.getInt("SpawnX"),
levelData.getInt("SpawnY"), levelData.getInt("SpawnY"),
@ -435,9 +400,7 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
worldFolder, worldFolder,
uuid, uuid,
version, version,
name, name,
worldHeight,
seaLevel,
spawnPoint, spawnPoint,
blockIdMapper, blockIdMapper,
blockPropertiesMapper, blockPropertiesMapper,
@ -469,22 +432,8 @@ public static MCAWorld load(Path worldFolder, UUID uuid, MinecraftVersion versio
public static Vector2i blockToChunk(Vector3i pos) { public static Vector2i blockToChunk(Vector3i pos) {
return new Vector2i( return new Vector2i(
MCAUtil.blockToChunk(pos.getX()), pos.getX() >> 4,
MCAUtil.blockToChunk(pos.getZ()) pos.getZ() >> 4
);
}
public static Vector2i blockToRegion(Vector3i pos) {
return new Vector2i(
MCAUtil.blockToRegion(pos.getX()),
MCAUtil.blockToRegion(pos.getZ())
);
}
public static Vector2i chunkToRegion(Vector2i pos) {
return new Vector2i(
MCAUtil.chunkToRegion(pos.getX()),
MCAUtil.chunkToRegion(pos.getY())
); );
} }

View File

@ -1,67 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.render;
import com.flowpowered.math.vector.Vector3i;
public class StaticRenderSettings implements RenderSettings {
private boolean excludeFacesWithoutSunlight;
private Vector3i min, max;
private boolean renderEdges;
public StaticRenderSettings(
boolean excludeFacesWithoutSunlight,
Vector3i min,
Vector3i max,
boolean renderEdges
) {
this.excludeFacesWithoutSunlight = excludeFacesWithoutSunlight;
this.min = min;
this.max = max;
this.renderEdges = renderEdges;
}
@Override
public boolean isExcludeFacesWithoutSunlight() {
return excludeFacesWithoutSunlight;
}
@Override
public Vector3i getMin() {
return min;
}
@Override
public Vector3i getMax() {
return max;
}
@Override
public boolean isRenderEdges() {
return renderEdges;
}
}

View File

@ -1,68 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.render;
import de.bluecolored.bluemap.core.render.hires.HiresModel;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.render.lowres.LowresModelManager;
import de.bluecolored.bluemap.core.util.AABB;
public class TileRenderer {
private HiresModelManager hiresModelManager;
private LowresModelManager lowresModelManager;
public TileRenderer(HiresModelManager hiresModelManager, LowresModelManager lowresModelManager) {
this.hiresModelManager = hiresModelManager;
this.lowresModelManager = lowresModelManager;
}
/**
* Renders the provided WorldTile (only) if the world is generated
*/
public void render(WorldTile tile) {
//check if the region is generated before rendering, don't render if it's not generated
AABB area = hiresModelManager.getTileRegion(tile);
if (!tile.getWorld().isAreaGenerated(area)) return;
HiresModel hiresModel = hiresModelManager.render(tile);
lowresModelManager.render(hiresModel);
}
/**
* Saves changes to disk
*/
public void save(){
lowresModelManager.save();
}
public HiresModelManager getHiresModelManager() {
return hiresModelManager;
}
public LowresModelManager getLowresModelManager() {
return lowresModelManager;
}
}

View File

@ -1,69 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.render;
import java.util.Objects;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.world.World;
public class WorldTile {
private World world;
private Vector2i tile;
private int hash;
public WorldTile(World world, Vector2i tile) {
this.world = world;
this.tile = tile;
this.hash = Objects.hash(world.getUUID(), tile);
}
public World getWorld() {
return world;
}
public Vector2i getTile() {
return tile;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof WorldTile)) return false;
WorldTile that = (WorldTile) obj;
if (!this.world.getUUID().equals(that.world.getUUID())) return false;
return this.tile.equals(that.tile);
}
@Override
public int hashCode() {
return hash;
}
}

View File

@ -1,229 +0,0 @@
/*
* This file is part of SpongeAPI, licensed under the MIT License (MIT).
*
* Copyright (c) SpongePowered <https://www.spongepowered.org>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.core.util;
import static com.google.common.base.Preconditions.checkNotNull;
import com.flowpowered.math.GenericMath;
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
/**
* An axis aligned bounding box. That is, an un-rotated cuboid.
* It is represented by its minimum and maximum corners.
*
* Using integers, the box has a minimum size of 1 in each direction.
*
* <p>This class is immutable, all objects returned are either new instances or
* itself.</p>
*/
public class AABB {
private final Vector3i min;
private final Vector3i max;
private Vector3i size = null;
private Vector3d center = null;
/**
* Constructs a new bounding box from two opposite corners.
* Fails the resulting box would be degenerate (a dimension is 0).
*
* @param x1 The first corner x coordinate
* @param y1 The first corner y coordinate
* @param z1 The first corner z coordinate
* @param x2 The second corner x coordinate
* @param y2 The second corner y coordinate
* @param z2 The second corner z coordinate
*/
public AABB(int x1, int y1, int z1, int x2, int y2, int z2) {
this(new Vector3i(x1, y1, z1), new Vector3i(x2, y2, z2));
}
/**
* Constructs a new bounding box from two opposite corners.
* Fails the resulting box would be degenerate (a dimension is 0).
*
* @param firstCorner The first corner
* @param secondCorner The second corner
*/
public AABB(Vector3i firstCorner, Vector3i secondCorner) {
checkNotNull(firstCorner, "firstCorner");
checkNotNull(secondCorner, "secondCorner");
this.min = firstCorner.min(secondCorner);
this.max = firstCorner.max(secondCorner);
}
/**
* The minimum corner of the box.
*
* @return The minimum corner
*/
public Vector3i getMin() {
return this.min;
}
/**
* The maximum corner of the box.
*
* @return The maximum corner
*/
public Vector3i getMax() {
return this.max;
}
/**
* Returns the center of the box, halfway between each corner.
*
* @return The center
*/
public Vector3d getCenter() {
if (this.center == null) {
this.center = this.min.toDouble().add(getSize().toDouble().div(2));
}
return this.center;
}
/**
* Gets the size of the box.
*
* @return The size
*/
public Vector3i getSize() {
if (this.size == null) {
this.size = this.max.sub(this.min).add(Vector3i.ONE);
}
return this.size;
}
/**
* Checks if the bounding box contains a point.
*
* @param point The point to check
* @return Whether or not the box contains the point
*/
public boolean contains(Vector3i point) {
checkNotNull(point, "point");
return contains(point.getX(), point.getY(), point.getZ());
}
/**
* Checks if the bounding box contains a point.
*
* @param point The point to check
* @return Whether or not the box contains the point
*/
public boolean contains(Vector3d point) {
checkNotNull(point, "point");
return contains(point.getX(), point.getY(), point.getZ());
}
/**
* Checks if the bounding box contains a point.
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
* @param z The z coordinate of the point
* @return Whether or not the box contains the point
*/
public boolean contains(double x, double y, double z) {
return contains(GenericMath.floor(x), GenericMath.floor(y), GenericMath.floor(z));
}
/**
* Checks if the bounding box contains a point.
*
* @param x The x coordinate of the point
* @param y The y coordinate of the point
* @param z The z coordinate of the point
* @return Whether or not the box contains the point
*/
public boolean contains(int x, int y, int z) {
return this.min.getX() <= x && this.max.getX() >= x
&& this.min.getY() <= y && this.max.getY() >= y
&& this.min.getZ() <= z && this.max.getZ() >= z;
}
/**
* Checks if the bounding box intersects another.
*
* @param other The other bounding box to check
* @return Whether this bounding box intersects with the other
*/
public boolean intersects(AABB other) {
checkNotNull(other, "other");
return this.max.getX() >= other.getMin().getX() && other.getMax().getX() >= this.min.getX()
&& this.max.getY() >= other.getMin().getY() && other.getMax().getY() >= this.min.getY()
&& this.max.getZ() >= other.getMin().getZ() && other.getMax().getZ() >= this.min.getZ();
}
/**
* Offsets this bounding box by a given amount and returns a new box.
*
* @param offset The offset to apply
* @return The new offset box
*/
public AABB offset(Vector3i offset) {
checkNotNull(offset, "offset");
return offset(offset.getX(), offset.getY(), offset.getZ());
}
/**
* Offsets this bounding box by a given amount and returns a new box.
*
* @param x The amount of offset for the x coordinate
* @param y The amount of offset for the y coordinate
* @param z The amount of offset for the z coordinate
* @return The new offset box
*/
public AABB offset(int x, int y, int z) {
return new AABB(this.min.add(x, y, z), this.max.add(x, y, z));
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(other instanceof AABB)) {
return false;
}
final AABB aabb = (AABB) other;
return this.min.equals(aabb.min) && this.max.equals(aabb.max);
}
@Override
public int hashCode() {
int result = this.min.hashCode();
result = 31 * result + this.max.hashCode();
return result;
}
@Override
public String toString() {
return "AABB(" + this.min + " to " + this.max + ")";
}
}

View File

@ -24,36 +24,39 @@
*/ */
package de.bluecolored.bluemap.core.webserver; package de.bluecolored.bluemap.core.webserver;
import java.io.IOException; import de.bluecolored.bluemap.core.logger.Logger;
import java.io.InputStream;
import java.io.OutputStream; import java.io.*;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.net.SocketException; import java.net.SocketException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.util.concurrent.TimeUnit;
import de.bluecolored.bluemap.core.logger.Logger;
import java.text.DateFormat; import java.text.DateFormat;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date; import java.util.Date;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
public class HttpConnection implements Runnable { public class HttpConnection implements Runnable {
private HttpRequestHandler handler; private final HttpRequestHandler handler;
private ServerSocket server; private final ServerSocket server;
private Socket connection; private final Socket connection;
private InputStream in; private final InputStream in;
private OutputStream out; private final OutputStream out;
private final boolean verbose; private final Semaphore processingSemaphore;
public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler handler, int timeout, TimeUnit timeoutUnit, boolean verbose) throws IOException { private final boolean verbose;
public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler handler, Semaphore processingSemaphore, int timeout, TimeUnit timeoutUnit, boolean verbose) throws IOException {
this.server = server; this.server = server;
this.connection = connection; this.connection = connection;
this.handler = handler; this.handler = handler;
this.verbose = verbose; this.verbose = verbose;
this.processingSemaphore = processingSemaphore;
if (isClosed()){ if (isClosed()){
throw new IOException("Socket already closed!"); throw new IOException("Socket already closed!");
@ -61,8 +64,8 @@ public HttpConnection(ServerSocket server, Socket connection, HttpRequestHandler
connection.setSoTimeout((int) timeoutUnit.toMillis(timeout)); connection.setSoTimeout((int) timeoutUnit.toMillis(timeout));
in = this.connection.getInputStream(); in = new BufferedInputStream(this.connection.getInputStream());
out = this.connection.getOutputStream(); out = new BufferedOutputStream(this.connection.getOutputStream());
} }
@Override @Override
@ -70,25 +73,30 @@ public void run() {
while (!isClosed() && !server.isClosed()){ while (!isClosed() && !server.isClosed()){
try { try {
HttpRequest request = acceptRequest(); HttpRequest request = acceptRequest();
HttpResponse response = handler.handle(request);
sendResponse(response); try {
if (verbose) { processingSemaphore.acquire();
log(request, response);
HttpResponse response = handler.handle(request);
sendResponse(response);
if (verbose) log(request, response);
} finally {
processingSemaphore.release();
} }
} catch (InvalidRequestException e){ } catch (InvalidRequestException e){
try { try {
sendResponse(new HttpResponse(HttpStatusCode.BAD_REQUEST)); sendResponse(new HttpResponse(HttpStatusCode.BAD_REQUEST));
} catch (IOException e1) {} } catch (IOException ignored) {}
break; break;
} catch (SocketTimeoutException e) { } catch (SocketTimeoutException | ConnectionClosedException | SocketException e) {
break;
} catch (SocketException e){
break;
} catch (ConnectionClosedException e){
break; break;
} catch (IOException e) { } catch (IOException e) {
Logger.global.logError("Unexpected error while processing a HttpRequest!", e); Logger.global.logError("Unexpected error while processing a HttpRequest!", e);
break; break;
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} }
} }

View File

@ -24,33 +24,22 @@
*/ */
package de.bluecolored.bluemap.core.webserver; package de.bluecolored.bluemap.core.webserver;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import de.bluecolored.bluemap.core.webserver.HttpConnection.ConnectionClosedException; import de.bluecolored.bluemap.core.webserver.HttpConnection.ConnectionClosedException;
import de.bluecolored.bluemap.core.webserver.HttpConnection.InvalidRequestException; import de.bluecolored.bluemap.core.webserver.HttpConnection.InvalidRequestException;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class HttpRequest { public class HttpRequest {
private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$"); private static final Pattern REQUEST_PATTERN = Pattern.compile("^(\\w+) (\\S+) (.+)$");
private String method; private String method;
private String adress; private String address;
private String version; private String version;
private Map<String, Set<String>> header; private Map<String, Set<String>> header;
private Map<String, Set<String>> headerLC; private Map<String, Set<String>> headerLC;
@ -60,9 +49,9 @@ public class HttpRequest {
private Map<String, String> getParams = null; private Map<String, String> getParams = null;
private String getParamString = null; private String getParamString = null;
public HttpRequest(String method, String adress, String version, Map<String, Set<String>> header) { public HttpRequest(String method, String address, String version, Map<String, Set<String>> header) {
this.method = method; this.method = method;
this.adress = adress; this.address = address;
this.version = version; this.version = version;
this.header = header; this.header = header;
this.headerLC = new HashMap<>(); this.headerLC = new HashMap<>();
@ -83,8 +72,8 @@ public String getMethod() {
return method; return method;
} }
public String getAdress(){ public String getAddress(){
return adress; return address;
} }
public String getVersion() { public String getVersion() {
@ -127,7 +116,7 @@ public String getGETParamString() {
} }
private void parseAdress() { private void parseAdress() {
String adress = this.adress; String adress = this.address;
if (adress.isEmpty()) adress = "/"; if (adress.isEmpty()) adress = "/";
String[] adressParts = adress.split("\\?", 2); String[] adressParts = adress.split("\\?", 2);
String path = adressParts[0]; String path = adressParts[0];
@ -167,8 +156,8 @@ public static HttpRequest read(InputStream in) throws IOException, InvalidReques
String method = m.group(1); String method = m.group(1);
if (method == null) throw new InvalidRequestException(); if (method == null) throw new InvalidRequestException();
String adress = m.group(2); String address = m.group(2);
if (adress == null) throw new InvalidRequestException(); if (address == null) throw new InvalidRequestException();
String version = m.group(3); String version = m.group(3);
if (version == null) throw new InvalidRequestException(); if (version == null) throw new InvalidRequestException();
@ -192,7 +181,7 @@ public static HttpRequest read(InputStream in) throws IOException, InvalidReques
headerMap.put(kv[0].trim(), values); headerMap.put(kv[0].trim(), values);
} }
HttpRequest request = new HttpRequest(method, adress, version, headerMap); HttpRequest request = new HttpRequest(method, address, version, headerMap);
if (request.getLowercaseHeader("Transfer-Encoding").contains("chunked")){ if (request.getLowercaseHeader("Transfer-Encoding").contains("chunked")){
try { try {

View File

@ -24,22 +24,13 @@
*/ */
package de.bluecolored.bluemap.core.webserver; package de.bluecolored.bluemap.core.webserver;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.Map.Entry;
public class HttpResponse implements Closeable { public class HttpResponse implements Closeable {
private String version; private String version;
@ -57,22 +48,12 @@ public HttpResponse(HttpStatusCode statusCode) {
} }
public void addHeader(String key, String value){ public void addHeader(String key, String value){
Set<String> valueSet = header.get(key); Set<String> valueSet = header.computeIfAbsent(key, k -> new HashSet<>());
if (valueSet == null){
valueSet = new HashSet<>();
header.put(key, valueSet);
}
valueSet.add(value); valueSet.add(value);
} }
public void removeHeader(String key, String value){ public void removeHeader(String key, String value){
Set<String> valueSet = header.get(key); Set<String> valueSet = header.computeIfAbsent(key, k -> new HashSet<>());
if (valueSet == null){
valueSet = new HashSet<>();
header.put(key, valueSet);
}
valueSet.remove(value); valueSet.remove(value);
} }

View File

@ -41,6 +41,7 @@ public class WebServer extends Thread {
private final boolean verbose; private final boolean verbose;
private final HttpRequestHandler handler; private final HttpRequestHandler handler;
private final Semaphore processingSemaphore;
private ThreadPoolExecutor connectionThreads; private ThreadPoolExecutor connectionThreads;
@ -57,6 +58,7 @@ public WebServer(InetAddress bindAddress, int port, int maxConnections, HttpRequ
this.verbose = verbose; this.verbose = verbose;
this.handler = handler; this.handler = handler;
this.processingSemaphore = new Semaphore(18);
connectionThreads = null; connectionThreads = null;
} }
@ -90,7 +92,7 @@ public void run(){
Socket connection = server.accept(); Socket connection = server.accept();
try { try {
connectionThreads.execute(new HttpConnection(server, connection, handler, 10, TimeUnit.SECONDS, verbose)); connectionThreads.execute(new HttpConnection(server, connection, handler, processingSemaphore, 10, TimeUnit.SECONDS, verbose));
} catch (RejectedExecutionException e){ } catch (RejectedExecutionException e){
connection.close(); connection.close();
Logger.global.logWarning("Dropped an incoming HttpConnection! (Too many connections?)"); Logger.global.logWarning("Dropped an incoming HttpConnection! (Too many connections?)");

View File

@ -42,7 +42,7 @@
*/ */
public class BlockState { public class BlockState {
private static Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$"); private static final Pattern BLOCKSTATE_SERIALIZATION_PATTERN = Pattern.compile("^(.+?)(?:\\[(.*)\\])?$");
public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap()); public static final BlockState AIR = new BlockState("minecraft:air", Collections.emptyMap());
public static final BlockState MISSING = new BlockState("bluemap:missing", Collections.emptyMap()); public static final BlockState MISSING = new BlockState("bluemap:missing", Collections.emptyMap());

View File

@ -0,0 +1,7 @@
package de.bluecolored.bluemap.core.world;
public interface Chunk {
boolean isGenerated();
}

View File

@ -0,0 +1,108 @@
package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import java.util.Objects;
public class Grid {
public static final Grid UNIT = new Grid(Vector2i.ONE);
private final Vector2i gridSize;
private final Vector2i offset;
public Grid(int gridSize) {
this(gridSize, 0);
}
public Grid(int gridSize, int offset) {
this(new Vector2i(gridSize, gridSize), new Vector2i(offset, offset));
}
public Grid(Vector2i gridSize) {
this(gridSize, Vector2i.ZERO);
}
public Grid(Vector2i gridSize, Vector2i offset) {
Objects.requireNonNull(gridSize);
Objects.requireNonNull(offset);
gridSize = gridSize.max(1,1);
this.gridSize = gridSize;
this.offset = offset;
}
public Vector2i getGridSize() {
return gridSize;
}
public Vector2i getOffset() {
return offset;
}
public Vector2i getCell(Vector2i pos) {
return new Vector2i(
Math.floorDiv(pos.getX() - offset.getX(), gridSize.getX()),
Math.floorDiv(pos.getY() - offset.getY(), gridSize.getY())
);
}
public Vector2i getCellMin(Vector2i cell) {
return new Vector2i(
cell.getX() * gridSize.getX() + offset.getX(),
cell.getY() * gridSize.getY() + offset.getY()
);
}
public Vector2i getCellMax(Vector2i cell) {
return new Vector2i(
(cell.getX() + 1) * gridSize.getX() + offset.getX() - 1,
(cell.getY() + 1) * gridSize.getY() + offset.getY() - 1
);
}
public Vector2i getCellMin(Vector2i cell, Grid targetGrid) {
return targetGrid.getCell(getCellMin(cell));
}
public Vector2i getCellMax(Vector2i cell, Grid targetGrid) {
return targetGrid.getCell(getCellMax(cell));
}
public Grid multiply(Grid other) {
return new Grid(
this.gridSize.mul(other.gridSize),
this.offset.mul(other.gridSize).add(other.offset)
);
}
public Grid divide(Grid other) {
return new Grid(
this.gridSize.div(other.gridSize),
this.offset.sub(other.offset).div(other.gridSize)
);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Grid grid = (Grid) o;
return gridSize.equals(grid.gridSize) && offset.equals(grid.offset);
}
@Override
public int hashCode() {
return Objects.hash(gridSize, offset);
}
@Override
public String toString() {
return "Grid{" +
"gridSize=" + gridSize +
", offset=" + offset +
'}';
}
}

View File

@ -0,0 +1,33 @@
package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
public interface Region {
/**
* Returns a collection of all generated chunks.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
default Collection<Vector2i> listChunks(){
return listChunks(0);
}
/**
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
Collection<Vector2i> listChunks(long modifiedSince);
default Chunk loadChunk(int chunkX, int chunkZ) throws IOException {
return loadChunk(chunkX, chunkZ, false);
}
Chunk loadChunk(int chunkX, int chunkZ, boolean ignoreMissingLightData) throws IOException;
File getRegionFile();
}

View File

@ -24,24 +24,22 @@
*/ */
package de.bluecolored.bluemap.core.world; package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.function.Predicate; import java.util.function.Predicate;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
/** /**
* A sliced view of a world. Everything outside the defined bounds is seen as "not generated" and "air". * A sliced view of a world. Everything outside the defined bounds is seen as "not generated" and "air".
*/ */
public class SlicedWorld implements World { public class SlicedWorld implements World {
private World world; private final World world;
private Vector3i min; private final Vector3i min;
private Vector3i max; private final Vector3i max;
public SlicedWorld(World world, Vector3i min, Vector3i max) { public SlicedWorld(World world, Vector3i min, Vector3i max) {
this.world = world; this.world = world;
@ -83,7 +81,17 @@ public int getMaxY() {
public int getMinY() { public int getMinY() {
return world.getMinY(); return world.getMinY();
} }
@Override
public Grid getChunkGrid() {
return world.getChunkGrid();
}
@Override
public Grid getRegionGrid() {
return world.getRegionGrid();
}
@Override @Override
public Biome getBiome(Vector3i pos) { public Biome getBiome(Vector3i pos) {
return world.getBiome(pos); return world.getBiome(pos);
@ -106,45 +114,6 @@ public Block getBlock(int x, int y, int z) {
block.setWorld(this); block.setWorld(this);
return block; return block;
} }
@Override
public Collection<Vector2i> getChunkList(long modifiedSince, Predicate<Vector2i> filter) {
return world.getChunkList(modifiedSince, filter.and(chunk -> isInside(chunk)));
}
@Override
public boolean isChunkGenerated(Vector2i chunkPos) {
if (!isInside(chunkPos)) return false;
return world.isChunkGenerated(chunkPos);
}
@Override
public boolean isAreaGenerated(AABB area) {
return isAreaGenerated(area.getMin(), area.getMax());
}
@Override
public boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) {
return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax));
}
@Override
public boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) {
if (!isInside(chunkMin) &&
!isInside(chunkMax) &&
!isInside(new Vector2i(chunkMin.getX(), chunkMax.getY())) &&
!isInside(new Vector2i(chunkMax.getX(), chunkMin.getY()))
) return false;
for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) {
for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) {
if (!world.isChunkGenerated(new Vector2i(x, z))) return false;
}
}
return true;
}
@Override @Override
public void invalidateChunkCache() { public void invalidateChunkCache() {
@ -152,8 +121,8 @@ public void invalidateChunkCache() {
} }
@Override @Override
public void invalidateChunkCache(Vector2i chunk) { public void invalidateChunkCache(int x, int z) {
world.invalidateChunkCache(chunk); world.invalidateChunkCache(x, z);
} }
@Override @Override
@ -161,11 +130,6 @@ public void cleanUpChunkCache() {
world.cleanUpChunkCache(); world.cleanUpChunkCache();
} }
@Override
public Vector2i blockPosToChunkPos(Vector3i block) {
return world.blockPosToChunkPos(block);
}
private boolean isInside(Vector3i blockPos) { private boolean isInside(Vector3i blockPos) {
return isInside(blockPos.getX(), blockPos.getY(), blockPos.getZ()); return isInside(blockPos.getX(), blockPos.getY(), blockPos.getZ());
} }
@ -180,19 +144,6 @@ private boolean isInside(int x, int y, int z) {
y <= max.getY(); y <= max.getY();
} }
private boolean isInside(Vector2i chunkPos) {
return isInside(chunkPos.getX(), chunkPos.getY());
}
private boolean isInside(int chunkX, int chunkZ) {
return
chunkX * 16 <= max.getX() &&
chunkX * 16 + 15 >= min.getX() &&
chunkZ * 16 <= max.getZ() &&
chunkZ * 16 + 15 >= min.getZ();
}
private Block createAirBlock(Vector3i pos) { private Block createAirBlock(Vector3i pos) {
return new Block( return new Block(
this, this,

View File

@ -24,17 +24,14 @@
*/ */
package de.bluecolored.bluemap.core.world; package de.bluecolored.bluemap.core.world;
import java.io.IOException; import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collection; import java.util.Collection;
import java.util.UUID; import java.util.UUID;
import java.util.function.Predicate; import java.util.function.Predicate;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.core.util.AABB;
/** /**
* Represents a World on the Server<br> * Represents a World on the Server<br>
* <br> * <br>
@ -52,123 +49,60 @@ public interface World {
Vector3i getSpawnPoint(); Vector3i getSpawnPoint();
default int getMaxY() { int getMaxY();
return 255;
}
default int getMinY() { int getMinY();
return 0;
} Grid getChunkGrid();
Grid getRegionGrid();
/** /**
* Returns the Biome on the specified position or the default biome if the block is not generated yet. * Returns the {@link Biome} on the specified position or the default biome if the block is not generated yet.
*/ */
Biome getBiome(Vector3i pos); Biome getBiome(int x, int y, int z);
/** /**
* Returns the Block on the specified position or an air-block if the block is not generated yet. * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet.
*/ */
Block getBlock(Vector3i pos); Block getBlock(Vector3i pos);
/** /**
* Returns the Block on the specified position or an air-block if the block is not generated yet. * Returns the {@link Block} on the specified position or an air-block if the block is not generated yet.
*/ */
default Block getBlock(int x, int y, int z) { default Block getBlock(int x, int y, int z) {
return getBlock(new Vector3i(x, y, z)); return getBlock(new Vector3i(x, y, z));
} }
/**
* Returns a collection of all generated chunks.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
public default Collection<Vector2i> getChunkList(){
return getChunkList(0, c -> true);
}
/**
* Returns a filtered collection of all generated chunks.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
public default Collection<Vector2i> getChunkList(Predicate<Vector2i> filter){
return getChunkList(0, filter);
}
/** /**
* Returns a collection of all chunks that have been modified at or after the specified timestamp.<br> * Returns the {@link Chunk} on the specified chunk-position
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/ */
public default Collection<Vector2i> getChunkList(long modifiedSince){ Chunk getChunk(int x, int z);
return getChunkList(modifiedSince, c -> true);
}
/**
* Returns a filtered collection of all chunks that have been modified at or after the specified timestamp.<br>
* <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
*/
public Collection<Vector2i> getChunkList(long modifiedSince, Predicate<Vector2i> filter);
/** /**
* Returns true if and only if that chunk is fully generated and no world-generation or lighting has yet to be done. * Returns the Chunk on the specified chunk-position
*/ */
public boolean isChunkGenerated(Vector2i chunkPos); Region getRegion(int x, int z);
/** /**
* Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done. * Returns a collection of all regions in this world.
* @param area The area to check * <i>(Be aware that the collection is not cached and recollected each time from the world-files!)</i>
* @throws IOException
*/ */
public default boolean isAreaGenerated(AABB area) { Collection<Vector2i> listRegions();
return isAreaGenerated(area.getMin(), area.getMax());
}
/**
* Returns true if and only if all chunks the given area is intersecting are fully generated and no world-generation or lighting has yet to be done.
* @param area The area to check
* @throws IOException
*/
public default boolean isAreaGenerated(Vector3i blockMin, Vector3i blockMax) {
return isAreaGenerated(blockPosToChunkPos(blockMin), blockPosToChunkPos(blockMax));
}
/**
* Returns true if and only if all chunks in the given range are fully generated and no world-generation or lighting has yet to be done.
* @param area The area to check
* @throws IOException
*/
public default boolean isAreaGenerated(Vector2i chunkMin, Vector2i chunkMax) {
for (int x = chunkMin.getX(); x <= chunkMax.getX(); x++) {
for (int z = chunkMin.getY(); z <= chunkMax.getY(); z++) {
if (!isChunkGenerated(new Vector2i(x, z))) return false;
}
}
return true;
}
/** /**
* Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk * Invalidates the complete chunk cache (if there is a cache), so that every chunk has to be reloaded from disk
*/ */
public void invalidateChunkCache(); void invalidateChunkCache();
/** /**
* Invalidates the chunk from the chunk-cache (if there is a cache), so that the chunk has to be reloaded from disk * Invalidates the chunk from the chunk-cache (if there is a cache), so that the chunk has to be reloaded from disk
*/ */
public void invalidateChunkCache(Vector2i chunk); void invalidateChunkCache(int x, int z);
/** /**
* Cleans up invalid cache-entries to free up memory * Cleans up invalid cache-entries to free up memory
*/ */
public void cleanUpChunkCache(); void cleanUpChunkCache();
/**
* Returns the ChunkPosition for a BlockPosition
*/
public default Vector2i blockPosToChunkPos(Vector3i block) {
return new Vector2i(
block.getX() >> 4,
block.getZ() >> 4
);
}
} }

View File

@ -24,10 +24,10 @@
*/ */
package de.bluecolored.bluemap.core.world; package de.bluecolored.bluemap.core.world;
import static org.junit.Assert.assertEquals; import org.junit.jupiter.api.Test;
import static org.junit.Assert.assertTrue;
import org.junit.Test; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class BlockStateTest { public class BlockStateTest {

View File

@ -0,0 +1,112 @@
package de.bluecolored.bluemap.core.world;
import com.flowpowered.math.vector.Vector2i;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GridTest {
@Test
public void testGetCell() {
Grid grid = new Grid(16, 0);
assertEquals(new Vector2i(0, 0), grid.getCell(new Vector2i(0, 0)));
assertEquals(new Vector2i(0, 0), grid.getCell(new Vector2i(15, 2)));
assertEquals(new Vector2i(1, 1), grid.getCell(new Vector2i(16, 20)));
assertEquals(new Vector2i(-1, -1), grid.getCell(new Vector2i(-1, -16)));
Grid grid2 = new Grid(16,2);
assertEquals(new Vector2i(-1, -1), grid2.getCell(new Vector2i(0, 0)));
assertEquals(new Vector2i(0, 0), grid2.getCell(new Vector2i(17, 2)));
}
@Test
public void testCellMin() {
Grid grid = new Grid(16, 0);
assertEquals(new Vector2i(0, 0), grid.getCellMin(new Vector2i(0, 0)));
assertEquals(new Vector2i(16, 32), grid.getCellMin(new Vector2i(1, 2)));
assertEquals(new Vector2i(-32, -16), grid.getCellMin(new Vector2i(-2, -1)));
Grid grid2 = new Grid(16, 2);
assertEquals(new Vector2i(2, 2), grid2.getCellMin(new Vector2i(0, 0)));
assertEquals(new Vector2i(18, 34), grid2.getCellMin(new Vector2i(1, 2)));
assertEquals(new Vector2i(-30, -14), grid2.getCellMin(new Vector2i(-2, -1)));
}
@Test
public void testCellMax() {
Grid grid = new Grid(16, 0);
assertEquals(new Vector2i(15, 15), grid.getCellMax(new Vector2i(0, 0)));
assertEquals(new Vector2i(31, 47), grid.getCellMax(new Vector2i(1, 2)));
assertEquals(new Vector2i(-17, -1), grid.getCellMax(new Vector2i(-2, -1)));
Grid grid2 = new Grid(16, 2);
assertEquals(new Vector2i(17, 17), grid2.getCellMax(new Vector2i(0, 0)));
assertEquals(new Vector2i(33, 49), grid2.getCellMax(new Vector2i(1, 2)));
assertEquals(new Vector2i(-15, 1), grid2.getCellMax(new Vector2i(-2, -1)));
}
@Test
public void testCellMinWithSmallerTargetGrid() {
Grid grid = new Grid(16, 0);
Grid target = new Grid(2, 1);
assertEquals(new Vector2i(-1, -1), grid.getCellMin(new Vector2i(0, 0), target));
assertEquals(new Vector2i(-9, 7), grid.getCellMin(new Vector2i(-1, 1), target));
}
@Test
public void testCellMinWithBiggerTargetGrid() {
Grid grid = new Grid(2, 0);
Grid target = new Grid(8, 2);
assertEquals(new Vector2i(-1, -1), grid.getCellMin(new Vector2i(0, 0), target));
assertEquals(new Vector2i(-1, 1), grid.getCellMin(new Vector2i(-1, 8), target));
assertEquals(new Vector2i(-1, 2), grid.getCellMin(new Vector2i(-1, 9), target));
}
@Test
public void testCellMaxWithSmallerTargetGrid() {
Grid grid = new Grid(16, 0);
Grid target = new Grid(2, 1);
assertEquals(new Vector2i(7, 7), grid.getCellMax(new Vector2i(0, 0), target));
assertEquals(new Vector2i(-1, 15), grid.getCellMax(new Vector2i(-1, 1), target));
}
@Test
public void testCellMaxWithBiggerTargetGrid() {
Grid grid = new Grid(2, 0);
Grid target = new Grid(8, 2);
assertEquals(new Vector2i(-1, -1), grid.getCellMax(new Vector2i(0, 0), target));
assertEquals(new Vector2i(-1, 1), grid.getCellMax(new Vector2i(-1, 8), target));
assertEquals(new Vector2i(-1, 2), grid.getCellMax(new Vector2i(-1, 9), target));
}
@Test
public void testMultiply() {
Grid grid1 = new Grid(2, 5);
Grid grid2 = new Grid(4, 2);
Grid result1 = new Grid(8, 22);
Grid result2 = new Grid(8, 9);
assertEquals(result1, grid1.multiply(grid2));
assertEquals(result2, grid2.multiply(grid1));
}
@Test
public void testDivide() {
Grid grid1 = new Grid(8, 22);
Grid grid2 = new Grid(4, 2);
Grid result1 = new Grid(2, 5);
assertEquals(result1, grid1.divide(grid2));
Grid grid3 = new Grid(8, 9);
Grid grid4 = new Grid(2, 5);
Grid result2 = new Grid(4, 2);
assertEquals(result2, grid3.divide(grid4));
}
}

View File

@ -24,29 +24,24 @@
*/ */
package de.bluecolored.bluemap.cli; package de.bluecolored.bluemap.cli;
import com.flowpowered.math.GenericMath; import de.bluecolored.bluemap.common.BlueMapService;
import com.flowpowered.math.vector.Vector2i; import de.bluecolored.bluemap.common.MissingResourcesException;
import de.bluecolored.bluemap.common.*; import de.bluecolored.bluemap.common.rendermanager.RenderManager;
import de.bluecolored.bluemap.core.BlueMap; import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion; import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.config.WebServerConfig; import de.bluecolored.bluemap.core.config.WebServerConfig;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.logger.LoggerLogger; import de.bluecolored.bluemap.core.logger.LoggerLogger;
import de.bluecolored.bluemap.core.metrics.Metrics; import de.bluecolored.bluemap.core.metrics.Metrics;
import de.bluecolored.bluemap.core.render.hires.HiresModelManager;
import de.bluecolored.bluemap.core.util.FileUtils; import de.bluecolored.bluemap.core.util.FileUtils;
import de.bluecolored.bluemap.core.web.FileRequestHandler; import de.bluecolored.bluemap.common.web.FileRequestHandler;
import de.bluecolored.bluemap.core.web.WebSettings; import de.bluecolored.bluemap.common.web.WebSettings;
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler; import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.WebServer; import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World;
import org.apache.commons.cli.*; import org.apache.commons.cli.*;
import org.apache.commons.lang3.time.DurationFormatUtils;
import java.io.*; import java.io.File;
import java.util.Collection; import java.io.IOException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
public class BlueMapCLI { public class BlueMapCLI {
@ -58,162 +53,9 @@ public void renderMaps(BlueMapService blueMap, boolean forceRender, boolean forc
blueMap.createOrUpdateWebApp(forceGenerateWebapp); blueMap.createOrUpdateWebApp(forceGenerateWebapp);
WebSettings webSettings = blueMap.updateWebAppSettings(); WebSettings webSettings = blueMap.updateWebAppSettings();
RenderManager renderManager = new RenderManager(blueMap.getCoreConfig().getRenderThreadCount()); RenderManager renderManager = new RenderManager();
File rmstate = new File(blueMap.getCoreConfig().getDataFolder(), "rmstate");
if (!forceRender && rmstate.exists()) {
try (
InputStream in = new GZIPInputStream(new FileInputStream(rmstate));
DataInputStream din = new DataInputStream(in);
){
renderManager.readState(din, blueMap.getMaps().values());
Logger.global.logInfo("Found unfinished render, continuing ... (If you want to start a new render, delete the this file: " + rmstate.getCanonicalPath() + " or force a full render using -f)");
} catch (IOException ex) {
Logger.global.logError("Failed to read saved render-state! Remove the file " + rmstate.getCanonicalPath() + " to start a new render.", ex);
return;
}
} else {
for (MapType map : blueMap.getMaps().values()) {
Logger.global.logInfo("Creating render-task for map '" + map.getId() + "' ...");
Logger.global.logInfo("Collecting tiles ...");
Collection<Vector2i> chunks;
if (!forceRender) {
long lastRender = webSettings.getLong("maps", map.getId(), "last-render");
chunks = map.getWorld().getChunkList(lastRender);
} else {
chunks = map.getWorld().getChunkList();
}
HiresModelManager hiresModelManager = map.getTileRenderer().getHiresModelManager();
Collection<Vector2i> tiles = hiresModelManager.getTilesForChunks(chunks);
Logger.global.logInfo("Found " + tiles.size() + " tiles to render! (" + chunks.size() + " chunks)");
if (!forceRender && chunks.size() == 0) {
Logger.global.logInfo("(This is normal if nothing has changed in the world since the last render. Use -f on the command-line to force a render of all chunks)");
}
if (tiles.isEmpty()) {
continue;
}
Vector2i renderCenter = map.getWorld().getSpawnPoint().toVector2(true); //TODO
RenderTask task = new RenderTask(map.getId(), map);
task.addTiles(tiles);
task.optimizeQueue(renderCenter);
renderManager.addRenderTask(task);
}
}
Logger.global.logInfo("Starting render ...");
renderManager.start();
Thread shutdownHook = new Thread(() -> {
Logger.global.logInfo("Stopping render ...");
renderManager.stop();
Logger.global.logInfo("Saving tiles ...");
RenderTask currentTask = renderManager.getCurrentRenderTask();
if (currentTask != null){
currentTask.getMapType().getTileRenderer().save();
}
try {
Logger.global.logInfo("Saving render-state ...");
FileUtils.createFile(rmstate);
try (
OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate));
DataOutputStream dos = new DataOutputStream(os);
) {
renderManager.writeState(dos);
Logger.global.logInfo("Render saved and stopped! Restart the render (without using -f) to resume.");
}
} catch (IOException ex) {
Logger.global.logError("Failed to save render-state!", ex);
}
});
Runtime.getRuntime().addShutdownHook(shutdownHook);
long startTime = System.currentTimeMillis();
long lastLogUpdate = startTime;
long lastSave = startTime;
while(renderManager.getRenderTaskCount() != 0) {
try {
Thread.sleep(200);
} catch (InterruptedException e) { Thread.currentThread().interrupt(); return; }
long now = System.currentTimeMillis();
if (lastLogUpdate < now - 10000) { // print update all 10 seconds
RenderTask currentTask = renderManager.getCurrentRenderTask();
if (currentTask == null) continue;
lastLogUpdate = now;
long time = currentTask.getActiveTime();
String durationString = DurationFormatUtils.formatDurationWords(time, true, true);
int tileCount = currentTask.getRemainingTileCount() + currentTask.getRenderedTileCount();
double pct = (double)currentTask.getRenderedTileCount() / (double) tileCount;
long ert = (long)((time / pct) * (1d - pct));
String ertDurationString = DurationFormatUtils.formatDurationWords(ert, true, true);
double tps = currentTask.getRenderedTileCount() / (time / 1000.0);
Logger.global.logInfo("Rendering map '" + currentTask.getName() + "':");
Logger.global.logInfo("Rendered " + currentTask.getRenderedTileCount() + " of " + tileCount + " tiles in " + durationString + " | " + GenericMath.round(tps, 3) + " tiles/s");
Logger.global.logInfo(GenericMath.round(pct * 100, 3) + "% | Estimated remaining time: " + ertDurationString);
}
if (lastSave < now - 1 * 60000) { // save every minute
RenderTask currentTask = renderManager.getCurrentRenderTask();
if (currentTask == null) continue;
lastSave = now;
currentTask.getMapType().getTileRenderer().save();
try (
OutputStream os = new GZIPOutputStream(new FileOutputStream(rmstate));
DataOutputStream dos = new DataOutputStream(os);
){
renderManager.writeState(dos);
} catch (IOException ex) {
Logger.global.logError("Failed to save render-state!", ex);
}
//clean up caches
for (World world : blueMap.getWorlds().values()) {
world.cleanUpChunkCache();
}
}
}
//render finished and saved, so this is no longer needed
Runtime.getRuntime().removeShutdownHook(shutdownHook);
//stop render-threads
renderManager.stop();
//render finished, so remove render state file
FileUtils.delete(rmstate);
for (MapType map : blueMap.getMaps().values()) {
webSettings.set(startTime, "maps", map.getId(), "last-render");
}
try {
webSettings.save();
} catch (IOException e) {
Logger.global.logError("Failed to update web-settings!", e);
}
Logger.global.logInfo("Render finished!");
} }
public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOException { public void startWebserver(BlueMapService blueMap, boolean verbose) throws IOException {

View File

@ -24,20 +24,11 @@
*/ */
package de.bluecolored.bluemap.fabric; package de.bluecolored.bluemap.fabric;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -51,6 +42,11 @@
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
public class FabricEventForwarder { public class FabricEventForwarder {
private FabricMod mod; private FabricMod mod;
@ -59,9 +55,7 @@ public class FabricEventForwarder {
public FabricEventForwarder(FabricMod mod) { public FabricEventForwarder(FabricMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
WorldSaveCallback.EVENT.register(this::onWorldSave);
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
AttackBlockCallback.EVENT.register(this::onBlockAttack); AttackBlockCallback.EVENT.register(this::onBlockAttack);
UseBlockCallback.EVENT.register(this::onBlockUse); UseBlockCallback.EVENT.register(this::onBlockUse);
@ -105,24 +99,6 @@ public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) {
} }
} }
public synchronized void onWorldSave(ServerWorld world) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) {
if (this.mod.getServer() != server) return; if (this.mod.getServer() != server) return;

View File

@ -1,43 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import com.flowpowered.math.vector.Vector2i;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface ChunkFinalizeCallback {
Event<ChunkFinalizeCallback> EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class,
(listeners) -> (world, chunkPos) -> {
for (ChunkFinalizeCallback event : listeners) {
event.onChunkFinalized(world, chunkPos);
}
}
);
void onChunkFinalized(ServerWorld world, Vector2i chunkPos);
}

View File

@ -1,41 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface WorldSaveCallback {
Event<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class,
(listeners) -> (world) -> {
for (WorldSaveCallback event : listeners) {
event.onWorldSaved(world);
}
}
);
void onWorldSaved(ServerWorld world);
}

View File

@ -1,44 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ProgressListener;
@Mixin(ServerWorld.class)
public abstract class MixinServerWorld {
@Inject(at = @At("RETURN"), method = "save")
public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) {
WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this);
}
}

View File

@ -1,58 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import java.util.concurrent.CompletableFuture;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.flowpowered.math.vector.Vector2i;
import com.mojang.datafixers.util.Either;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import net.minecraft.server.world.ChunkHolder;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
@Mixin(ThreadedAnvilChunkStorage.class)
public abstract class MixinThreadedAnvilChunkStorage {
@Accessor("world")
public abstract ServerWorld getWorld();
@Inject(at = @At("RETURN"), method = "method_20617")
public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<Chunk, ChunkHolder.Unloaded>>> ci) {
if (requiredStatus == ChunkStatus.FULL) {
ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z));
}
}
}

View File

@ -24,20 +24,11 @@
*/ */
package de.bluecolored.bluemap.fabric; package de.bluecolored.bluemap.fabric;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -51,6 +42,11 @@
import net.minecraft.util.math.Direction; import net.minecraft.util.math.Direction;
import net.minecraft.world.World; import net.minecraft.world.World;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
public class FabricEventForwarder { public class FabricEventForwarder {
private FabricMod mod; private FabricMod mod;
@ -59,9 +55,7 @@ public class FabricEventForwarder {
public FabricEventForwarder(FabricMod mod) { public FabricEventForwarder(FabricMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
WorldSaveCallback.EVENT.register(this::onWorldSave);
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
AttackBlockCallback.EVENT.register(this::onBlockAttack); AttackBlockCallback.EVENT.register(this::onBlockAttack);
UseBlockCallback.EVENT.register(this::onBlockUse); UseBlockCallback.EVENT.register(this::onBlockUse);
@ -105,24 +99,6 @@ public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) {
} }
} }
public synchronized void onWorldSave(ServerWorld world) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) {
if (this.mod.getServer() != server) return; if (this.mod.getServer() != server) return;

View File

@ -1,43 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import com.flowpowered.math.vector.Vector2i;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface ChunkFinalizeCallback {
Event<ChunkFinalizeCallback> EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class,
(listeners) -> (world, chunkPos) -> {
for (ChunkFinalizeCallback event : listeners) {
event.onChunkFinalized(world, chunkPos);
}
}
);
void onChunkFinalized(ServerWorld world, Vector2i chunkPos);
}

View File

@ -1,41 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface WorldSaveCallback {
Event<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class,
(listeners) -> (world) -> {
for (WorldSaveCallback event : listeners) {
event.onWorldSaved(world);
}
}
);
void onWorldSaved(ServerWorld world);
}

View File

@ -1,44 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ProgressListener;
@Mixin(ServerWorld.class)
public abstract class MixinServerWorld {
@Inject(at = @At("RETURN"), method = "save")
public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) {
WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this);
}
}

View File

@ -1,58 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import java.util.concurrent.CompletableFuture;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.flowpowered.math.vector.Vector2i;
import com.mojang.datafixers.util.Either;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import net.minecraft.server.world.ChunkHolder;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
@Mixin(ThreadedAnvilChunkStorage.class)
public abstract class MixinThreadedAnvilChunkStorage {
@Accessor("world")
public abstract ServerWorld getWorld();
@Inject(at = @At("RETURN"), method = "generateChunk")
public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<Chunk, ChunkHolder.Unloaded>>> ci) {
if (requiredStatus == ChunkStatus.FULL) {
ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z));
}
}
}

View File

@ -34,10 +34,8 @@
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback; import de.bluecolored.bluemap.fabric.events.PlayerJoinCallback;
import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback; import de.bluecolored.bluemap.fabric.events.PlayerLeaveCallback;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.minecraft.entity.player.PlayerEntity; import net.minecraft.entity.player.PlayerEntity;
@ -59,9 +57,7 @@ public class FabricEventForwarder {
public FabricEventForwarder(FabricMod mod) { public FabricEventForwarder(FabricMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
WorldSaveCallback.EVENT.register(this::onWorldSave);
ChunkFinalizeCallback.EVENT.register(this::onChunkFinalize);
AttackBlockCallback.EVENT.register(this::onBlockAttack); AttackBlockCallback.EVENT.register(this::onBlockAttack);
UseBlockCallback.EVENT.register(this::onBlockUse); UseBlockCallback.EVENT.register(this::onBlockUse);
@ -105,24 +101,6 @@ public synchronized void onBlockChange(ServerWorld world, BlockPos blockPos) {
} }
} }
public synchronized void onWorldSave(ServerWorld world) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(uuid);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onChunkFinalize(ServerWorld world, Vector2i chunkPos) {
try {
UUID uuid = mod.getUUIDForWorld(world);
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(uuid, chunkPos);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) { public synchronized void onPlayerJoin(MinecraftServer server, ServerPlayerEntity player) {
if (this.mod.getServer() != server) return; if (this.mod.getServer() != server) return;

View File

@ -1,43 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import com.flowpowered.math.vector.Vector2i;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface ChunkFinalizeCallback {
Event<ChunkFinalizeCallback> EVENT = EventFactory.createArrayBacked(ChunkFinalizeCallback.class,
(listeners) -> (world, chunkPos) -> {
for (ChunkFinalizeCallback event : listeners) {
event.onChunkFinalized(world, chunkPos);
}
}
);
void onChunkFinalized(ServerWorld world, Vector2i chunkPos);
}

View File

@ -1,41 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.events;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.minecraft.server.world.ServerWorld;
public interface WorldSaveCallback {
Event<WorldSaveCallback> EVENT = EventFactory.createArrayBacked(WorldSaveCallback.class,
(listeners) -> (world) -> {
for (WorldSaveCallback event : listeners) {
event.onWorldSaved(world);
}
}
);
void onWorldSaved(ServerWorld world);
}

View File

@ -1,44 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import de.bluecolored.bluemap.fabric.events.WorldSaveCallback;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.util.ProgressListener;
@Mixin(ServerWorld.class)
public abstract class MixinServerWorld {
@Inject(at = @At("RETURN"), method = "save")
public void save(ProgressListener progressListener, boolean flush, boolean bl, CallbackInfo ci) {
WorldSaveCallback.EVENT.invoker().onWorldSaved((ServerWorld) (Object) this);
}
}

View File

@ -1,58 +0,0 @@
/*
* This file is part of BlueMap, licensed under the MIT License (MIT).
*
* Copyright (c) Blue (Lukas Rieger) <https://bluecolored.de>
* Copyright (c) contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package de.bluecolored.bluemap.fabric.mixin;
import java.util.concurrent.CompletableFuture;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import com.flowpowered.math.vector.Vector2i;
import com.mojang.datafixers.util.Either;
import de.bluecolored.bluemap.fabric.events.ChunkFinalizeCallback;
import net.minecraft.server.world.ChunkHolder;
import net.minecraft.server.world.ServerWorld;
import net.minecraft.server.world.ThreadedAnvilChunkStorage;
import net.minecraft.world.chunk.Chunk;
import net.minecraft.world.chunk.ChunkStatus;
@Mixin(ThreadedAnvilChunkStorage.class)
public abstract class MixinThreadedAnvilChunkStorage {
@Accessor("world")
public abstract ServerWorld getWorld();
@Inject(at = @At("RETURN"), method = "upgradeChunk")
public void upgradeChunk(ChunkHolder holder, ChunkStatus requiredStatus, CallbackInfoReturnable<CompletableFuture<Either<Chunk, ChunkHolder.Unloaded>>> ci) {
if (requiredStatus == ChunkStatus.FULL) {
ChunkFinalizeCallback.EVENT.invoker().onChunkFinalized(getWorld(), new Vector2i(holder.getPos().x, holder.getPos().z));
}
}
}

View File

@ -24,48 +24,31 @@
*/ */
package de.bluecolored.bluemap.forge; package de.bluecolored.bluemap.forge;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.world.World;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
public class ForgeEventForwarder { public class ForgeEventForwarder {
private ForgeMod mod; private ForgeMod mod;
private Collection<ServerEventListener> eventListeners; private Collection<ServerEventListener> eventListeners;
private Deque<WorldChunk> loadChunkEvents;
private Thread loadChunkEventProcessor;
public ForgeEventForwarder(ForgeMod mod) { public ForgeEventForwarder(ForgeMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
loadChunkEvents = new ConcurrentLinkedDeque<>();
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
//see processLoadChunkEvents JavaDoc comment
loadChunkEventProcessor = new Thread(this::processLoadChunkEvents);
loadChunkEventProcessor.setDaemon(true);
loadChunkEventProcessor.start();
} }
public synchronized void addEventListener(ServerEventListener listener) { public synchronized void addEventListener(ServerEventListener listener) {
@ -103,34 +86,6 @@ private synchronized void onBlockChange(BlockEvent evt) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e); 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;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
*/
@SubscribeEvent @SubscribeEvent
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
@ -143,64 +98,5 @@ public synchronized void onPlayerLeave(PlayerLoggedOutEvent evt) {
UUID uuid = evt.getPlayer().getUniqueID(); UUID uuid = evt.getPlayer().getUniqueID();
for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid);
} }
@SubscribeEvent
public void onChunkLoad(ChunkEvent.Load evt) {
if (!(evt.getWorld() instanceof ServerWorld)) return;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
synchronized (loadChunkEvents) {
loadChunkEvents.add(new WorldChunk(world, chunk));
loadChunkEvents.notify();
}
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
/**
* This is a workaround for forge not providing a way to detect if chunks are newly generated:
* Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files.
* If it is a new chunk it will likely not be saved to the disk right away.
*/
private void processLoadChunkEvents() {
while (!Thread.interrupted()) {
WorldChunk worldChunk;
if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) {
try {
World world = mod.getPlugin().getWorld(worldChunk.world);
if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue;
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk);
} catch (RuntimeException e) {
Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e);
}
} else {
synchronized (loadChunkEvents) {
try {
loadChunkEvents.wait(10000);
} catch (InterruptedException e) {
break;
}
}
}
}
Thread.currentThread().interrupt();
}
private class WorldChunk {
final UUID world;
final Vector2i chunk;
public WorldChunk(UUID world, Vector2i chunk) {
this.world = world;
this.chunk = chunk;
}
}
} }

View File

@ -24,48 +24,31 @@
*/ */
package de.bluecolored.bluemap.forge; package de.bluecolored.bluemap.forge;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.UUID;
import java.util.concurrent.ConcurrentLinkedDeque;
import com.flowpowered.math.vector.Vector2i;
import com.flowpowered.math.vector.Vector3i; import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.core.logger.Logger; import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.world.World;
import net.minecraft.world.server.ServerWorld; import net.minecraft.world.server.ServerWorld;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedInEvent;
import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent; import net.minecraftforge.event.entity.player.PlayerEvent.PlayerLoggedOutEvent;
import net.minecraftforge.event.world.BlockEvent; import net.minecraftforge.event.world.BlockEvent;
import net.minecraftforge.event.world.ChunkDataEvent;
import net.minecraftforge.event.world.ChunkEvent;
import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.eventbus.api.SubscribeEvent;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.UUID;
public class ForgeEventForwarder { public class ForgeEventForwarder {
private ForgeMod mod; private ForgeMod mod;
private Collection<ServerEventListener> eventListeners; private Collection<ServerEventListener> eventListeners;
private Deque<WorldChunk> loadChunkEvents;
private Thread loadChunkEventProcessor;
public ForgeEventForwarder(ForgeMod mod) { public ForgeEventForwarder(ForgeMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
loadChunkEvents = new ConcurrentLinkedDeque<>();
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
//see processLoadChunkEvents JavaDoc comment
loadChunkEventProcessor = new Thread(this::processLoadChunkEvents);
loadChunkEventProcessor.setDaemon(true);
loadChunkEventProcessor.start();
} }
public synchronized void addEventListener(ServerEventListener listener) { public synchronized void addEventListener(ServerEventListener listener) {
@ -103,34 +86,6 @@ private synchronized void onBlockChange(BlockEvent evt) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e); 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;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
*/
@SubscribeEvent @SubscribeEvent
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
@ -143,64 +98,5 @@ public synchronized void onPlayerLeave(PlayerLoggedOutEvent evt) {
UUID uuid = evt.getPlayer().getUniqueID(); UUID uuid = evt.getPlayer().getUniqueID();
for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid);
} }
@SubscribeEvent
public void onChunkLoad(ChunkEvent.Load evt) {
if (!(evt.getWorld() instanceof ServerWorld)) return;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
synchronized (loadChunkEvents) {
loadChunkEvents.add(new WorldChunk(world, chunk));
loadChunkEvents.notify();
}
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
/**
* This is a workaround for forge not providing a way to detect if chunks are newly generated:
* Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files.
* If it is a new chunk it will likely not be saved to the disk right away.
*/
private void processLoadChunkEvents() {
while (!Thread.interrupted()) {
WorldChunk worldChunk;
if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) {
try {
World world = mod.getPlugin().getWorld(worldChunk.world);
if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue;
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk);
} catch (RuntimeException e) {
Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e);
}
} else {
synchronized (loadChunkEvents) {
try {
loadChunkEvents.wait(10000);
} catch (InterruptedException e) {
break;
}
}
}
}
Thread.currentThread().interrupt();
}
private class WorldChunk {
final UUID world;
final Vector2i chunk;
public WorldChunk(UUID world, Vector2i chunk) {
this.world = world;
this.chunk = chunk;
}
}
} }

View File

@ -51,21 +51,11 @@ public class ForgeEventForwarder {
private ForgeMod mod; private ForgeMod mod;
private Collection<ServerEventListener> eventListeners; private Collection<ServerEventListener> eventListeners;
private Deque<WorldChunk> loadChunkEvents;
private Thread loadChunkEventProcessor;
public ForgeEventForwarder(ForgeMod mod) { public ForgeEventForwarder(ForgeMod mod) {
this.mod = mod; this.mod = mod;
this.eventListeners = new ArrayList<>(1); this.eventListeners = new ArrayList<>(1);
loadChunkEvents = new ConcurrentLinkedDeque<>();
MinecraftForge.EVENT_BUS.register(this); MinecraftForge.EVENT_BUS.register(this);
//see processLoadChunkEvents JavaDoc comment
loadChunkEventProcessor = new Thread(this::processLoadChunkEvents);
loadChunkEventProcessor.setDaemon(true);
loadChunkEventProcessor.start();
} }
public synchronized void addEventListener(ServerEventListener listener) { public synchronized void addEventListener(ServerEventListener listener) {
@ -104,34 +94,6 @@ 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;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
for (ServerEventListener listener : eventListeners) listener.onWorldSaveToDisk(world);
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
*/
@SubscribeEvent @SubscribeEvent
public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) { public synchronized void onPlayerJoin(PlayerLoggedInEvent evt) {
UUID uuid = evt.getPlayer().getUniqueID(); UUID uuid = evt.getPlayer().getUniqueID();
@ -143,64 +105,5 @@ public synchronized void onPlayerLeave(PlayerLoggedOutEvent evt) {
UUID uuid = evt.getPlayer().getUniqueID(); UUID uuid = evt.getPlayer().getUniqueID();
for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid); for (ServerEventListener listener : eventListeners) listener.onPlayerLeave(uuid);
} }
@SubscribeEvent
public void onChunkLoad(ChunkEvent.Load evt) {
if (!(evt.getWorld() instanceof ServerWorld)) return;
try {
UUID world = mod.getUUIDForWorld((ServerWorld) evt.getWorld());
Vector2i chunk = new Vector2i(evt.getChunk().getPos().x, evt.getChunk().getPos().z);
synchronized (loadChunkEvents) {
loadChunkEvents.add(new WorldChunk(world, chunk));
loadChunkEvents.notify();
}
} catch (IOException e) {
Logger.global.noFloodError("Failed to get the UUID for a world!", e);
}
}
/**
* This is a workaround for forge not providing a way to detect if chunks are newly generated:
* Each time a chunk-load-event occurs, it is (asynchronously) tested if the chunk is already generated on the world files.
* If it is a new chunk it will likely not be saved to the disk right away.
*/
private void processLoadChunkEvents() {
while (!Thread.interrupted()) {
WorldChunk worldChunk;
if (mod.getPlugin().isLoaded() && (worldChunk = loadChunkEvents.poll()) != null) {
try {
World world = mod.getPlugin().getWorld(worldChunk.world);
if (world == null || world.isChunkGenerated(worldChunk.chunk)) continue;
for (ServerEventListener listener : eventListeners) listener.onChunkFinishedGeneration(worldChunk.world, worldChunk.chunk);
} catch (RuntimeException e) {
Logger.global.noFloodWarning("processLoadChunkEventsError", "Failed to test if a chunk is newly generated:" + e);
}
} else {
synchronized (loadChunkEvents) {
try {
loadChunkEvents.wait(10000);
} catch (InterruptedException e) {
break;
}
}
}
}
Thread.currentThread().interrupt();
}
private class WorldChunk {
final UUID world;
final Vector2i chunk;
public WorldChunk(UUID world, Vector2i chunk) {
this.world = world;
this.chunk = chunk;
}
}
} }

View File

@ -24,23 +24,13 @@
*/ */
package de.bluecolored.bluemap.bukkit; package de.bluecolored.bluemap.bukkit;
import java.io.File; import de.bluecolored.bluemap.common.plugin.Plugin;
import java.io.IOException; import de.bluecolored.bluemap.common.plugin.serverinterface.Player;
import java.lang.reflect.Field; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import java.util.ArrayList; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import java.util.Collection; import de.bluecolored.bluemap.core.MinecraftVersion;
import java.util.Collections; import de.bluecolored.bluemap.core.logger.Logger;
import java.util.List; import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bstats.bukkit.MetricsLite; import org.bstats.bukkit.MetricsLite;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
@ -53,13 +43,16 @@
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import de.bluecolored.bluemap.common.plugin.Plugin; import java.io.File;
import de.bluecolored.bluemap.common.plugin.serverinterface.Player; import java.io.IOException;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener; import java.lang.reflect.Field;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface; import java.util.*;
import de.bluecolored.bluemap.core.MinecraftVersion; import java.util.concurrent.CompletableFuture;
import de.bluecolored.bluemap.core.logger.Logger; import java.util.concurrent.ConcurrentHashMap;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class BukkitPlugin extends JavaPlugin implements ServerInterface, Listener { public class BukkitPlugin extends JavaPlugin implements ServerInterface, Listener {

View File

@ -24,35 +24,21 @@
*/ */
package de.bluecolored.bluemap.bukkit; package de.bluecolored.bluemap.bukkit;
import java.util.ArrayList; import com.flowpowered.math.vector.Vector3i;
import java.util.Collection; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import java.util.UUID; import de.bluecolored.bluemap.common.plugin.text.Text;
import org.bukkit.Chunk;
import org.bukkit.Location; import org.bukkit.Location;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority; import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.block.*;
import org.bukkit.event.block.BlockBurnEvent;
import org.bukkit.event.block.BlockExplodeEvent;
import org.bukkit.event.block.BlockFadeEvent;
import org.bukkit.event.block.BlockFertilizeEvent;
import org.bukkit.event.block.BlockFormEvent;
import org.bukkit.event.block.BlockGrowEvent;
import org.bukkit.event.block.BlockPlaceEvent;
import org.bukkit.event.block.BlockSpreadEvent;
import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.world.ChunkPopulateEvent;
import org.bukkit.event.world.ChunkUnloadEvent;
import com.flowpowered.math.vector.Vector2i; import java.util.ArrayList;
import com.flowpowered.math.vector.Vector3i; import java.util.Collection;
import java.util.UUID;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class EventForwarder implements Listener { public class EventForwarder implements Listener {
@ -70,19 +56,6 @@ public synchronized void removeAllListeners() {
listeners.clear(); listeners.clear();
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public synchronized void onChunkSaveToDisk(ChunkUnloadEvent evt) {
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) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public void onBlockChange(BlockPlaceEvent evt) { public void onBlockChange(BlockPlaceEvent evt) {
onBlockChange(evt.getBlock().getLocation()); onBlockChange(evt.getBlock().getLocation());
@ -133,14 +106,6 @@ private synchronized void onBlockChange(Location loc) {
Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ()); Vector3i pos = new Vector3i(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
for (ServerEventListener listener : listeners) listener.onBlockChange(world, pos); for (ServerEventListener listener : listeners) listener.onBlockChange(world, pos);
} }
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public synchronized void onChunkFinishedGeneration(ChunkPopulateEvent evt) {
Chunk chunk = evt.getChunk();
UUID world = chunk.getWorld().getUID();
Vector2i chunkPos = new Vector2i(chunk.getX(), chunk.getZ());
for (ServerEventListener listener : listeners) listener.onChunkFinishedGeneration(world, chunkPos);
}
@EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true) @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
public synchronized void onPlayerJoin(PlayerJoinEvent evt) { public synchronized void onPlayerJoin(PlayerJoinEvent evt) {

View File

@ -24,8 +24,8 @@
*/ */
package de.bluecolored.bluemap.sponge; package de.bluecolored.bluemap.sponge;
import java.util.Optional; import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.text.Text;
import org.spongepowered.api.block.BlockSnapshot; import org.spongepowered.api.block.BlockSnapshot;
import org.spongepowered.api.data.Transaction; import org.spongepowered.api.data.Transaction;
import org.spongepowered.api.event.Listener; import org.spongepowered.api.event.Listener;
@ -34,15 +34,9 @@
import org.spongepowered.api.event.filter.type.Exclude; import org.spongepowered.api.event.filter.type.Exclude;
import org.spongepowered.api.event.message.MessageChannelEvent; import org.spongepowered.api.event.message.MessageChannelEvent;
import org.spongepowered.api.event.network.ClientConnectionEvent; import org.spongepowered.api.event.network.ClientConnectionEvent;
import org.spongepowered.api.event.world.chunk.PopulateChunkEvent;
import org.spongepowered.api.event.world.chunk.SaveChunkEvent;
import org.spongepowered.api.world.Location; import org.spongepowered.api.world.Location;
import com.flowpowered.math.vector.Vector2i; import java.util.Optional;
import com.flowpowered.math.vector.Vector3i;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerEventListener;
import de.bluecolored.bluemap.common.plugin.text.Text;
public class EventForwarder { public class EventForwarder {
@ -51,18 +45,6 @@ public class EventForwarder {
public EventForwarder(ServerEventListener listener) { public EventForwarder(ServerEventListener listener) {
this.listener = 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) @Listener(order = Order.POST)
@Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class}) @Exclude({ChangeBlockEvent.Post.class, ChangeBlockEvent.Pre.class, ChangeBlockEvent.Modify.class})
@ -71,18 +53,10 @@ public void onBlockChange(ChangeBlockEvent evt) {
if(!tr.isValid()) continue; if(!tr.isValid()) continue;
Optional<Location<org.spongepowered.api.world.World>> ow = tr.getFinal().getLocation(); Optional<Location<org.spongepowered.api.world.World>> ow = tr.getFinal().getLocation();
if (ow.isPresent()) { ow.ifPresent(worldLocation -> listener.onBlockChange(worldLocation.getExtent().getUniqueId(), worldLocation.getPosition().toInt()));
listener.onBlockChange(ow.get().getExtent().getUniqueId(), ow.get().getPosition().toInt());
}
} }
} }
@Listener(order = Order.POST)
public void onChunkFinishedGeneration(PopulateChunkEvent.Post evt) {
Vector3i chunkPos = evt.getTargetChunk().getPosition();
listener.onChunkFinishedGeneration(evt.getTargetChunk().getWorld().getUniqueId(), new Vector2i(chunkPos.getX(), chunkPos.getZ()));
}
@Listener(order = Order.POST) @Listener(order = Order.POST)
public void onPlayerJoin(ClientConnectionEvent.Join evt) { public void onPlayerJoin(ClientConnectionEvent.Join evt) {
listener.onPlayerJoin(evt.getTargetEntity().getUniqueId()); listener.onPlayerJoin(evt.getTargetEntity().getUniqueId());