Reimplement render command and add update command

This commit is contained in:
Blue (Lukas Rieger) 2021-05-08 23:47:13 +02:00
parent 412e27682f
commit d2d6071446
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
11 changed files with 426 additions and 140 deletions

View File

@ -38,7 +38,7 @@ public InterruptableReentrantLock(boolean fair) {
}
/**
* Aquires the lock and interrupts the currently holding thread if there is any.
* Acquires the lock and interrupts the currently holding thread if there is any.
*/
public void interruptAndLock() {
while (!tryLock()) {

View File

@ -32,8 +32,7 @@
import de.bluecolored.bluemap.common.live.LiveAPIRequestHandler;
import de.bluecolored.bluemap.common.plugin.serverinterface.ServerInterface;
import de.bluecolored.bluemap.common.plugin.skins.PlayerSkinUpdater;
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
import de.bluecolored.bluemap.common.rendermanager.*;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.config.CoreConfig;
@ -79,7 +78,7 @@ public class Plugin {
private RenderManager renderManager;
private WebServer webServer;
private final Timer daemonTimer;
private Timer daemonTimer;
private TimerTask saveTask;
private TimerTask metricsTask;
@ -93,8 +92,6 @@ public Plugin(MinecraftVersion minecraftVersion, String implementationType, Serv
this.minecraftVersion = minecraftVersion;
this.implementationType = implementationType.toLowerCase();
this.serverInterface = serverInterface;
this.daemonTimer = new Timer("BlueMap-Plugin-Daemon-Timer", true);
}
public void load() throws IOException, ParseResourceException {
@ -120,7 +117,7 @@ public void load() throws IOException, ParseResourceException {
true,
true
));
//create and start webserver
if (webServerConfig.isWebserverEnabled()) {
FileUtils.mkDirs(webServerConfig.getWebRoot());
@ -161,10 +158,28 @@ public void load() throws IOException, ParseResourceException {
//warn if no maps are configured
if (maps.isEmpty()) {
Logger.global.logWarning("There are no valid maps configured, please check your render-config! Disabling BlueMap...");
unload();
return;
}
//initialize render manager
renderManager = new RenderManager();
//update all maps
for (BmMap map : maps.values()) {
Collection<Vector2i> regions = map.getWorld().listRegions();
List<WorldRegionRenderTask> mapTasks = new ArrayList<>(regions.size());
for (Vector2i region : regions)
mapTasks.add(new WorldRegionRenderTask(map, region));
mapTasks.sort(WorldRegionRenderTask::compare);
CombinedRenderTask<WorldRegionRenderTask> mapUpdateTask = new CombinedRenderTask<>("Update map '" + map.getId() + "'", mapTasks);
renderManager.scheduleRenderTask(mapUpdateTask);
}
//start render-manager
renderManager.start(coreConfig.getRenderThreadCount());
//update webapp and settings
@ -180,6 +195,9 @@ public void load() throws IOException, ParseResourceException {
serverInterface.registerListener(skinUpdater);
}
//init timer
daemonTimer = new Timer("BlueMap-Plugin-Daemon-Timer", true);
//periodically save
saveTask = new TimerTask() {
@Override
@ -193,7 +211,7 @@ public void run() {
}
};
daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2));
//metrics
metricsTask = new TimerTask() {
@Override
@ -203,12 +221,6 @@ public void run() {
}
};
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
loaded = true;
//enable api
this.api = new BlueMapAPIImpl(this);
this.api.register();
//watch map-changes
this.regionFileWatchServices = new ArrayList<>();
@ -222,13 +234,12 @@ public void run() {
}
}
//update all maps
for (BmMap map : maps.values()) {
for (Vector2i region : map.getWorld().listRegions()){
renderManager.scheduleRenderTask(new WorldRegionRenderTask(map, region));
}
}
//enable api
this.api = new BlueMapAPIImpl(this);
this.api.register();
//done
loaded = true;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
@ -249,22 +260,31 @@ public void unload() {
//unregister listeners
serverInterface.unregisterAllListeners();
skinUpdater = null;
//stop scheduled threads
if (metricsTask != null) metricsTask.cancel();
metricsTask = null;
if (saveTask != null) saveTask.cancel();
saveTask = null;
if (daemonTimer != null) daemonTimer.cancel();
daemonTimer = null;
// stop file-watchers
//stop file-watchers
if (regionFileWatchServices != null) {
for (RegionFileWatchService watcher : regionFileWatchServices) {
watcher.close();
}
regionFileWatchServices.clear();
}
regionFileWatchServices = null;
//stop services
if (renderManager != null) renderManager.stop();
renderManager = null;
if (webServer != null) webServer.close();
webServer = null;
//save renders
if (maps != null) {
@ -277,13 +297,14 @@ public void unload() {
blueMap = null;
worlds = null;
maps = null;
renderManager = null;
webServer = null;
coreConfig = null;
renderConfig = null;
webServerConfig = null;
pluginConfig = null;
//done
loaded = false;
}
} finally {
loadingLock.unlock();
@ -334,7 +355,7 @@ public Collection<BmMap> getMapTypes(){
public RenderManager getRenderManager() {
return renderManager;
}
public WebServer getWebServer() {
return webServer;
}

View File

@ -24,9 +24,6 @@
*/
package de.bluecolored.bluemap.common.plugin.commands;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
@ -34,6 +31,9 @@
import com.mojang.brigadier.suggestion.Suggestions;
import com.mojang.brigadier.suggestion.SuggestionsBuilder;
import java.util.Collection;
import java.util.concurrent.CompletableFuture;
public abstract class AbstractSuggestionProvider<S> implements SuggestionProvider<S> {
@Override

View File

@ -24,13 +24,16 @@
*/
package de.bluecolored.bluemap.common.plugin.commands;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.text.Text;
import de.bluecolored.bluemap.common.plugin.text.TextColor;
import de.bluecolored.bluemap.common.rendermanager.RenderManager;
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.World;
import org.apache.commons.lang3.time.DurationFormatUtils;
import java.util.ArrayList;
import java.util.List;
@ -48,25 +51,61 @@ public List<Text> createStatusMessage(){
List<Text> lines = new ArrayList<>();
RenderManager renderer = plugin.getRenderManager();
List<RenderTask> tasks = renderer.getScheduledRenderTasks();
lines.add(Text.of(TextColor.BLUE, "BlueMap - Status:"));
if (renderer.isRunning()) {
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, "!"));
Text status;
if (tasks.isEmpty()) {
status = Text.of(TextColor.GRAY, "idle");
} else {
status = Text.of(TextColor.GREEN, "running");
}
status.setHoverText(Text.of("click to stop rendering"));
status.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap stop");
lines.add(Text.of(TextColor.WHITE, " Render-Threads are ", status, TextColor.WHITE, "!"));
if (!tasks.isEmpty()) {
lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):"));
for (int i = 0; i < tasks.size(); i++) {
if (i >= 10){
lines.add(Text.of(TextColor.GRAY, "..."));
break;
}
RenderTask task = tasks.get(i);
lines.add(Text.of(TextColor.GRAY, " - ", TextColor.GOLD, task.getDescription()));
if (i == 0) {
lines.add(Text.of(TextColor.GRAY, " Progress: ", TextColor.WHITE,
(Math.round(task.estimateProgress() * 10000) / 100.0) + "%"));
lines.add(Text.of(TextColor.GRAY, " ETA: ", TextColor.WHITE, DurationFormatUtils.formatDuration(renderer.estimateCurrentRenderTaskTimeRemaining(), "HH:mm:ss")));
}
}
}
} 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"),
Text.of(TextColor.RED, "stopped")
.setHoverText(Text.of("click to start rendering"))
.setClickAction(Text.ClickAction.RUN_COMMAND, "/bluemap start"),
TextColor.GRAY, "!"));
}
List<RenderTask> tasks = renderer.getScheduledRenderTasks();
lines.add(Text.of(TextColor.WHITE, " Scheduled tasks: ", TextColor.GOLD, tasks.size()));
if (!tasks.isEmpty()) {
lines.add(Text.of(TextColor.WHITE, " Queued Tasks (" + tasks.size() + "):"));
for (int i = 0; i < tasks.size(); i++) {
if (i >= 10){
lines.add(Text.of(TextColor.GRAY, "..."));
break;
}
RenderTask task = tasks.get(i);
lines.add(Text.of(TextColor.GRAY, " - ", TextColor.WHITE, task.getDescription()));
}
}
}
return lines;
}
@ -88,4 +127,25 @@ public Text mapHelperHover() {
return Text.of("map").setHoverText(Text.of(TextColor.WHITE, "Available maps: \n", TextColor.GRAY, joiner.toString()));
}
public List<Vector2i> getRegions(World world, Vector2i center, int radius) {
if (center == null || radius < 0) return new ArrayList<>(world.listRegions());
List<Vector2i> regions = new ArrayList<>();
Grid regionGrid = world.getRegionGrid();
Vector2i halfCell = regionGrid.getGridSize().div(2);
int increasedRadiusSquared = (int) Math.pow(radius + Math.ceil(halfCell.length()), 2);
for (Vector2i region : world.listRegions()) {
Vector2i min = regionGrid.getCellMin(region);
Vector2i regionCenter = min.add(halfCell);
if (regionCenter.distanceSquared(center) <= increasedRadiusSquared)
regions.add(region);
}
return regions;
}
}

View File

@ -28,11 +28,13 @@
import com.flowpowered.math.vector.Vector3d;
import com.flowpowered.math.vector.Vector3i;
import com.google.common.collect.Lists;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.arguments.ArgumentType;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
@ -47,12 +49,15 @@
import de.bluecolored.bluemap.common.plugin.text.Text;
import de.bluecolored.bluemap.common.plugin.text.TextColor;
import de.bluecolored.bluemap.common.plugin.text.TextFormat;
import de.bluecolored.bluemap.common.rendermanager.CombinedRenderTask;
import de.bluecolored.bluemap.common.rendermanager.WorldRegionRenderTask;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.mca.MCAChunk;
import de.bluecolored.bluemap.core.map.MapRenderState;
import de.bluecolored.bluemap.core.mca.ChunkAnvil112;
import de.bluecolored.bluemap.core.mca.MCAChunk;
import de.bluecolored.bluemap.core.mca.MCAWorld;
import de.bluecolored.bluemap.core.resourcepack.ParseResourceException;
import de.bluecolored.bluemap.core.world.Block;
@ -61,6 +66,8 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
@ -137,45 +144,37 @@ public void init() {
.build();
LiteralCommandNode<S> pauseCommand =
literal("pause")
.requires(requirements("bluemap.pause"))
.executes(this::pauseCommand)
literal("stop")
.requires(requirements("bluemap.stop"))
.executes(this::stopCommand)
.build();
LiteralCommandNode<S> resumeCommand =
literal("resume")
.requires(requirements("bluemap.resume"))
.executes(this::resumeCommand)
literal("start")
.requires(requirements("bluemap.start"))
.executes(this::startCommand)
.build();
LiteralCommandNode<S> renderCommand =
literal("render")
.requires(requirements("bluemap.render"))
.executes(this::renderCommand) // /bluemap render
addRenderArguments(
literal("render")
.requires(requirements("bluemap.render")),
this::renderCommand
).build();
.then(argument("radius", IntegerArgumentType.integer())
.executes(this::renderCommand)) // /bluemap render <radius>
.then(argument("x", DoubleArgumentType.doubleArg())
.then(argument("z", DoubleArgumentType.doubleArg())
.then(argument("radius", IntegerArgumentType.integer())
.executes(this::renderCommand)))) // /bluemap render <x> <z> <radius>
.then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin))
.executes(this::renderCommand) // /bluemap render <world|map>
.then(argument("x", DoubleArgumentType.doubleArg())
.then(argument("z", DoubleArgumentType.doubleArg())
.then(argument("radius", IntegerArgumentType.integer())
.executes(this::renderCommand))))) // /bluemap render <world|map> <x> <z> <radius>
.build();
LiteralCommandNode<S> updateCommand =
addRenderArguments(
literal("update")
.requires(requirements("bluemap.update")),
this::updateCommand
).build();
LiteralCommandNode<S> purgeCommand =
literal("purge")
.requires(requirements("bluemap.render"))
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
.executes(this::purgeCommand))
.build();
.requires(requirements("bluemap.render"))
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
.executes(this::purgeCommand))
.build();
LiteralCommandNode<S> worldsCommand =
literal("worlds")
@ -225,7 +224,8 @@ public void init() {
baseCommand.addChild(debugCommand);
baseCommand.addChild(pauseCommand);
baseCommand.addChild(resumeCommand);
//baseCommand.addChild(renderCommand);
baseCommand.addChild(renderCommand);
baseCommand.addChild(updateCommand);
baseCommand.addChild(purgeCommand);
baseCommand.addChild(worldsCommand);
baseCommand.addChild(mapsCommand);
@ -233,6 +233,27 @@ public void init() {
markerCommand.addChild(createMarkerCommand);
markerCommand.addChild(removeMarkerCommand);
}
private <B extends ArgumentBuilder<S, B>> B addRenderArguments(B builder, Command<S> command) {
return builder
.executes(command) // /bluemap render
.then(argument("radius", IntegerArgumentType.integer())
.executes(command)) // /bluemap render <radius>
.then(argument("x", DoubleArgumentType.doubleArg())
.then(argument("z", DoubleArgumentType.doubleArg())
.then(argument("radius", IntegerArgumentType.integer())
.executes(command)))) // /bluemap render <x> <z> <radius>
.then(argument("world|map", StringArgumentType.string()).suggests(new WorldOrMapSuggestionProvider<>(plugin))
.executes(command) // /bluemap render <world|map>
.then(argument("x", DoubleArgumentType.doubleArg())
.then(argument("z", DoubleArgumentType.doubleArg())
.then(argument("radius", IntegerArgumentType.integer())
.executes(command))))); // /bluemap render <world|map> <x> <z> <radius>
}
private Predicate<S> requirements(String permission){
return s -> {
@ -498,58 +519,66 @@ public int debugBlockCommand(CommandContext<S> context) {
return 1;
}
public int pauseCommand(CommandContext<S> context) {
public int stopCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
if (plugin.getRenderManager().isRunning()) {
plugin.getRenderManager().stop();
source.sendMessage(Text.of(TextColor.GREEN, "BlueMap rendering paused!"));
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads stopped!"));
return 1;
} else {
source.sendMessage(Text.of(TextColor.RED, "BlueMap rendering are already paused!"));
source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already stopped!"));
return 0;
}
}
public int resumeCommand(CommandContext<S> context) {
public int startCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
if (!plugin.getRenderManager().isRunning()) {
plugin.getRenderManager().start(plugin.getCoreConfig().getRenderThreadCount());
source.sendMessage(Text.of(TextColor.GREEN, "BlueMap renders resumed!"));
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!"));
return 1;
} else {
source.sendMessage(Text.of(TextColor.RED, "BlueMap renders are already running!"));
source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already running!"));
return 0;
}
}
public int renderCommand(CommandContext<S> context) {
return updateCommand(context, true);
}
public int updateCommand(CommandContext<S> context) {
return updateCommand(context, false);
}
public int updateCommand(CommandContext<S> context, boolean force) {
final CommandSource source = commandSourceInterface.apply(context.getSource());
// parse world/map argument
Optional<String> worldOrMap = getOptionalArgument(context, "world|map", String.class);
final World world;
final BmMap map;
final World worldToRender;
final BmMap mapToRender;
if (worldOrMap.isPresent()) {
world = parseWorld(worldOrMap.get()).orElse(null);
worldToRender = parseWorld(worldOrMap.get()).orElse(null);
if (world == null) {
map = parseMap(worldOrMap.get()).orElse(null);
if (worldToRender == null) {
mapToRender = parseMap(worldOrMap.get()).orElse(null);
if (map == null) {
if (mapToRender == null) {
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.worldHelperHover(), " or ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, worldOrMap.get()));
return 0;
}
} else {
map = null;
mapToRender = null;
}
} else {
world = source.getWorld().orElse(null);
map = null;
worldToRender = source.getWorld().orElse(null);
mapToRender = null;
if (world == null) {
if (worldToRender == null) {
source.sendMessage(Text.of(TextColor.RED, "Can't detect a world from this command-source, you'll have to define a world or a map to render!").setHoverText(Text.of(TextColor.GRAY, "/bluemap render <world|map>")));
return 0;
}
@ -580,13 +609,45 @@ public int renderCommand(CommandContext<S> context) {
// execute render
new Thread(() -> {
try {
if (world != null) {
plugin.getServerInterface().persistWorldChanges(world.getUUID());
//TODO: helper.createWorldRenderTask(source, world, center, radius);
List<BmMap> maps = new ArrayList<>();
World world = worldToRender;
if (worldToRender != null) {
plugin.getServerInterface().persistWorldChanges(worldToRender.getUUID());
for (BmMap map : plugin.getMapTypes()) {
if (map.getWorld().equals(worldToRender)) maps.add(map);
}
} else {
plugin.getServerInterface().persistWorldChanges(map.getWorld().getUUID());
//TODO: helper.createMapRenderTask(source, map, center, radius);
plugin.getServerInterface().persistWorldChanges(mapToRender.getWorld().getUUID());
maps.add(mapToRender);
world = mapToRender.getWorld();
}
String taskType = "Update";
List<Vector2i> regions = helper.getRegions(world, center, radius);
if (force) {
taskType = "Render";
for (BmMap map : maps) {
MapRenderState state = map.getRenderState();
regions.forEach(region -> state.setRenderTime(region, -1));
}
}
if (center != null) {
taskType = "Radius-" + taskType;
}
for (BmMap map : maps) {
List<WorldRegionRenderTask> tasks = new ArrayList<>(regions.size());
regions.forEach(region -> tasks.add(new WorldRegionRenderTask(map, region)));
tasks.sort(WorldRegionRenderTask::compare);
plugin.getRenderManager().scheduleRenderTask(new CombinedRenderTask<>(
taskType + " map '" + map.getId() + "'",
tasks
));
source.sendMessage(Text.of(TextColor.GREEN, "Created new " + taskType + "-Task for map '" + map.getId() + "' ", TextColor.GRAY, "(" + regions.size() + " regions, ~" + regions.size() * 1024L + " chunks)"));
}
source.sendMessage(Text.of(TextColor.GREEN, "Use ", TextColor.GRAY, "/bluemap", TextColor.GREEN, " to see the progress."));
} 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..."));
Logger.global.logError("Unexpected exception trying to save the world!", ex);

View File

@ -24,18 +24,20 @@
*/
package de.bluecolored.bluemap.common.rendermanager;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.*;
public class CombinedRenderTask<T extends RenderTask> implements RenderTask {
private final String description;
private final List<T> tasks;
private final Set<T> taskSet;
private int currentTaskIndex;
public CombinedRenderTask(Collection<T> tasks) {
this.tasks = new ArrayList<>();
this.tasks.addAll(tasks);
public CombinedRenderTask(String description, Collection<T> tasks) {
this.description = description;
this.tasks = Collections.unmodifiableList(new ArrayList<>(tasks));
this.taskSet = Collections.unmodifiableSet(new HashSet<>(tasks));
this.currentTaskIndex = 0;
}
@ -43,7 +45,7 @@ public CombinedRenderTask(Collection<T> tasks) {
public void doWork() throws Exception {
T task;
synchronized (this.tasks) {
synchronized (this) {
if (!hasMoreWork()) return;
task = this.tasks.get(this.currentTaskIndex);
@ -57,20 +59,18 @@ public void doWork() throws Exception {
}
@Override
public boolean hasMoreWork() {
public synchronized boolean hasMoreWork() {
return this.currentTaskIndex < this.tasks.size();
}
@Override
public double estimateProgress() {
synchronized (this.tasks) {
if (!hasMoreWork()) return 1;
public synchronized double estimateProgress() {
if (!hasMoreWork()) return 1;
double total = currentTaskIndex;
total += this.tasks.get(this.currentTaskIndex).estimateProgress();
double total = currentTaskIndex;
total += this.tasks.get(this.currentTaskIndex).estimateProgress();
return total / tasks.size();
}
return total / tasks.size();
}
@Override
@ -78,4 +78,22 @@ public void cancel() {
for (T task : tasks) task.cancel();
}
@Override
public boolean contains(RenderTask task) {
if (this.equals(task)) return true;
if (taskSet.contains(task)) return true;
for (RenderTask subTask : this.tasks) {
if (subTask.contains(task)) return true;
}
return false;
}
@Override
public String getDescription() {
//return description + " (" + (this.currentTaskIndex + 1) + "/" + tasks.size() + ")";
return description;
}
}

View File

@ -36,6 +36,9 @@ public class RenderManager {
private final int id;
private volatile boolean running;
private volatile long currentTaskStartTime;
private volatile double currentTaskStartProgress;
private final AtomicInteger nextWorkerThreadIndex;
private final Collection<WorkerThread> workerThreads;
private final AtomicInteger busyCount;
@ -53,6 +56,9 @@ public RenderManager() {
this.renderTasks = new LinkedList<>();
this.renderTaskSet = new HashSet<>();
this.currentTaskStartTime = System.currentTimeMillis();
this.currentTaskStartProgress = -1;
}
public void start(int threadCount) throws IllegalStateException {
@ -65,6 +71,9 @@ public void start(int threadCount) throws IllegalStateException {
this.running = true;
this.currentTaskStartTime = System.currentTimeMillis();
this.currentTaskStartProgress = -1;
for (int i = 0; i < threadCount; i++) {
WorkerThread worker = new WorkerThread();
this.workerThreads.add(worker);
@ -106,27 +115,38 @@ public void awaitShutdown() throws InterruptedException {
public boolean scheduleRenderTask(RenderTask task) {
synchronized (this.renderTasks) {
if (renderTaskSet.add(task)) {
renderTasks.addLast(task);
renderTasks.notifyAll();
return true;
} else {
return false;
if (containsRenderTask(task)) return false;
renderTaskSet.add(task);
renderTasks.addLast(task);
renderTasks.notifyAll();
return true;
}
}
public int scheduleRenderTasks(RenderTask... tasks) {
return scheduleRenderTasks(Arrays.asList(tasks));
}
public int scheduleRenderTasks(Collection<RenderTask> tasks) {
synchronized (this.renderTasks) {
int count = 0;
for (RenderTask task : tasks) {
if (scheduleRenderTask(task)) count++;
}
return count;
}
}
public boolean scheduleRenderTaskNext(RenderTask task) {
synchronized (this.renderTasks) {
if (renderTasks.size() <= 1) return scheduleRenderTask(task);
if (containsRenderTask(task)) return false;
if (renderTaskSet.add(task)) {
renderTasks.add(1, task);
renderTasks.notifyAll();
return true;
} else {
return false;
}
renderTaskSet.add(task);
renderTasks.add(1, task);
renderTasks.notifyAll();
return true;
}
}
@ -140,7 +160,7 @@ public void reorderRenderTasks(Comparator<RenderTask> taskComparator) {
}
}
public boolean removeTask(RenderTask task) {
public boolean removeRenderTask(RenderTask task) {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return false;
@ -156,7 +176,7 @@ public boolean removeTask(RenderTask task) {
}
}
public void removeAllTasks() {
public void removeAllRenderTasks() {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return;
@ -167,8 +187,51 @@ public void removeAllTasks() {
}
}
public long estimateCurrentRenderTaskTimeRemaining() {
synchronized (this.renderTasks) {
long now = System.currentTimeMillis();
double progress = getCurrentRenderTask().estimateProgress();
long deltaTime = now - currentTaskStartTime;
double deltaProgress = progress - currentTaskStartProgress;
double estimatedTotalDuration = deltaTime / deltaProgress;
double estimatedRemainingDuration = (1 - progress) * estimatedTotalDuration;
return (long) estimatedRemainingDuration;
}
}
public RenderTask getCurrentRenderTask() {
synchronized (this.renderTasks) {
return this.renderTasks.getFirst();
}
}
public List<RenderTask> getScheduledRenderTasks() {
return Collections.unmodifiableList(renderTasks);
synchronized (this.renderTasks) {
return new ArrayList<>(this.renderTasks);
}
}
public boolean containsRenderTask(RenderTask task) {
synchronized (this.renderTasks) {
// checking all scheduled renderTasks except the first one, since that is already being processed
// quick check
if (renderTaskSet.contains(task) && !getCurrentRenderTask().equals(task)) return true;
// iterate over all (skipping the first) using the "contains" method
Iterator<RenderTask> iterator = renderTasks.iterator();
if (!iterator.hasNext()) return false;
iterator.next(); // skip first
while(iterator.hasNext()) {
if (iterator.next().contains(task)) return true;
}
return false;
}
}
public int getWorkerThreadCount() {
@ -184,12 +247,21 @@ private void doWork() throws Exception {
task = this.renderTasks.getFirst();
if (this.currentTaskStartProgress < 0) {
this.currentTaskStartTime = System.currentTimeMillis();
this.currentTaskStartProgress = task.estimateProgress();
}
// 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());
this.renderTasks.notifyAll();
this.currentTaskStartTime = System.currentTimeMillis();
this.currentTaskStartProgress = -1;
busyCount.set(0);
} else {
this.renderTasks.wait(10000);

View File

@ -46,4 +46,13 @@ default double estimateProgress() {
*/
void cancel();
/**
* Checks if the given task is somehow included with this task
*/
default boolean contains(RenderTask task) {
return equals(task);
}
String getDescription();
}

View File

@ -25,9 +25,11 @@
package de.bluecolored.bluemap.common.rendermanager;
import com.flowpowered.math.vector.Vector2i;
import de.bluecolored.bluemap.core.logger.Logger;
import de.bluecolored.bluemap.core.map.BmMap;
import de.bluecolored.bluemap.core.world.Grid;
import de.bluecolored.bluemap.core.world.Region;
import de.bluecolored.bluemap.core.world.World;
import java.util.Collection;
import java.util.TreeSet;
@ -66,6 +68,8 @@ private synchronized void init() {
tiles = new TreeSet<>(WorldRegionRenderTask::tileComparator);
startTime = System.currentTimeMillis();
//Logger.global.logInfo("Starting: " + worldRegion);
long changesSince = 0;
if (!force) changesSince = map.getRenderState().getRenderTime(worldRegion);
@ -106,6 +110,7 @@ public void doWork() {
this.atWork++;
}
//Logger.global.logInfo("Working on " + worldRegion + " - Tile " + tile);
map.renderTile(tile); // <- actual work
synchronized (this) {
@ -119,10 +124,12 @@ public void doWork() {
private void complete() {
map.getRenderState().setRenderTime(worldRegion, startTime);
//Logger.global.logInfo("Done with: " + worldRegion);
}
@Override
public boolean hasMoreWork() {
public synchronized boolean hasMoreWork() {
return !cancelled && (tiles == null || !tiles.isEmpty());
}
@ -144,6 +151,27 @@ public void cancel() {
}
}
public BmMap getMap() {
return map;
}
public Vector2i getWorldRegion() {
return worldRegion;
}
public boolean isForce() {
return force;
}
@Override
public String getDescription() {
if (force) {
return "Render region " + getWorldRegion() + " for map '" + map.getId() + "'";
} else {
return "Update region " + getWorldRegion() + " for map '" + map.getId() + "'";
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -164,4 +192,27 @@ private static int tileComparator(Vector2i v1, Vector2i v2) {
return v2.getY() - v1.getY();
}
public static int compare(WorldRegionRenderTask task1, WorldRegionRenderTask task2) {
if (task1.equals(task2)) return 0;
int comp = task1.getMap().getId().compareTo(task2.getMap().getId());
if (comp != 0) return comp;
//sort based on the worlds spawn-point
World world = task1.getMap().getWorld();
Vector2i spawnPoint = world.getSpawnPoint().toVector2(true);
Grid regionGrid = world.getRegionGrid();
Vector2i spawnRegion = regionGrid.getCell(spawnPoint);
Vector2i task1Rel = task1.getWorldRegion().sub(spawnRegion);
Vector2i task2Rel = task2.getWorldRegion().sub(spawnRegion);
comp = tileComparator(task1Rel, task2Rel);
if (comp != 0) return comp;
if (task1.isForce() == task2.isForce()) return 0;
if (task1.isForce()) return -1;
return 1;
}
}

View File

@ -136,15 +136,7 @@ public boolean isClosed(){
}
public void close() throws IOException {
try {
in.close();
} finally {
try {
out.close();
} finally {
connection.close();
}
}
connection.close();
}
public static class ConnectionClosedException extends IOException {

View File

@ -86,11 +86,13 @@ public void renderMaps(BlueMapService blueMap, boolean watch, boolean forceRende
}
//update all maps
for (BmMap map : maps.values()) {
for (Vector2i region : map.getWorld().listRegions()){
renderManager.scheduleRenderTask(new WorldRegionRenderTask(map, region, forceRender));
}
}
List<WorldRegionRenderTask> tasks = new ArrayList<>();
for (BmMap map : maps.values())
for (Vector2i region : map.getWorld().listRegions())
tasks.add(new WorldRegionRenderTask(map, region, forceRender));
tasks.sort(WorldRegionRenderTask::compare);
tasks.forEach(renderManager::scheduleRenderTask);
int totalRegions = renderManager.getScheduledRenderTasks().size();
Logger.global.logInfo("Start " + (forceRender ? "rendering " : "updating ") + maps.size() + " maps (" + totalRegions + " regions, ~" + totalRegions * 1024L + " chunks)...");
@ -115,7 +117,7 @@ public void run() {
long etr = (long) ((elapsedTime / progress) * (1 - progress));
String etrDurationString = DurationFormatUtils.formatDuration(etr, "HH:mm:ss");
Logger.global.logInfo("Rendering: " + (Math.round(progress * 100000) / 1000.0) + "% (ETR: " + etrDurationString + ")");
Logger.global.logInfo("Rendering: " + (Math.round(progress * 100000) / 1000.0) + "% (ETA: " + etrDurationString + ")");
}
};
timer.scheduleAtFixedRate(updateInfoTask, TimeUnit.SECONDS.toMillis(10), TimeUnit.SECONDS.toMillis(10));