Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Defer World modification to when the worlds are not being ticked on Paper #2802

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
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);
}
Original file line number Diff line number Diff line change
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!");
}
});
}
}
Original file line number Diff line number Diff line change
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.");
}
});
}
}
Original file line number Diff line number Diff line change
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));
};
});
}
}
Original file line number Diff line number Diff line change
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!");
});
}
}
}
Original file line number Diff line number Diff line change
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 + "'!");
}
});
}
}
Original file line number Diff line number Diff line change
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!");
};
});
}
}
Original file line number Diff line number Diff line change
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!");
}
});
}
}
Original file line number Diff line number Diff line change
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 + "'!");
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,13 @@
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;
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;
Expand All @@ -48,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;

/**
Expand Down Expand Up @@ -1029,4 +1031,45 @@ public Collection<String> getPotentialWorlds() {
.map(File::getName)
.collect(Collectors.toList());
}

/**
* {@inheritDoc}
*/
@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() {
worldModification.run();
}
}.runTask(plugin);
willkroboth marked this conversation as resolved.
Show resolved Hide resolved
}
}

private boolean safeToAddOrRemoveWorld(){
Logging.finest("Checking for Paper");
Method isTickingWorlds;
try {
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);
}
}
}