Add abillity to freeze maps (they wont update) and persist stopped render-threads

This commit is contained in:
Blue (Lukas Rieger) 2021-05-17 00:24:27 +02:00
parent 113d7f3feb
commit e90f329ad8
No known key found for this signature in database
GPG Key ID: 904C4995F9E1F800
4 changed files with 251 additions and 39 deletions

View File

@ -47,6 +47,8 @@
import de.bluecolored.bluemap.core.webserver.HttpRequestHandler;
import de.bluecolored.bluemap.core.webserver.WebServer;
import de.bluecolored.bluemap.core.world.World;
import org.spongepowered.configurate.gson.GsonConfigurationLoader;
import org.spongepowered.configurate.serialize.SerializationException;
import java.io.File;
import java.io.IOException;
@ -72,6 +74,8 @@ public class Plugin {
private WebServerConfig webServerConfig;
private PluginConfig pluginConfig;
private PluginStatus pluginStatus;
private Map<UUID, World> worlds;
private Map<String, BmMap> maps;
@ -82,7 +86,7 @@ public class Plugin {
private TimerTask saveTask;
private TimerTask metricsTask;
private Collection<RegionFileWatchService> regionFileWatchServices;
private Map<String, RegionFileWatchService> regionFileWatchServices;
private PlayerSkinUpdater skinUpdater;
@ -118,6 +122,17 @@ public void load() throws IOException, ParseResourceException {
true
));
//load plugin status
try {
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.file(new File(getCoreConfig().getDataFolder(), "pluginStatus.json"))
.build();
pluginStatus = loader.load().get(PluginStatus.class);
} catch (SerializationException ex) {
Logger.global.logWarning("Failed to load pluginStatus.json (invalid format), creating a new one...");
pluginStatus = new PluginStatus();
}
//create and start webserver
if (webServerConfig.isWebserverEnabled()) {
FileUtils.mkDirs(webServerConfig.getWebRoot());
@ -168,11 +183,17 @@ public void load() throws IOException, ParseResourceException {
//update all maps
for (BmMap map : maps.values()) {
renderManager.scheduleRenderTask(new MapUpdateTask(map));
if (pluginStatus.getMapStatus(map).isUpdateEnabled()) {
renderManager.scheduleRenderTask(new MapUpdateTask(map));
}
}
//start render-manager
renderManager.start(coreConfig.getRenderThreadCount());
if (pluginStatus.isRenderThreadsEnabled()) {
renderManager.start(coreConfig.getRenderThreadCount());
} else {
Logger.global.logInfo("Render-Threads are STOPPED! Use the command 'bluemap start' to start them.");
}
//update webapp and settings
blueMap.createOrUpdateWebApp(false);
@ -194,12 +215,7 @@ public void load() throws IOException, ParseResourceException {
saveTask = new TimerTask() {
@Override
public void run() {
synchronized (Plugin.this) {
if (maps == null) return;
for (BmMap map : maps.values()) {
map.save();
}
}
save();
}
};
daemonTimer.schedule(saveTask, TimeUnit.MINUTES.toMillis(2), TimeUnit.MINUTES.toMillis(2));
@ -215,14 +231,10 @@ public void run() {
daemonTimer.scheduleAtFixedRate(metricsTask, TimeUnit.MINUTES.toMillis(1), TimeUnit.MINUTES.toMillis(30));
//watch map-changes
this.regionFileWatchServices = new ArrayList<>();
this.regionFileWatchServices = new HashMap<>();
for (BmMap map : maps.values()) {
try {
RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map, false);
watcher.start();
regionFileWatchServices.add(watcher);
} catch (IOException ex) {
Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + " (This map might not automatically update)", ex);
if (pluginStatus.getMapStatus(map).isUpdateEnabled()) {
startWatchingMap(map);
}
}
@ -245,6 +257,8 @@ public void unload() {
try {
loadingLock.interruptAndLock();
synchronized (this) {
//save
save();
//disable api
if (api != null) api.unregister();
@ -264,7 +278,7 @@ public void unload() {
//stop file-watchers
if (regionFileWatchServices != null) {
for (RegionFileWatchService watcher : regionFileWatchServices) {
for (RegionFileWatchService watcher : regionFileWatchServices.values()) {
watcher.close();
}
regionFileWatchServices.clear();
@ -278,13 +292,6 @@ public void unload() {
if (webServer != null) webServer.close();
webServer = null;
//save renders
if (maps != null) {
for (BmMap map : maps.values()) {
map.save();
}
}
//clear resources and configs
blueMap = null;
worlds = null;
@ -295,6 +302,8 @@ public void unload() {
webServerConfig = null;
pluginConfig = null;
pluginStatus = null;
//done
loaded = false;
}
@ -308,6 +317,44 @@ public void reload() throws IOException, ParseResourceException {
load();
}
public synchronized void save() {
if (pluginStatus != null) {
try {
GsonConfigurationLoader loader = GsonConfigurationLoader.builder()
.file(new File(getCoreConfig().getDataFolder(), "pluginStatus.json"))
.build();
loader.save(loader.createNode().set(PluginStatus.class, pluginStatus));
} catch (IOException ex) {
Logger.global.logError("Failed to save pluginStatus.json!", ex);
}
}
if (maps != null) {
for (BmMap map : maps.values()) {
map.save();
}
}
}
public synchronized void startWatchingMap(BmMap map) {
stopWatchingMap(map);
try {
RegionFileWatchService watcher = new RegionFileWatchService(renderManager, map, false);
watcher.start();
regionFileWatchServices.put(map.getId(), watcher);
} catch (IOException ex) {
Logger.global.logError("Failed to create file-watcher for map: " + map.getId() + " (This means the map might not automatically update)", ex);
}
}
public synchronized void stopWatchingMap(BmMap map) {
RegionFileWatchService watcher = regionFileWatchServices.remove(map.getId());
if (watcher != null) {
watcher.close();
}
}
public boolean flushWorldUpdates(UUID worldUUID) throws IOException {
return serverInterface.persistWorldChanges(worldUUID);
}
@ -331,6 +378,10 @@ public WebServerConfig getWebServerConfig() {
public PluginConfig getPluginConfig() {
return pluginConfig;
}
public PluginStatus getPluginStatus() {
return pluginStatus;
}
public World getWorld(UUID uuid){
return worlds.get(uuid);

View File

@ -24,11 +24,44 @@
*/
package de.bluecolored.bluemap.common.plugin;
import de.bluecolored.bluemap.core.map.BmMap;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("FieldMayBeFinal")
@ConfigSerializable
public class PluginStatus {
private boolean renderThreadsEnabled = true;
private Map<String, MapStatus> maps = new HashMap<>();
public boolean isRenderThreadsEnabled() {
return renderThreadsEnabled;
}
public void setRenderThreadsEnabled(boolean renderThreadsEnabled) {
this.renderThreadsEnabled = renderThreadsEnabled;
}
public MapStatus getMapStatus(BmMap map) {
return maps.computeIfAbsent(map.getId(), k -> new MapStatus());
}
@ConfigSerializable
public static class MapStatus {
private boolean updateEnabled = true;
public boolean isUpdateEnabled() {
return updateEnabled;
}
public void setUpdateEnabled(boolean update) {
this.updateEnabled = update;
}
}
}

View File

@ -44,13 +44,12 @@
import de.bluecolored.bluemap.api.marker.MarkerSet;
import de.bluecolored.bluemap.api.marker.POIMarker;
import de.bluecolored.bluemap.common.plugin.Plugin;
import de.bluecolored.bluemap.common.plugin.PluginStatus;
import de.bluecolored.bluemap.common.plugin.serverinterface.CommandSource;
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.MapPurgeTask;
import de.bluecolored.bluemap.common.rendermanager.MapUpdateTask;
import de.bluecolored.bluemap.common.rendermanager.RenderTask;
import de.bluecolored.bluemap.common.rendermanager.*;
import de.bluecolored.bluemap.core.BlueMap;
import de.bluecolored.bluemap.core.MinecraftVersion;
import de.bluecolored.bluemap.core.logger.Logger;
@ -140,18 +139,32 @@ public void init() {
.build();
LiteralCommandNode<S> pauseCommand =
LiteralCommandNode<S> stopCommand =
literal("stop")
.requires(requirements("bluemap.stop"))
.executes(this::stopCommand)
.build();
LiteralCommandNode<S> resumeCommand =
LiteralCommandNode<S> startCommand =
literal("start")
.requires(requirements("bluemap.start"))
.executes(this::startCommand)
.build();
LiteralCommandNode<S> freezeCommand =
literal("freeze")
.requires(requirements("bluemap.freeze"))
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
.executes(this::freezeCommand))
.build();
LiteralCommandNode<S> unfreezeCommand =
literal("unfreeze")
.requires(requirements("bluemap.freeze"))
.then(argument("map", StringArgumentType.string()).suggests(new MapSuggestionProvider<>(plugin))
.executes(this::unfreezeCommand))
.build();
LiteralCommandNode<S> forceUpdateCommand =
addRenderArguments(
literal("force-update")
@ -227,8 +240,10 @@ public void init() {
baseCommand.addChild(helpCommand);
baseCommand.addChild(reloadCommand);
baseCommand.addChild(debugCommand);
baseCommand.addChild(pauseCommand);
baseCommand.addChild(resumeCommand);
baseCommand.addChild(stopCommand);
baseCommand.addChild(startCommand);
baseCommand.addChild(freezeCommand);
baseCommand.addChild(unfreezeCommand);
baseCommand.addChild(forceUpdateCommand);
baseCommand.addChild(updateCommand);
baseCommand.addChild(cancelCommand);
@ -521,26 +536,113 @@ 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, "Render-Threads stopped!"));
return 1;
new Thread(() -> {
plugin.getPluginStatus().setRenderThreadsEnabled(false);
plugin.getRenderManager().stop();
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads stopped!"));
plugin.save();
}).start();
} else {
source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already stopped!"));
return 0;
}
return 1;
}
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, "Render-Threads started!"));
return 1;
new Thread(() -> {
plugin.getPluginStatus().setRenderThreadsEnabled(true);
plugin.getRenderManager().start(plugin.getCoreConfig().getRenderThreadCount());
source.sendMessage(Text.of(TextColor.GREEN, "Render-Threads started!"));
plugin.save();
}).start();
} else {
source.sendMessage(Text.of(TextColor.RED, "Render-Threads are already running!"));
return 0;
}
return 1;
}
public int freezeCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
// parse map argument
String mapString = context.getArgument("map", String.class);
BmMap map = parseMap(mapString).orElse(null);
if (map == null) {
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));
return 0;
}
PluginStatus.MapStatus mapStatus = plugin.getPluginStatus().getMapStatus(map);
if (mapStatus.isUpdateEnabled()) {
new Thread(() -> {
mapStatus.setUpdateEnabled(false);
plugin.stopWatchingMap(map);
plugin.getRenderManager().removeRenderTasksIf(task -> {
if (task instanceof MapUpdateTask)
return ((MapUpdateTask) task).getMap().equals(map);
if (task instanceof WorldRegionRenderTask)
return ((WorldRegionRenderTask) task).getMap().equals(map);
return false;
});
source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is now frozen and will no longer be automatically updated!"));
source.sendMessage(Text.of(TextColor.GRAY, "Any currently scheduled updates for this map have been cancelled."));
plugin.save();
}).start();
} else {
source.sendMessage(Text.of(TextColor.RED, "This map is already frozen!"));
return 0;
}
return 1;
}
public int unfreezeCommand(CommandContext<S> context) {
CommandSource source = commandSourceInterface.apply(context.getSource());
// parse map argument
String mapString = context.getArgument("map", String.class);
BmMap map = parseMap(mapString).orElse(null);
if (map == null) {
source.sendMessage(Text.of(TextColor.RED, "There is no ", helper.mapHelperHover(), " with this name: ", TextColor.WHITE, mapString));
return 0;
}
PluginStatus.MapStatus mapStatus = plugin.getPluginStatus().getMapStatus(map);
if (!mapStatus.isUpdateEnabled()) {
new Thread(() -> {
mapStatus.setUpdateEnabled(true);
plugin.startWatchingMap(map);
plugin.getRenderManager().scheduleRenderTask(new MapUpdateTask(map));
source.sendMessage(Text.of(TextColor.GREEN, "Map ", TextColor.WHITE, mapString, TextColor.GREEN, " is no longer frozen and will be automatically updated!"));
plugin.save();
}).start();
} else {
source.sendMessage(Text.of(TextColor.RED, "This map is not frozen!"));
return 0;
}
return 1;
}
public int forceUpdateCommand(CommandContext<S> context) {
@ -727,7 +829,21 @@ public int mapsCommand(CommandContext<S> context) {
source.sendMessage(Text.of(TextColor.BLUE, "Maps loaded by BlueMap:"));
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())));
boolean unfrozen = plugin.getPluginStatus().getMapStatus(map).isUpdateEnabled();
if (unfrozen) {
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())));
} else {
source.sendMessage(Text.of(
TextColor.GRAY, " - ",
TextColor.WHITE, map.getId(),
TextColor.GRAY, " (" + map.getName() + ") - ",
TextColor.AQUA, TextFormat.ITALIC, "frozen!"
).setHoverText(Text.of(TextColor.WHITE, "World: ", TextColor.GRAY, map.getWorld().getName())));
}
}
return 1;
@ -741,7 +857,7 @@ public int createMarkerCommand(CommandContext<S> context) {
.replace("<", "&lt;")
.replace(">", "&gt;"); //no html via commands
// parse world/map argument
// parse map argument
String mapString = context.getArgument("map", String.class);
BmMap map = parseMap(mapString).orElse(null);

View File

@ -29,6 +29,7 @@
import java.util.*;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Predicate;
public class RenderManager {
private static final AtomicInteger nextRenderManagerIndex = new AtomicInteger(0);
@ -176,6 +177,17 @@ public boolean removeRenderTask(RenderTask task) {
}
}
public void removeRenderTasksIf(Predicate<RenderTask> removeCondition) {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return;
RenderTask first = renderTasks.removeFirst();
if (removeCondition.test(first)) first.cancel();
renderTasks.removeIf(removeCondition);
renderTasks.addFirst(first);
}
}
public void removeAllRenderTasks() {
synchronized (this.renderTasks) {
if (this.renderTasks.isEmpty()) return;