canvas checks

This commit is contained in:
Brian Neumann-Fopiano
2026-02-17 22:44:01 -05:00
parent 05d79b6d40
commit ba819e4899
9 changed files with 211 additions and 29 deletions
+2 -1
View File
@@ -54,7 +54,7 @@ registerCustomOutputTask("PixelFury", "C://Users/repix/workplace/Iris/1.21.3 - D
registerCustomOutputTask("PixelFuryDev", "C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins") registerCustomOutputTask("PixelFuryDev", "C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins")
// ========================== UNIX ============================== // ========================== UNIX ==============================
registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins") registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins")
registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/plugin-jars") registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins")
registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins") registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins")
registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins") registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins")
// ============================================================== // ==============================================================
@@ -228,6 +228,7 @@ allprojects {
compileJava { compileJava {
options.compilerArgs.add("-parameters") options.compilerArgs.add("-parameters")
options.encoding = "UTF-8" options.encoding = "UTF-8"
options.debugOptions.debugLevel = "none"
} }
javadoc { javadoc {
+1
View File
@@ -174,6 +174,7 @@ tasks {
compileJava { compileJava {
options.compilerArgs.add("-parameters") options.compilerArgs.add("-parameters")
options.encoding = "UTF-8" options.encoding = "UTF-8"
options.debugOptions.debugLevel = "none"
} }
/** /**
@@ -1004,7 +1004,6 @@ public class Iris extends VolmitPlugin implements Listener {
} }
public void splash() { public void splash() {
Iris.info("Server type & version: " + Bukkit.getName() + " v" + Bukkit.getVersion());
Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes()); Iris.info("Custom Biomes: " + INMS.get().countCustomBiomes());
printPacks(); printPacks();
@@ -21,6 +21,7 @@ package art.arcane.iris.core.commands;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings; import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.IrisWorlds; import art.arcane.iris.core.IrisWorlds;
import art.arcane.iris.core.link.FoliaWorldsLink;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.nms.INMS; import art.arcane.iris.core.nms.INMS;
import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
@@ -545,7 +546,7 @@ public class CommandIris implements DirectorExecutor {
return; return;
} }
if (!Bukkit.unloadWorld(world, false)) { if (!FoliaWorldsLink.get().unloadWorld(world, false)) {
sender().sendMessage(C.RED + "Failed to unload world: " + world.getName()); sender().sendMessage(C.RED + "Failed to unload world: " + world.getName());
return; return;
} }
@@ -1998,8 +1999,12 @@ public class CommandIris implements DirectorExecutor {
sender().sendMessage(C.GREEN + "Unloading world: " + world.getName()); sender().sendMessage(C.GREEN + "Unloading world: " + world.getName());
try { try {
IrisToolbelt.evacuate(world); IrisToolbelt.evacuate(world);
Bukkit.unloadWorld(world, false); boolean unloaded = FoliaWorldsLink.get().unloadWorld(world, false);
sender().sendMessage(C.GREEN + "World unloaded successfully."); if (unloaded) {
sender().sendMessage(C.GREEN + "World unloaded successfully.");
} else {
sender().sendMessage(C.RED + "Failed to unload the world.");
}
} catch (Exception e) { } catch (Exception e) {
sender().sendMessage(C.RED + "Failed to unload the world: " + e.getMessage()); sender().sendMessage(C.RED + "Failed to unload the world: " + e.getMessage());
e.printStackTrace(); e.printStackTrace();
@@ -24,6 +24,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
public class FoliaWorldsLink { public class FoliaWorldsLink {
private static volatile FoliaWorldsLink instance; private static volatile FoliaWorldsLink instance;
@@ -136,6 +137,11 @@ public class FoliaWorldsLink {
return false; return false;
} }
CompletableFuture<Boolean> asyncWorldUnload = unloadWorldViaAsyncApi(world, save);
if (asyncWorldUnload != null) {
return resolveAsyncUnload(asyncWorldUnload);
}
try { try {
return Bukkit.unloadWorld(world, save); return Bukkit.unloadWorld(world, save);
} catch (UnsupportedOperationException unsupported) { } catch (UnsupportedOperationException unsupported) {
@@ -158,6 +164,67 @@ public class FoliaWorldsLink {
} }
} }
private boolean resolveAsyncUnload(CompletableFuture<Boolean> asyncWorldUnload) {
if (J.isPrimaryThread()) {
if (!asyncWorldUnload.isDone()) {
return true;
}
try {
return Boolean.TRUE.equals(asyncWorldUnload.join());
} catch (Throwable e) {
throw new IllegalStateException("Failed to consume async world unload result.", unwrap(e));
}
}
try {
return Boolean.TRUE.equals(asyncWorldUnload.get(120, TimeUnit.SECONDS));
} catch (Throwable e) {
throw new IllegalStateException("Failed while waiting for async world unload result.", unwrap(e));
}
}
private CompletableFuture<Boolean> unloadWorldViaAsyncApi(World world, boolean save) {
Object bukkitServer = Bukkit.getServer();
if (bukkitServer == null) {
return null;
}
Method unloadWorldAsyncMethod;
try {
unloadWorldAsyncMethod = bukkitServer.getClass().getMethod("unloadWorldAsync", World.class, boolean.class, Consumer.class);
} catch (Throwable ignored) {
return null;
}
CompletableFuture<Boolean> callbackFuture = new CompletableFuture<>();
Runnable invokeTask = () -> {
Consumer<Boolean> callback = result -> callbackFuture.complete(Boolean.TRUE.equals(result));
try {
unloadWorldAsyncMethod.invoke(bukkitServer, world, save, callback);
} catch (Throwable e) {
callbackFuture.completeExceptionally(unwrap(e));
}
};
if (J.isFolia() && !isGlobalTickThread()) {
CompletableFuture<Void> scheduled = J.sfut(invokeTask);
if (scheduled == null) {
callbackFuture.completeExceptionally(new IllegalStateException("Failed to schedule global world-unload task."));
return callbackFuture;
}
scheduled.whenComplete((unused, throwable) -> {
if (throwable != null) {
callbackFuture.completeExceptionally(unwrap(throwable));
}
});
} else {
invokeTask.run();
}
return callbackFuture;
}
private boolean isWorldsProviderActive() { private boolean isWorldsProviderActive() {
return provider != null && levelStemClass != null && generatorTypeClass != null; return provider != null && levelStemClass != null && generatorTypeClass != null;
} }
@@ -457,17 +524,7 @@ public class FoliaWorldsLink {
return; return;
} }
Server server = Bukkit.getServer(); if (isGlobalTickThread()) {
boolean globalThread = false;
if (server != null) {
try {
Method isGlobalTickThreadMethod = server.getClass().getMethod("isGlobalTickThread");
globalThread = (boolean) isGlobalTickThreadMethod.invoke(server);
} catch (Throwable ignored) {
}
}
if (globalThread) {
detachTask.run(); detachTask.run();
return; return;
} }
@@ -479,9 +536,29 @@ public class FoliaWorldsLink {
detachFuture.get(15, TimeUnit.SECONDS); detachFuture.get(15, TimeUnit.SECONDS);
} }
private static boolean isGlobalTickThread() {
Server server = Bukkit.getServer();
if (server == null) {
return false;
}
try {
Method isGlobalTickThreadMethod = server.getClass().getMethod("isGlobalTickThread");
return Boolean.TRUE.equals(isGlobalTickThreadMethod.invoke(server));
} catch (Throwable ignored) {
return false;
}
}
private static Throwable unwrap(Throwable throwable) { private static Throwable unwrap(Throwable throwable) {
if (throwable instanceof InvocationTargetException invocationTargetException && invocationTargetException.getCause() != null) { if (throwable instanceof InvocationTargetException invocationTargetException && invocationTargetException.getCause() != null) {
return invocationTargetException.getCause(); return unwrap(invocationTargetException.getCause());
}
if (throwable instanceof java.util.concurrent.CompletionException completionException && completionException.getCause() != null) {
return unwrap(completionException.getCause());
}
if (throwable instanceof java.util.concurrent.ExecutionException executionException && executionException.getCause() != null) {
return unwrap(executionException.getCause());
} }
return throwable; return throwable;
} }
@@ -60,6 +60,7 @@ import java.io.IOException;
import java.util.Objects; import java.util.Objects;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer; import java.util.function.Consumer;
@SuppressWarnings("ALL") @SuppressWarnings("ALL")
@@ -291,11 +292,45 @@ public class IrisProject {
} }
} }
J.attemptAsync(() -> IO.delete(folder)); J.attemptAsync(() -> deleteStudioFolderWithRetry(folder, worldName));
Iris.debug("Closed Active Provider " + worldName); Iris.debug("Closed Active Provider " + worldName);
activeProvider = null; activeProvider = null;
} }
private static void deleteStudioFolderWithRetry(File folder, String worldName) {
if (folder == null) {
return;
}
long unloadWaitDeadlineMs = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(20);
while (Bukkit.getWorld(worldName) != null && System.currentTimeMillis() < unloadWaitDeadlineMs) {
J.sleep(100);
}
int attempts = 0;
while (folder.exists() && attempts < 40) {
IO.delete(folder);
if (!folder.exists()) {
return;
}
attempts++;
J.sleep(250);
}
if (!folder.exists()) {
return;
}
try {
Iris.queueWorldDeletionOnStartup(java.util.Collections.singleton(worldName));
Iris.warn("Queued deferred deletion for studio world folder \"" + worldName + "\".");
} catch (IOException e) {
Iris.warn("Failed to queue deferred deletion for studio world folder \"" + worldName + "\".");
Iris.reportError(e);
}
}
public File getCodeWorkspaceFile() { public File getCodeWorkspaceFile() {
return new File(path, getName() + ".code-workspace"); return new File(path, getName() + ".code-workspace");
} }
@@ -2,6 +2,7 @@ package art.arcane.iris.core.tools;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.link.FoliaWorldsLink;
import art.arcane.iris.core.pregenerator.PregenTask; import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
@@ -102,7 +103,7 @@ public class IrisPackBenchmarking {
var world = Bukkit.getWorld("benchmark"); var world = Bukkit.getWorld("benchmark");
if (world == null) return; if (world == null) return;
IrisToolbelt.evacuate(world); IrisToolbelt.evacuate(world);
Bukkit.unloadWorld(world, true); FoliaWorldsLink.get().unloadWorld(world, true);
}); });
stopwatch.end(); stopwatch.end();
@@ -167,4 +168,4 @@ public class IrisPackBenchmarking {
private int findHighest(KList<Integer> list) { private int findHighest(KList<Integer> list) {
return Collections.max(list); return Collections.max(list);
} }
} }
@@ -5,6 +5,9 @@ import art.arcane.iris.Iris
import art.arcane.iris.core.IrisSettings import art.arcane.iris.core.IrisSettings
import art.arcane.iris.util.format.C import art.arcane.iris.util.format.C
import art.arcane.volmlib.util.format.Form import art.arcane.volmlib.util.format.Form
import org.bukkit.Bukkit
import java.time.LocalDate
import java.time.format.DateTimeFormatter
enum class Mode(private val color: C) { enum class Mode(private val color: C) {
STABLE(C.IRIS), STABLE(C.IRIS),
@@ -34,6 +37,11 @@ enum class Mode(private val color: C) {
fun splash() { fun splash() {
val padd = Form.repeat(" ", 8) val padd = Form.repeat(" ", 8)
val padd2 = Form.repeat(" ", 4) val padd2 = Form.repeat(" ", 4)
val version = Iris.instance.description.version
val releaseTrain = getReleaseTrain(version)
val serverVersion = getServerVersion()
val startupDate = getStartupDate()
val javaVersion = getJavaVersion()
val splash = arrayOf( val splash = arrayOf(
padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@",
@@ -51,14 +59,16 @@ enum class Mode(private val color: C) {
val info = arrayOf( val info = arrayOf(
"", "",
padd2 + color + " Iris, " + C.AQUA + "Iris, Dimension Engine " + C.RED + "[" + releaseTrain + " RELEASE]",
padd2 + C.GRAY + " Version: " + color + version,
padd2 + C.GRAY + " By: " + color + "Arcane Arts (Volmit Software)",
padd2 + C.GRAY + " Server: " + color + serverVersion,
padd2 + C.GRAY + " Java: " + color + javaVersion + C.GRAY + " | Date: " + color + startupDate,
padd2 + C.GRAY + " Commit: " + color + BuildConstants.COMMIT + C.GRAY + "/" + color + BuildConstants.ENVIRONMENT,
"", "",
"", "",
"", "",
"", "",
padd2 + color + " Iris",
padd2 + C.GRAY + " by " + color + "Volmit Software",
padd2 + C.GRAY + " v" + color + Iris.instance.description.version,
padd2 + C.GRAY + " c" + color + BuildConstants.COMMIT + C.GRAY + "/" + color + BuildConstants.ENVIRONMENT,
) )
@@ -73,4 +83,43 @@ enum class Mode(private val color: C) {
Iris.info(builder.toString()) Iris.info(builder.toString())
} }
}
private fun getServerVersion(): String {
var version = Bukkit.getVersion()
val mcMarkerIndex = version.indexOf(" (MC:")
if (mcMarkerIndex != -1) {
version = version.substring(0, mcMarkerIndex)
}
return version
}
private fun getJavaVersion(): Int {
var version = System.getProperty("java.version")
if (version.startsWith("1.")) {
version = version.substring(2, 3)
} else {
val dot = version.indexOf(".")
if (dot != -1) {
version = version.substring(0, dot)
}
}
return version.toInt()
}
private fun getStartupDate(): String {
return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE)
}
private fun getReleaseTrain(version: String): String {
var value = version
val suffixIndex = value.indexOf("-")
if (suffixIndex >= 0) {
value = value.substring(0, suffixIndex)
}
val split = value.split('.')
if (split.size >= 2) {
return split[0] + "." + split[1]
}
return value
}
}
@@ -56,6 +56,7 @@ private val incompatibilities by task {
private val software by task { private val software by task {
val supported = setOf( val supported = setOf(
"canvas",
"folia", "folia",
"purpur", "purpur",
"pufferfish", "pufferfish",
@@ -64,10 +65,10 @@ private val software by task {
"bukkit" "bukkit"
) )
if (supported.any { server.name.contains(it, true) }) STABLE.withDiagnostics() if (isCanvasServer() || supported.any { server.name.contains(it, true) }) STABLE.withDiagnostics()
else WARNING.withDiagnostics( else WARNING.withDiagnostics(
WARN.create("Unsupported Server Software"), WARN.create("Unsupported Server Software"),
WARN.create("- Please consider using Folia, Paper, or Purpur instead.") WARN.create("- Please consider using Canvas, Folia, Paper, or Purpur instead.")
) )
} }
@@ -88,7 +89,7 @@ private val injection by task {
WARNING.withDiagnostics( WARNING.withDiagnostics(
WARN.create("Java Agent"), WARN.create("Java Agent"),
WARN.create("- Skipping dynamic Java agent attach on Spigot/Bukkit to avoid runtime agent warnings."), WARN.create("- Skipping dynamic Java agent attach on Spigot/Bukkit to avoid runtime agent warnings."),
WARN.create("- For full runtime injection support, run with -javaagent:" + Agent.AGENT_JAR.path + " or use Paper/Purpur.") WARN.create("- For full runtime injection support, run with -javaagent:" + Agent.AGENT_JAR.path + " or use Canvas/Folia/Paper/Purpur.")
) )
} else if (!Agent.install()) UNSTABLE.withDiagnostics( } else if (!Agent.install()) UNSTABLE.withDiagnostics(
ERROR.create("Java Agent"), ERROR.create("Java Agent"),
@@ -154,7 +155,20 @@ val tasks = listOf(
private val server get() = Bukkit.getServer() private val server get() = Bukkit.getServer()
private fun isPaperPreferredServer(): Boolean { private fun isPaperPreferredServer(): Boolean {
val name = server.name.lowercase(Locale.ROOT) val name = server.name.lowercase(Locale.ROOT)
return name.contains("folia") || name.contains("paper") || name.contains("purpur") || name.contains("pufferfish") return isCanvasServer()
|| name.contains("folia")
|| name.contains("paper")
|| name.contains("purpur")
|| name.contains("pufferfish")
}
private fun isCanvasServer(): Boolean {
val loader: ClassLoader? = server.javaClass.classLoader
return try {
Class.forName("io.canvasmc.canvas.region.WorldRegionizer", false, loader)
true
} catch (_: Throwable) {
server.name.contains("canvas", true)
}
} }
private fun <T> MutableList<T>.addAll(vararg values: T) = values.forEach(this::add) private fun <T> MutableList<T>.addAll(vararg values: T) = values.forEach(this::add)
fun task(action: () -> ValueWithDiagnostics<Mode>) = PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, Task>> { _, _ -> fun task(action: () -> ValueWithDiagnostics<Mode>) = PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, Task>> { _, _ ->