Skip to content

Commit

Permalink
Add method MVWorldManager#addOrRemoveWorldSafely
Browse files Browse the repository at this point in the history
Addresses Multiverse#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
  • Loading branch information
willkroboth committed Mar 31, 2023
1 parent 1c24b17 commit 3417db8
Show file tree
Hide file tree
Showing 10 changed files with 86 additions and 36 deletions.
Expand Up @@ -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<String> 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);
}
Expand Up @@ -40,16 +40,19 @@ public CloneCommand(MultiverseCore plugin) {
@Override
public void runCommand(CommandSender sender, List<String> 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!");
}
});
}
}
Expand Up @@ -120,12 +120,17 @@ public void runCommand(CommandSender sender, List<String> 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.");
}
});
}
}
Expand Up @@ -47,14 +47,14 @@ public void runCommand(CommandSender sender, List<String> 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));
return;
}
sender.sendMessage(String.format("%sThere was an issue deleting '%s'! Please check console for errors.",
ChatColor.RED, worldName));
};
});
}
}
Expand Up @@ -127,11 +127,14 @@ public void runCommand(CommandSender sender, List<String> 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!");
});
}
}
}
Expand Up @@ -33,10 +33,13 @@ public LoadCommand(MultiverseCore plugin) {

@Override
public void runCommand(CommandSender sender, List<String> 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 + "'!");
}
});
}
}
Expand Up @@ -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!");
};
});
}
}
Expand Up @@ -33,10 +33,13 @@ public RemoveCommand(MultiverseCore plugin) {

@Override
public void runCommand(CommandSender sender, List<String> 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!");
}
});
}
}
Expand Up @@ -33,10 +33,13 @@ public UnloadCommand(MultiverseCore plugin) {

@Override
public void runCommand(CommandSender sender, List<String> 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 + "'!");
}
});
}
}
Expand Up @@ -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;
Expand Down Expand Up @@ -1029,4 +1030,24 @@ public Collection<String> 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);
}
}
}

0 comments on commit 3417db8

Please sign in to comment.