From 9ea2037910c1c7e21cd8f9164af807b716e6fa94 Mon Sep 17 00:00:00 2001 From: willkroboth Date: Tue, 12 Jul 2022 18:05:50 -0400 Subject: [PATCH 1/6] Add method MVWorldManager#addOrRemoveWorldSafely Addresses Multiverse/Multiverse-Core#2560 Commands that load or unload worlds trigger an IllegalStateException if they are run via a command block while the worlds are being ticked. Using a BukkitRunnable, the operation can be delayed until the next tick at a time when the worlds are not being ticked. MVWorldManager#addOrRemoveWorldSafely performs this logic, either running the operation now or delaying it. 8 commands were modified to use MVWorldManager#addOrRemoveWorldSafely, which I think are all the relevant commands. Unfortunately I haven't found a way to tell when the worlds are being ticked on a Spigot server. There probably won't be any way to do this until [SPIGOT-7089](https://hub.spigotmc.org/jira/browse/SPIGOT-7089) resolves --- .../MultiverseCore/api/MVWorldManager.java | 9 ++++++++ .../MultiverseCore/commands/CloneCommand.java | 15 +++++++------ .../commands/CreateCommand.java | 17 +++++++++------ .../commands/DeleteCommand.java | 4 ++-- .../commands/ImportCommand.java | 13 +++++++----- .../MultiverseCore/commands/LoadCommand.java | 13 +++++++----- .../MultiverseCore/commands/RegenCommand.java | 4 ++-- .../commands/RemoveCommand.java | 13 +++++++----- .../commands/UnloadCommand.java | 13 +++++++----- .../MultiverseCore/utils/WorldManager.java | 21 +++++++++++++++++++ 10 files changed, 86 insertions(+), 36 deletions(-) diff --git a/src/main/java/com/onarandombox/MultiverseCore/api/MVWorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/api/MVWorldManager.java index 599ce7cb9..1a6757d52 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/api/MVWorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/api/MVWorldManager.java @@ -355,4 +355,13 @@ boolean addWorld(String name, Environment env, String seedString, WorldType type * @return A collection of world names that are deemed importable. */ Collection getPotentialWorlds(); + + /** + * Performs the given operation that creates and/or unloads a world. If this operation cannot run because the worlds are being ticked, the operation is delayed until the next tick. + * + * @param worldName The world being modified. + * @param operationName The name of the operation being done. + * @param worldModification The operation to perform + */ + void addOrRemoveWorldSafely(String worldName, String operationName, Runnable worldModification); } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/CloneCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/CloneCommand.java index 95a0bf146..3acf1a308 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/CloneCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/CloneCommand.java @@ -40,16 +40,19 @@ public CloneCommand(MultiverseCore plugin) { @Override public void runCommand(CommandSender sender, List args) { String oldName = args.get(0); - if (!this.worldManager.hasUnloadedWorld(oldName, true)) { + String newName = args.get(1); + if (!this.worldManager.hasUnloadedWorld(oldName, true)) { // If no world was found, we can't clone. sender.sendMessage("Sorry, Multiverse doesn't know about world " + oldName + ", so we can't clone it!"); sender.sendMessage("Check the " + ChatColor.GREEN + "/mv list" + ChatColor.WHITE + " command to verify it is listed."); return; } - if (this.plugin.getMVWorldManager().cloneWorld(oldName, args.get(1))) { - sender.sendMessage(ChatColor.GREEN + "World cloned!"); - } else { - sender.sendMessage(ChatColor.RED + "World could NOT be cloned!"); - } + this.worldManager.addOrRemoveWorldSafely(oldName, "clone", () -> { + if (this.worldManager.cloneWorld(oldName, newName)) { + sender.sendMessage(ChatColor.GREEN + "World cloned!"); + } else { + sender.sendMessage(ChatColor.RED + "World could NOT be cloned!"); + } + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/CreateCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/CreateCommand.java index 6d8f3833b..a929f3918 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/CreateCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/CreateCommand.java @@ -120,12 +120,17 @@ public void runCommand(CommandSender sender, List args) { return; } } - Command.broadcastCommandMessage(sender, "Starting creation of world '" + worldName + "'..."); - if (this.worldManager.addWorld(worldName, environment, seed, type, allowStructures, generator, useSpawnAdjust)) { - Command.broadcastCommandMessage(sender, "Complete!"); - } else { - Command.broadcastCommandMessage(sender, "FAILED."); - } + boolean finalAllowStructures = allowStructures; + boolean finalUseSpawnAdjust = useSpawnAdjust; + this.worldManager.addOrRemoveWorldSafely(worldName, "create", () -> { + Command.broadcastCommandMessage(sender, "Starting creation of world '" + worldName + "'..."); + + if (this.worldManager.addWorld(worldName, environment, seed, type, finalAllowStructures, generator, finalUseSpawnAdjust)) { + Command.broadcastCommandMessage(sender, "Complete!"); + } else { + Command.broadcastCommandMessage(sender, "FAILED."); + } + }); } } \ No newline at end of file diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java index 603454935..e7f05385a 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/DeleteCommand.java @@ -47,7 +47,7 @@ public void runCommand(CommandSender sender, List args) { private Runnable deleteRunnable(@NotNull CommandSender sender, @NotNull String worldName) { - return () -> { + return () -> this.plugin.getMVWorldManager().addOrRemoveWorldSafely(worldName, "delete", () -> { sender.sendMessage(String.format("Deleting world '%s'...", worldName)); if (this.plugin.getMVWorldManager().deleteWorld(worldName)) { sender.sendMessage(String.format("%sWorld %s was deleted!", ChatColor.GREEN, worldName)); @@ -55,6 +55,6 @@ private Runnable deleteRunnable(@NotNull CommandSender sender, } sender.sendMessage(String.format("%sThere was an issue deleting '%s'! Please check console for errors.", ChatColor.RED, worldName)); - }; + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/ImportCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/ImportCommand.java index 7a52d5a15..48964d283 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/ImportCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/ImportCommand.java @@ -127,11 +127,14 @@ public void runCommand(CommandSender sender, List args) { sender.sendMessage("That world environment did not exist."); sender.sendMessage("For a list of available world types, type: " + ChatColor.AQUA + "/mvenv"); } else { - Command.broadcastCommandMessage(sender, String.format("Starting import of world '%s'...", worldName)); - if (this.worldManager.addWorld(worldName, environment, null, null, null, generator, useSpawnAdjust)) - Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Complete!"); - else - Command.broadcastCommandMessage(sender, ChatColor.RED + "Failed!"); + boolean finalUseSpawnAdjust = useSpawnAdjust; + this.worldManager.addOrRemoveWorldSafely(worldName, "unload", () -> { + Command.broadcastCommandMessage(sender, String.format("Starting import of world '%s'...", worldName)); + if (this.worldManager.addWorld(worldName, environment, null, null, null, generator, finalUseSpawnAdjust)) + Command.broadcastCommandMessage(sender, ChatColor.GREEN + "Complete!"); + else + Command.broadcastCommandMessage(sender, ChatColor.RED + "Failed!"); + }); } } } \ No newline at end of file diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/LoadCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/LoadCommand.java index 4a96ef36c..dee3ebf02 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/LoadCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/LoadCommand.java @@ -33,10 +33,13 @@ public LoadCommand(MultiverseCore plugin) { @Override public void runCommand(CommandSender sender, List args) { - if (this.plugin.getMVWorldManager().loadWorld(args.get(0))) { - Command.broadcastCommandMessage(sender, "Loaded world '" + args.get(0) + "'!"); - } else { - sender.sendMessage("Error trying to load world '" + args.get(0) + "'!"); - } + String worldName = args.get(0); + this.plugin.getMVWorldManager().addOrRemoveWorldSafely(worldName, "load", () -> { + if (this.plugin.getMVWorldManager().loadWorld(worldName)) { + Command.broadcastCommandMessage(sender, "Loaded world '" + worldName + "'!"); + } else { + sender.sendMessage("Error trying to load world '" + worldName + "'!"); + } + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java index 4efe7ad51..8d6601d6f 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/RegenCommand.java @@ -58,12 +58,12 @@ private Runnable doWorldRegen(@NotNull CommandSender sender, @NotNull String seed, boolean keepGamerules) { - return () -> { + return () -> plugin.getMVWorldManager().addOrRemoveWorldSafely(worldName, "regenerate", () -> { if (this.plugin.getMVWorldManager().regenWorld(worldName, useSeed, randomSeed, seed, keepGamerules)) { sender.sendMessage(ChatColor.GREEN + "World Regenerated!"); return; } sender.sendMessage(ChatColor.RED + "World could NOT be regenerated!"); - }; + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/RemoveCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/RemoveCommand.java index ff24e7408..5b5636e37 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/RemoveCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/RemoveCommand.java @@ -33,10 +33,13 @@ public RemoveCommand(MultiverseCore plugin) { @Override public void runCommand(CommandSender sender, List args) { - if (this.plugin.getMVWorldManager().removeWorldFromConfig(args.get(0))) { - sender.sendMessage("World removed from config!"); - } else { - sender.sendMessage("Error trying to remove world from config!"); - } + String worldName = args.get(0); + this.plugin.getMVWorldManager().addOrRemoveWorldSafely(worldName, "remove", () -> { + if (this.plugin.getMVWorldManager().removeWorldFromConfig(worldName)) { + sender.sendMessage("World removed from config!"); + } else { + sender.sendMessage("Error trying to remove world from config!"); + } + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java b/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java index 52f92c6ca..0d3eb315f 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java +++ b/src/main/java/com/onarandombox/MultiverseCore/commands/UnloadCommand.java @@ -33,10 +33,13 @@ public UnloadCommand(MultiverseCore plugin) { @Override public void runCommand(CommandSender sender, List args) { - if (this.plugin.getMVWorldManager().unloadWorld(args.get(0))) { - Command.broadcastCommandMessage(sender, "Unloaded world '" + args.get(0) + "'!"); - } else { - sender.sendMessage("Error trying to unload world '" + args.get(0) + "'!"); - } + String worldName = args.get(0); + this.plugin.getMVWorldManager().addOrRemoveWorldSafely(worldName, "unload", () -> { + if (this.plugin.getMVWorldManager().unloadWorld(worldName)) { + Command.broadcastCommandMessage(sender, "Unloaded world '" + worldName + "'!"); + } else { + sender.sendMessage("Error trying to unload world '" + worldName + "'!"); + } + }); } } diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index 0b2ab0701..8b94c7cdf 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -32,6 +32,7 @@ import org.bukkit.permissions.Permission; import org.bukkit.permissions.PermissionDefault; import org.bukkit.plugin.Plugin; +import org.bukkit.scheduler.BukkitRunnable; import java.io.File; import java.io.FilenameFilter; @@ -1029,4 +1030,24 @@ public Collection getPotentialWorlds() { .map(File::getName) .collect(Collectors.toList()); } + + /** + * {@inheritDoc} + */ + public void addOrRemoveWorldSafely(String worldName, String operationName, Runnable worldModification) { + // TODO: Find real way to tell if worlds are being ticked + if (!worldName.equals("testWorld")) { + // Operation is fine to do now + worldModification.run(); + } else { + // Operation needs to be delayed until worlds are not being ticked + + Logging.fine("Worlds were being ticked while attempting to %s %s. Trying again in the next tick", operationName, worldName); + new BukkitRunnable() { + public void run() { + worldModification.run(); + } + }.runTask(plugin); + } + } } From 200a461b03bd9b18ebb6c98bffc893c41ad71eb6 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:32:48 -0400 Subject: [PATCH 2/6] Use reflection to test for Paper and check if it is safe to load or unload worlds Note: this code was designed under the assumption that PaperMC/Paper#8316 will be accepted, so it won't work unless the commit from that PR is included in the Paper build. --- .../MultiverseCore/utils/WorldManager.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index 8b94c7cdf..730171b2d 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -37,6 +37,8 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -1035,8 +1037,7 @@ public Collection getPotentialWorlds() { * {@inheritDoc} */ public void addOrRemoveWorldSafely(String worldName, String operationName, Runnable worldModification) { - // TODO: Find real way to tell if worlds are being ticked - if (!worldName.equals("testWorld")) { + if (safeToAddOrRemoveWorld()) { // Operation is fine to do now worldModification.run(); } else { @@ -1050,4 +1051,21 @@ public void run() { }.runTask(plugin); } } + + private boolean safeToAddOrRemoveWorld(){ + Method isTickingWorlds; + try { + isTickingWorlds = Bukkit.class.getMethod("isTickingWorlds"); + } catch (NoSuchMethodException e) { + // Paper fixes aren't active, so it is always considered safe to proceed + return true; + } + // Paper fixes are active, so we need to and can check Bukkit.isTickingWorlds + try { + return !(boolean) isTickingWorlds.invoke(null); + } catch (InvocationTargetException | IllegalAccessException e) { + // Shouldn't happen, I know I'm using the method correctly + throw new RuntimeException(e); + } + } } From b92c2fa403a2328b93b229c9eeaf11061b1bf8e1 Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Fri, 19 Aug 2022 17:37:14 -0400 Subject: [PATCH 3/6] Add @Override annotation to addOrRemoveWorldSafely --- .../java/com/onarandombox/MultiverseCore/utils/WorldManager.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index 730171b2d..e967012b8 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -1036,6 +1036,7 @@ public Collection getPotentialWorlds() { /** * {@inheritDoc} */ + @Override public void addOrRemoveWorldSafely(String worldName, String operationName, Runnable worldModification) { if (safeToAddOrRemoveWorld()) { // Operation is fine to do now From bc4127e16aef103b282b50f30ac204bd776f764e Mon Sep 17 00:00:00 2001 From: BuildTools <46540330+willkroboth@users.noreply.github.com> Date: Sat, 17 Sep 2022 17:15:38 -0400 Subject: [PATCH 4/6] Use more reflection to directly access isIteratingOverLevels --- .../MultiverseCore/utils/WorldManager.java | 41 +++++++++++-------- 1 file changed, 24 insertions(+), 17 deletions(-) diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index e967012b8..65a666804 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -17,13 +17,8 @@ import com.onarandombox.MultiverseCore.api.SafeTTeleporter; import com.onarandombox.MultiverseCore.api.WorldPurger; import com.onarandombox.MultiverseCore.event.MVWorldDeleteEvent; -import org.bukkit.Bukkit; -import org.bukkit.GameRule; -import org.bukkit.Location; -import org.bukkit.World; +import org.bukkit.*; import org.bukkit.World.Environment; -import org.bukkit.WorldCreator; -import org.bukkit.WorldType; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -37,6 +32,7 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; +import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -1054,19 +1050,30 @@ public void run() { } private boolean safeToAddOrRemoveWorld(){ - Method isTickingWorlds; + Server server = Bukkit.getServer(); + Logging.finest("Using reflection to test for Paper build after PR #7653"); try { - isTickingWorlds = Bukkit.class.getMethod("isTickingWorlds"); - } catch (NoSuchMethodException e) { - // Paper fixes aren't active, so it is always considered safe to proceed + // basically doing ((CraftServer) Bukkit.getServer()).getServer().isIteratingOverLevels; + Method getConsole = server.getClass().getMethod("getServer"); + Object console = getConsole.invoke(server); + + Field isTickingWorlds = console.getClass().getField("isIteratingOverLevels"); + boolean isTicking = isTickingWorlds.getBoolean(console); + + Logging.finest("Paper fix active"); + return !isTicking; + } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { + Logging.finest("%sUnexpected exception: %s", ChatColor.RED, e.getMessage()); + Logging.finest("Assuming Paper fix is inactive"); + // If the Paper fix actually is active it should become obvious when Paper complains + // about a world being loaded/unloaded while being ticked + // If that happens, this method needs to be fixed + return true; + } catch (NoSuchFieldException ignored) { + // Expected to fail when field isIteratingOverLevels doesn't exist + // Therefore, Paper fixes aren't active, so it is always considered safe to proceed + Logging.finest("Paper fix inactive"); return true; - } - // Paper fixes are active, so we need to and can check Bukkit.isTickingWorlds - try { - return !(boolean) isTickingWorlds.invoke(null); - } catch (InvocationTargetException | IllegalAccessException e) { - // Shouldn't happen, I know I'm using the method correctly - throw new RuntimeException(e); } } } From cc4fbbde1ce812238672a1dbb309d1232773d841 Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 31 Mar 2023 08:03:19 -0400 Subject: [PATCH 5/6] Revert automatic star import --- .../onarandombox/MultiverseCore/utils/WorldManager.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index 65a666804..00275803b 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -17,8 +17,15 @@ import com.onarandombox.MultiverseCore.api.SafeTTeleporter; import com.onarandombox.MultiverseCore.api.WorldPurger; import com.onarandombox.MultiverseCore.event.MVWorldDeleteEvent; -import org.bukkit.*; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.GameRule; +import org.bukkit.Location; +import org.bukkit.Server; +import org.bukkit.World; import org.bukkit.World.Environment; +import org.bukkit.WorldCreator; +import org.bukkit.WorldType; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; From 11c9a06a31bb8124d49a155115efd66ea8af8c1a Mon Sep 17 00:00:00 2001 From: BuildTools Date: Fri, 31 Mar 2023 08:16:10 -0400 Subject: [PATCH 6/6] Use original safeToAddOrRemoveWorld method (https://github.com/PaperMC/Paper/pull/8316 was accepted, so this works now) Also some more logging messages --- .../MultiverseCore/utils/WorldManager.java | 43 +++++++------------ 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java index 00275803b..edb60649b 100644 --- a/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java +++ b/src/main/java/com/onarandombox/MultiverseCore/utils/WorldManager.java @@ -18,10 +18,8 @@ import com.onarandombox.MultiverseCore.api.WorldPurger; import com.onarandombox.MultiverseCore.event.MVWorldDeleteEvent; import org.bukkit.Bukkit; -import org.bukkit.ChatColor; import org.bukkit.GameRule; import org.bukkit.Location; -import org.bukkit.Server; import org.bukkit.World; import org.bukkit.World.Environment; import org.bukkit.WorldCreator; @@ -39,7 +37,6 @@ import java.io.File; import java.io.FilenameFilter; import java.io.IOException; -import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -54,7 +51,6 @@ import java.util.Stack; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Pattern; import java.util.stream.Collectors; /** @@ -1041,12 +1037,13 @@ public Collection getPotentialWorlds() { */ @Override public void addOrRemoveWorldSafely(String worldName, String operationName, Runnable worldModification) { + Logging.finest("Checking if it is safe to perform world modification"); if (safeToAddOrRemoveWorld()) { // Operation is fine to do now + Logging.finest("Clear to modify worlds"); worldModification.run(); } else { // Operation needs to be delayed until worlds are not being ticked - Logging.fine("Worlds were being ticked while attempting to %s %s. Trying again in the next tick", operationName, worldName); new BukkitRunnable() { public void run() { @@ -1057,30 +1054,22 @@ public void run() { } private boolean safeToAddOrRemoveWorld(){ - Server server = Bukkit.getServer(); - Logging.finest("Using reflection to test for Paper build after PR #7653"); + Logging.finest("Checking for Paper"); + Method isTickingWorlds; try { - // basically doing ((CraftServer) Bukkit.getServer()).getServer().isIteratingOverLevels; - Method getConsole = server.getClass().getMethod("getServer"); - Object console = getConsole.invoke(server); - - Field isTickingWorlds = console.getClass().getField("isIteratingOverLevels"); - boolean isTicking = isTickingWorlds.getBoolean(console); - - Logging.finest("Paper fix active"); - return !isTicking; - } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { - Logging.finest("%sUnexpected exception: %s", ChatColor.RED, e.getMessage()); - Logging.finest("Assuming Paper fix is inactive"); - // If the Paper fix actually is active it should become obvious when Paper complains - // about a world being loaded/unloaded while being ticked - // If that happens, this method needs to be fixed - return true; - } catch (NoSuchFieldException ignored) { - // Expected to fail when field isIteratingOverLevels doesn't exist - // Therefore, Paper fixes aren't active, so it is always considered safe to proceed - Logging.finest("Paper fix inactive"); + isTickingWorlds = Bukkit.class.getMethod("isTickingWorlds"); + } catch (NoSuchMethodException e) { + // Paper fixes aren't active, so it is always considered safe to proceed + Logging.finest("Paper fixes inactive"); return true; } + // Paper fixes are active, and Paper wants us to check Bukkit.isTickingWorlds() + Logging.finest("Paper fixes active"); + try { + return !(boolean) isTickingWorlds.invoke(null); + } catch (InvocationTargetException | IllegalAccessException e) { + // Shouldn't happen, I know I'm using the method correctly + throw new RuntimeException(e); + } } }