Merge pull request #780 from DRE2N/world-io

Fix world unloading issues
This commit is contained in:
Daniel Saukel 2020-05-01 14:05:30 +02:00 committed by GitHub
commit 64650ca9d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 123 additions and 95 deletions

View File

@ -80,14 +80,13 @@ import de.erethon.dungeonsxl.trigger.TriggerListener;
import de.erethon.dungeonsxl.trigger.TriggerTypeCache; import de.erethon.dungeonsxl.trigger.TriggerTypeCache;
import de.erethon.dungeonsxl.util.LWCUtil; import de.erethon.dungeonsxl.util.LWCUtil;
import de.erethon.dungeonsxl.util.PlaceholderUtil; import de.erethon.dungeonsxl.util.PlaceholderUtil;
import de.erethon.dungeonsxl.world.DEditWorld;
import de.erethon.dungeonsxl.world.DResourceWorld; import de.erethon.dungeonsxl.world.DResourceWorld;
import de.erethon.dungeonsxl.world.DWorldListener; import de.erethon.dungeonsxl.world.DWorldListener;
import de.erethon.dungeonsxl.world.LWCIntegration; import de.erethon.dungeonsxl.world.LWCIntegration;
import de.erethon.dungeonsxl.world.WorldConfig; import de.erethon.dungeonsxl.world.WorldConfig;
import de.erethon.dungeonsxl.world.WorldUnloadTask;
import de.erethon.vignette.api.VignetteAPI; import de.erethon.vignette.api.VignetteAPI;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -97,8 +96,6 @@ import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.LivingEntity; import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.HandlerList; import org.bukkit.event.HandlerList;
@ -207,23 +204,8 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI {
@Override @Override
public void onEnable() { public void onEnable() {
super.onEnable(); super.onEnable();
if (compat.isPaper() && Internals.andHigher(Internals.v1_14_R1).contains(compat.getInternals()) && System.getProperty("XLDevMode") == null) { if (Internals.andHigher(Internals.v1_14_R1).contains(compat.getInternals())) {
File paperFile = new File("paper.yml"); getLogger().warning("Support for Minecraft 1.14 and higher is experimental. Do not use this in a production environment.");
FileConfiguration paperConfig = YamlConfiguration.loadConfiguration(paperFile);
if (paperConfig.getBoolean("settings.async-chunks.enable")) {
MessageUtil.log(this, "&4It seems that the server runs Paper 1.14 or higher and that asynchronous world / chunk (un-) loading is enabled.");
MessageUtil.log(this, "&4This feature seems to be too error-prone for massive usage at runtime, which DungeonsXL requires.");
MessageUtil.log(this, "&4See &6https://github.com/PaperMC/Paper/issues/3063 &4for further information.");
MessageUtil.log(this, "&4The server will be restarted with asynchronous chunk loading turned off.");
paperConfig.set("settings.async-chunks.enable", false);
try {
paperConfig.save(paperFile);
} catch (IOException exception) {
exception.printStackTrace();
}
getServer().spigot().restart();
return;
}
} }
instance = this; instance = this;
@ -351,7 +333,6 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI {
if (!DResourceWorld.RAW.exists()) { if (!DResourceWorld.RAW.exists()) {
DResourceWorld.createRaw(); DResourceWorld.createRaw();
} }
new WorldUnloadTask(this).runTaskTimer(this, 20L, 20L);//1200L
Bukkit.getPluginManager().registerEvents(new DWorldListener(this), this); Bukkit.getPluginManager().registerEvents(new DWorldListener(this), this);
if (LWCUtil.isLWCLoaded()) { if (LWCUtil.isLWCLoaded()) {
new LWCIntegration(this); new LWCIntegration(this);
@ -436,7 +417,7 @@ public class DungeonsXL extends DREPlugin implements DungeonsAPI {
public void saveData() { public void saveData() {
protections.saveAll(); protections.saveAll();
instanceCache.getAllIf(i -> i instanceof EditWorld).forEach(i -> ((EditWorld) i).save()); instanceCache.getAllIf(i -> i instanceof EditWorld).forEach(i -> ((DEditWorld) i).forceSave());
} }
public void loadData() { public void loadData() {

View File

@ -48,7 +48,7 @@ public class MainConfig extends DREConfig {
NEVER NEVER
} }
public static final int CONFIG_VERSION = 17; public static final int CONFIG_VERSION = 18;
private String language = "english"; private String language = "english";
private boolean enableEconomy = false; private boolean enableEconomy = false;
@ -96,6 +96,7 @@ public class MainConfig extends DREConfig {
/* Performance */ /* Performance */
private int maxInstances = 10; private int maxInstances = 10;
private int editInstanceRemovalDelay = 5;
/* Secure Mode */ /* Secure Mode */
private boolean secureModeEnabled = false; private boolean secureModeEnabled = false;
@ -385,6 +386,20 @@ public class MainConfig extends DREConfig {
this.maxInstances = maxInstances; this.maxInstances = maxInstances;
} }
/**
* @return the delay in seconds until an edit world without players is saved and removed
*/
public int getEditInstanceRemovalDelay() {
return editInstanceRemovalDelay;
}
/**
* @param delay the delay in seconds until an edit world without players is saved and removed
*/
public void setEditInstanceRemovalDelay(int delay) {
editInstanceRemovalDelay = delay;
}
/** /**
* @return if the secure mode is enabled * @return if the secure mode is enabled
*/ */
@ -559,6 +574,10 @@ public class MainConfig extends DREConfig {
config.set("maxInstances", maxInstances); config.set("maxInstances", maxInstances);
} }
if (!config.contains("editInstanceRemovalDelay")) {
config.set("editInstanceRemovalDelay", editInstanceRemovalDelay);
}
if (!config.contains("secureMode.enabled")) { if (!config.contains("secureMode.enabled")) {
config.set("secureMode.enabled", secureModeEnabled); config.set("secureMode.enabled", secureModeEnabled);
} }
@ -647,6 +666,7 @@ public class MainConfig extends DREConfig {
} }
maxInstances = config.getInt("maxInstances", maxInstances); maxInstances = config.getInt("maxInstances", maxInstances);
editInstanceRemovalDelay = config.getInt("editInstanceRemovalDelay", editInstanceRemovalDelay);
secureModeEnabled = config.getBoolean("secureMode.enabled", secureModeEnabled); secureModeEnabled = config.getBoolean("secureMode.enabled", secureModeEnabled);
openInventories = config.getBoolean("secureMode.openInventories", openInventories); openInventories = config.getBoolean("secureMode.openInventories", openInventories);
dropItems = config.getBoolean("secureMode.dropItems", dropItems); dropItems = config.getBoolean("secureMode.dropItems", dropItems);

View File

@ -21,8 +21,8 @@ import de.erethon.dungeonsxl.DungeonsXL;
import de.erethon.dungeonsxl.config.DMessage; import de.erethon.dungeonsxl.config.DMessage;
import de.erethon.dungeonsxl.player.DGlobalPlayer; import de.erethon.dungeonsxl.player.DGlobalPlayer;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference;
import java.util.Collection; import java.util.Collection;
import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.FileConfiguration;
@ -39,7 +39,7 @@ public abstract class GlobalProtection {
public static final String SIGN_TAG = "[DXL]"; public static final String SIGN_TAG = "[DXL]";
private WeakReference<World> world; private String world;
private int id; private int id;
protected GlobalProtection(DungeonsXL plugin, World world, int id) { protected GlobalProtection(DungeonsXL plugin, World world, int id) {
@ -47,7 +47,7 @@ public abstract class GlobalProtection {
protections = plugin.getGlobalProtectionCache(); protections = plugin.getGlobalProtectionCache();
config = plugin.getGlobalData().getConfig(); config = plugin.getGlobalData().getConfig();
this.world = new WeakReference<>(world); this.world = world.getName();
this.id = id; this.id = id;
protections.addProtection(this); protections.addProtection(this);
@ -57,7 +57,7 @@ public abstract class GlobalProtection {
* @return the world * @return the world
*/ */
public World getWorld() { public World getWorld() {
return world.get(); return Bukkit.getWorld(world);
} }
/** /**

View File

@ -143,8 +143,8 @@ public class DEditPlayer extends DInstancePlayer implements EditPlayer {
reset(false); reset(false);
if (editWorld != null) { if (!plugin.isLoadingWorld() && editWorld != null && editWorld.getPlayers().isEmpty()) {
editWorld.save(); editWorld.delete();
} }
} }

View File

@ -457,6 +457,10 @@ public class DGamePlayer extends DInstancePlayer implements GamePlayer {
// ...*flies away* // ...*flies away*
} }
} }
if (gameWorld.getPlayers().isEmpty()) {
gameWorld.delete();
}
} }
@Override @Override

View File

@ -18,16 +18,21 @@ package de.erethon.dungeonsxl.world;
import de.erethon.commons.compatibility.Version; import de.erethon.commons.compatibility.Version;
import de.erethon.commons.misc.FileUtil; import de.erethon.commons.misc.FileUtil;
import de.erethon.commons.misc.ProgressBar;
import de.erethon.dungeonsxl.DungeonsXL; import de.erethon.dungeonsxl.DungeonsXL;
import de.erethon.dungeonsxl.api.event.world.EditWorldSaveEvent; import de.erethon.dungeonsxl.api.event.world.EditWorldSaveEvent;
import de.erethon.dungeonsxl.api.event.world.EditWorldUnloadEvent; import de.erethon.dungeonsxl.api.event.world.EditWorldUnloadEvent;
import de.erethon.dungeonsxl.api.world.EditWorld; import de.erethon.dungeonsxl.api.world.EditWorld;
import de.erethon.dungeonsxl.player.DEditPlayer;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.Location;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.block.Sign; import org.bukkit.block.Sign;
import org.bukkit.entity.Player;
import org.bukkit.scheduler.BukkitRunnable; import org.bukkit.scheduler.BukkitRunnable;
/** /**
@ -39,12 +44,8 @@ public class DEditWorld extends DInstanceWorld implements EditWorld {
private File idFile; private File idFile;
DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) {
super(plugin, resourceWorld, folder, world, id);
}
DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) { DEditWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) {
this(plugin, resourceWorld, folder, null, id); super(plugin, resourceWorld, folder, id);
} }
/* Getters and setters */ /* Getters and setters */
@ -87,7 +88,51 @@ public class DEditWorld extends DInstanceWorld implements EditWorld {
public void save() { public void save() {
EditWorldSaveEvent event = new EditWorldSaveEvent(this); EditWorldSaveEvent event = new EditWorldSaveEvent(this);
Bukkit.getPluginManager().callEvent(event); Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
plugin.setLoadingWorld(true);
Map<Player, Double[]> players = new HashMap<>();
getWorld().getPlayers().forEach(p -> players.put(p,
new Double[]{
p.getLocation().getX(),
p.getLocation().getY(),
p.getLocation().getZ(),
new Double(p.getLocation().getYaw()),
new Double(p.getLocation().getPitch())
}
));
kickAllPlayers();
getResource().editWorld = null;
plugin.getInstanceCache().remove(this);
getResource().getSignData().serializeSigns(signs.values());
Bukkit.unloadWorld(getWorld(), true);
new ProgressBar(players.keySet(), plugin.getMainConfig().getEditInstanceRemovalDelay()) {
@Override
public void onFinish() {
getResource().clearFolder();
FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES);
DResourceWorld.deleteUnusedFiles(getResource().getFolder());
FileUtil.removeDir(getFolder());
plugin.setLoadingWorld(false);
EditWorld newEditWorld = getResource().getOrInstantiateEditWorld(true);
players.keySet().forEach(p -> {
if (p.isOnline()) {
new DEditPlayer(plugin, p, newEditWorld);
Double[] coords = players.get(p);
p.teleport(new Location(newEditWorld.getWorld(), coords[0], coords[1], coords[2], coords[3].floatValue(), coords[4].floatValue()));
}
});
}
}.send(plugin);
}
public void forceSave() {
EditWorldSaveEvent event = new EditWorldSaveEvent(this);
Bukkit.getPluginManager().callEvent(event);
if (event.isCancelled()) { if (event.isCancelled()) {
return; return;
} }
@ -116,21 +161,27 @@ public class DEditWorld extends DInstanceWorld implements EditWorld {
kickAllPlayers(); kickAllPlayers();
if (save) { if (save) {
getResource().getSignData().serializeSigns(signs.values());
Bukkit.unloadWorld(getWorld(), true); Bukkit.unloadWorld(getWorld(), true);
new BukkitRunnable() { new BukkitRunnable() {
@Override @Override
public void run() { public void run() {
getResource().clearFolder();
FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES); FileUtil.copyDir(getFolder(), getResource().getFolder(), DungeonsXL.EXCLUDED_FILES);
DResourceWorld.deleteUnusedFiles(getResource().getFolder()); DResourceWorld.deleteUnusedFiles(getResource().getFolder());
FileUtil.removeDir(getFolder()); FileUtil.removeDir(getFolder());
} }
}.runTaskLaterAsynchronously(plugin, 200L); }.runTaskLaterAsynchronously(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L);
} }
if (!save) { if (!save) {
Bukkit.unloadWorld(getWorld(), /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4)); Bukkit.unloadWorld(getWorld(), /* SPIGOT-5225 */ !Version.isAtLeast(Version.MC1_14_4));
DResourceWorld.deleteUnusedFiles(getResource().getFolder()); new BukkitRunnable() {
@Override
public void run() {
FileUtil.removeDir(getFolder()); FileUtil.removeDir(getFolder());
} }
}.runTaskLaterAsynchronously(plugin, plugin.getMainConfig().getEditInstanceRemovalDelay() * 20L);
}
getResource().editWorld = null; getResource().editWorld = null;
plugin.getInstanceCache().remove(this); plugin.getInstanceCache().remove(this);

View File

@ -96,13 +96,8 @@ public class DGameWorld extends DInstanceWorld implements GameWorld {
private boolean readySign; private boolean readySign;
DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) {
super(plugin, resourceWorld, folder, world, id);
caliburn = plugin.getCaliburn();
}
DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) { DGameWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) {
this(plugin, resourceWorld, folder, null, id); super(plugin, resourceWorld, folder, id);
caliburn = plugin.getCaliburn(); caliburn = plugin.getCaliburn();
} }

View File

@ -28,7 +28,6 @@ import de.erethon.dungeonsxl.api.player.PlayerCache;
import de.erethon.dungeonsxl.api.sign.DungeonSign; import de.erethon.dungeonsxl.api.sign.DungeonSign;
import de.erethon.dungeonsxl.api.world.InstanceWorld; import de.erethon.dungeonsxl.api.world.InstanceWorld;
import java.io.File; import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Collection; import java.util.Collection;
@ -51,17 +50,16 @@ public abstract class DInstanceWorld implements InstanceWorld {
protected Map<Block, DungeonSign> signs = new HashMap<>(); protected Map<Block, DungeonSign> signs = new HashMap<>();
private DResourceWorld resourceWorld; private DResourceWorld resourceWorld;
private File folder; private File folder;
WeakReference<World> world; String world;
private int id; private int id;
private Location lobby; private Location lobby;
DInstanceWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, World world, int id) { DInstanceWorld(DungeonsXL plugin, DResourceWorld resourceWorld, File folder, int id) {
this.plugin = plugin; this.plugin = plugin;
dPlayers = plugin.getPlayerCache(); dPlayers = plugin.getPlayerCache();
this.resourceWorld = resourceWorld; this.resourceWorld = resourceWorld;
this.folder = folder; this.folder = folder;
this.world = new WeakReference<>(world);
this.id = id; this.id = id;
plugin.getInstanceCache().add(id, this); plugin.getInstanceCache().add(id, this);
@ -85,7 +83,10 @@ public abstract class DInstanceWorld implements InstanceWorld {
@Override @Override
public World getWorld() { public World getWorld() {
return world.get(); if (world == null) {
return null;
}
return Bukkit.getWorld(world);
} }
/** /**

View File

@ -29,7 +29,6 @@ import de.erethon.dungeonsxl.api.world.GameWorld;
import de.erethon.dungeonsxl.api.world.ResourceWorld; import de.erethon.dungeonsxl.api.world.ResourceWorld;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.WeakReference;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.GameRule; import org.bukkit.GameRule;
import org.bukkit.OfflinePlayer; import org.bukkit.OfflinePlayer;
@ -65,7 +64,7 @@ public class DResourceWorld implements ResourceWorld {
config = new WorldConfig(plugin, configFile); config = new WorldConfig(plugin, configFile);
} }
signData = new SignData(new File(folder, "DXLData.data")); signData = new SignData(new File(folder, SignData.FILE_NAME));
} }
public DResourceWorld(DungeonsXL plugin, File folder) { public DResourceWorld(DungeonsXL plugin, File folder) {
@ -78,7 +77,7 @@ public class DResourceWorld implements ResourceWorld {
config = new WorldConfig(plugin, configFile); config = new WorldConfig(plugin, configFile);
} }
signData = new SignData(new File(folder, "DXLData.data")); signData = new SignData(new File(folder, SignData.FILE_NAME));
} }
/* Getters and setters */ /* Getters and setters */
@ -201,7 +200,7 @@ public class DResourceWorld implements ResourceWorld {
DInstanceWorld instance = game ? new DGameWorld(plugin, this, instanceFolder, id) : new DEditWorld(plugin, this, instanceFolder, id); DInstanceWorld instance = game ? new DGameWorld(plugin, this, instanceFolder, id) : new DEditWorld(plugin, this, instanceFolder, id);
FileUtil.copyDir(folder, instanceFolder, DungeonsXL.EXCLUDED_FILES); FileUtil.copyDir(folder, instanceFolder, DungeonsXL.EXCLUDED_FILES);
instance.world = new WeakReference<>(Bukkit.createWorld(WorldCreator.name(name).environment(getWorldEnvironment()))); instance.world = Bukkit.createWorld(WorldCreator.name(name).environment(getWorldEnvironment())).getName();
instance.getWorld().setGameRule(GameRule.DO_FIRE_TICK, false); instance.getWorld().setGameRule(GameRule.DO_FIRE_TICK, false);
if (Bukkit.getPluginManager().isPluginEnabled("dynmap")) { if (Bukkit.getPluginManager().isPluginEnabled("dynmap")) {
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "dynmap pause all"); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), "dynmap pause all");
@ -211,6 +210,7 @@ public class DResourceWorld implements ResourceWorld {
if (game) { if (game) {
signData.deserializeSigns((DGameWorld) instance); signData.deserializeSigns((DGameWorld) instance);
instance.getWorld().setAutoSave(false);
} else { } else {
signData.deserializeSigns((DEditWorld) instance); signData.deserializeSigns((DEditWorld) instance);
} }
@ -291,12 +291,25 @@ public class DResourceWorld implements ResourceWorld {
} }
FileUtil.copyDir(RAW, folder, DungeonsXL.EXCLUDED_FILES); FileUtil.copyDir(RAW, folder, DungeonsXL.EXCLUDED_FILES);
editWorld.generateIdFile(); editWorld.generateIdFile();
editWorld.world = new WeakReference<>(creator.createWorld()); editWorld.world = creator.createWorld().getName();
editWorld.generateIdFile(); editWorld.generateIdFile();
return editWorld; return editWorld;
} }
void clearFolder() {
for (File file : FileUtil.getFilesForFolder(getFolder())) {
if (file.getName().equals(SignData.FILE_NAME)) {
continue;
}
if (file.isDirectory()) {
FileUtil.removeDir(file);
} else {
file.delete();
}
}
}
/** /**
* Removes files that are not needed from a world * Removes files that are not needed from a world
* *

View File

@ -36,6 +36,8 @@ import org.bukkit.block.Sign;
*/ */
public class SignData { public class SignData {
public static final String FILE_NAME = "DXLData.data";
private File file; private File file;
public SignData(File file) { public SignData(File file) {
@ -55,7 +57,7 @@ public class SignData {
} }
public void updateFile(DResourceWorld resource) { public void updateFile(DResourceWorld resource) {
file = new File(resource.getFolder(), "DXLData.data"); file = new File(resource.getFolder(), FILE_NAME);
} }
/** /**

View File

@ -1,39 +0,0 @@
/*
* Copyright (C) 2012-2020 Frank Baumann
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package de.erethon.dungeonsxl.world;
import de.erethon.dungeonsxl.DungeonsXL;
import de.erethon.dungeonsxl.api.world.InstanceWorld;
import org.bukkit.scheduler.BukkitRunnable;
/**
* @author Frank Baumann, Daniel Saukel
*/
public class WorldUnloadTask extends BukkitRunnable {
private DungeonsXL plugin;
public WorldUnloadTask(DungeonsXL plugin) {
this.plugin = plugin;
}
@Override
public void run() {
plugin.getInstanceCache().getAllIf(i -> ((DInstanceWorld) i).exists() && i.getPlayers().isEmpty()).forEach(InstanceWorld::delete);
}
}