WIP - Dont compile

This commit is contained in:
Brian Neumann-Fopiano
2026-02-17 00:34:34 -05:00
parent 440c1bec6c
commit 43a65135b1
63 changed files with 3763 additions and 419 deletions
+9 -2
View File
@@ -35,6 +35,9 @@ plugins {
group = "art.arcane" group = "art.arcane"
version = "4.0.0-1.20.1-1.21.11-Dev1" version = "4.0.0-1.20.1-1.21.11-Dev1"
val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate")
.orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT")
.get()
apply<ApiGenerator>() apply<ApiGenerator>()
@@ -89,6 +92,10 @@ nmsBindings.forEach { (key, value) ->
dependencies { dependencies {
compileOnly(project(":core")) compileOnly(project(":core"))
compileOnly(volmLibCoordinate) {
isChanging = true
isTransitive = false
}
compileOnly(rootProject.libs.annotations) compileOnly(rootProject.libs.annotations)
compileOnly(rootProject.libs.byteBuddy.core) compileOnly(rootProject.libs.byteBuddy.core)
} }
@@ -181,8 +188,8 @@ fun exec(vararg command: Any) {
} }
configurations.configureEach { configurations.configureEach {
resolutionStrategy.cacheChangingModulesFor(60, "minutes") resolutionStrategy.cacheChangingModulesFor(0, "seconds")
resolutionStrategy.cacheDynamicVersionsFor(60, "minutes") resolutionStrategy.cacheDynamicVersionsFor(0, "seconds")
} }
allprojects { allprojects {
+7 -5
View File
@@ -38,6 +38,9 @@ plugins {
val apiVersion = "1.19" val apiVersion = "1.19"
val main = "art.arcane.iris.Iris" val main = "art.arcane.iris.Iris"
val lib = "art.arcane.iris.util" val lib = "art.arcane.iris.util"
val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate")
.orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT")
.get()
/** /**
* Dependencies. * Dependencies.
@@ -73,6 +76,10 @@ dependencies {
// Shaded // Shaded
implementation(slimjarHelper("spigot")) implementation(slimjarHelper("spigot"))
implementation(volmLibCoordinate) {
isChanging = true
isTransitive = false
}
// Dynamically Loaded // Dynamically Loaded
slim(libs.paralithic) slim(libs.paralithic)
@@ -234,10 +241,5 @@ rootProject.tasks.named("prepareKotlinBuildScriptModel") {
} }
sourceSets.main { sourceSets.main {
java.srcDir("../../VolmLib/shared/src/main/java")
java.srcDir(generateTemplates.map { it.outputs }) java.srcDir(generateTemplates.map { it.outputs })
} }
kotlin.sourceSets.named("main") {
kotlin.srcDir("../../VolmLib/shared/src/main/kotlin")
}
+164 -1
View File
@@ -57,6 +57,7 @@ import art.arcane.iris.util.plugin.VolmitPlugin;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import art.arcane.iris.util.plugin.chunk.ChunkTickets; import art.arcane.iris.util.plugin.chunk.ChunkTickets;
import art.arcane.iris.util.scheduling.J; import art.arcane.iris.util.scheduling.J;
import art.arcane.iris.util.misc.ServerProperties;
import art.arcane.volmlib.util.scheduling.Queue; import art.arcane.volmlib.util.scheduling.Queue;
import art.arcane.volmlib.util.scheduling.ShurikenQueue; import art.arcane.volmlib.util.scheduling.ShurikenQueue;
import lombok.NonNull; import lombok.NonNull;
@@ -94,6 +95,7 @@ public class Iris extends VolmitPlugin implements Listener {
private static VolmitSender sender; private static VolmitSender sender;
private static Thread shutdownHook; private static Thread shutdownHook;
private static File settingsFile; private static File settingsFile;
private static final String PENDING_WORLD_DELETE_FILE = "pending-world-deletes.txt";
static { static {
try { try {
@@ -470,6 +472,12 @@ public class Iris extends VolmitPlugin implements Listener {
services.values().forEach(IrisService::onEnable); services.values().forEach(IrisService::onEnable);
services.values().forEach(this::registerListener); services.values().forEach(this::registerListener);
addShutdownHook(); addShutdownHook();
processPendingStartupWorldDeletes();
if (J.isFolia()) {
checkForBukkitWorlds(s -> true);
}
J.s(() -> { J.s(() -> {
J.a(() -> IO.delete(getTemp())); J.a(() -> IO.delete(getTemp()));
J.a(LazyPregenerator::loadLazyGenerators, 100); J.a(LazyPregenerator::loadLazyGenerators, 100);
@@ -480,7 +488,9 @@ public class Iris extends VolmitPlugin implements Listener {
J.a(ServerConfigurator::configure, 20); J.a(ServerConfigurator::configure, 20);
autoStartStudio(); autoStartStudio();
checkForBukkitWorlds(s -> true); if (!J.isFolia()) {
checkForBukkitWorlds(s -> true);
}
IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName()); IrisToolbelt.retainMantleDataForSlice(String.class.getCanonicalName());
IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName()); IrisToolbelt.retainMantleDataForSlice(BlockData.class.getCanonicalName());
}); });
@@ -534,6 +544,12 @@ public class Iris extends VolmitPlugin implements Listener {
Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!"); Iris.info(C.LIGHT_PURPLE + "Loaded " + s + "!");
} catch (Throwable e) { } catch (Throwable e) {
Iris.error("Failed to load world " + s + "!"); Iris.error("Failed to load world " + s + "!");
if (containsCreateWorldUnsupportedOperation(e)) {
Iris.error("This server denied Bukkit.createWorld for \"" + s + "\" at the current startup phase.");
Iris.error("Ensure Iris is loaded at STARTUP and restart after staging worlds in bukkit.yml.");
reportError(e);
return;
}
e.printStackTrace(); e.printStackTrace();
} }
}); });
@@ -543,6 +559,153 @@ public class Iris extends VolmitPlugin implements Listener {
} }
} }
private static boolean containsCreateWorldUnsupportedOperation(Throwable throwable) {
Throwable cursor = throwable;
while (cursor != null) {
if (cursor instanceof UnsupportedOperationException || cursor instanceof IllegalStateException) {
for (StackTraceElement element : cursor.getStackTrace()) {
if ("org.bukkit.craftbukkit.CraftServer".equals(element.getClassName())
&& "createWorld".equals(element.getMethodName())) {
return true;
}
}
}
cursor = cursor.getCause();
}
return false;
}
public static synchronized int queueWorldDeletionOnStartup(Collection<String> worldNames) throws IOException {
if (instance == null || worldNames == null || worldNames.isEmpty()) {
return 0;
}
LinkedHashMap<String, String> queue = loadPendingWorldDeleteMap();
int before = queue.size();
for (String worldName : worldNames) {
String normalized = normalizeWorldName(worldName);
if (normalized == null) {
continue;
}
queue.putIfAbsent(normalized.toLowerCase(Locale.ROOT), normalized);
}
if (queue.size() != before) {
writePendingWorldDeleteMap(queue);
}
return queue.size() - before;
}
private void processPendingStartupWorldDeletes() {
try {
LinkedHashMap<String, String> queue = loadPendingWorldDeleteMap();
if (queue.isEmpty()) {
return;
}
LinkedHashMap<String, String> remaining = new LinkedHashMap<>();
for (String worldName : queue.values()) {
if (worldName.equalsIgnoreCase(ServerProperties.LEVEL_NAME)) {
Iris.warn("Skipping queued deletion for \"" + worldName + "\" because it is configured as level-name.");
continue;
}
if (Bukkit.getWorld(worldName) != null) {
Iris.warn("Skipping queued deletion for \"" + worldName + "\" because it is currently loaded.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
continue;
}
File worldFolder = new File(Bukkit.getWorldContainer(), worldName);
if (!worldFolder.exists()) {
Iris.info("Queued world deletion skipped for \"" + worldName + "\" (folder missing).");
continue;
}
IO.delete(worldFolder);
if (worldFolder.exists()) {
Iris.warn("Failed to delete queued world folder \"" + worldName + "\". Retrying on next startup.");
remaining.put(worldName.toLowerCase(Locale.ROOT), worldName);
continue;
}
Iris.info("Deleted queued world folder \"" + worldName + "\".");
}
writePendingWorldDeleteMap(remaining);
} catch (Throwable e) {
Iris.error("Failed to process queued startup world deletions.");
reportError(e);
e.printStackTrace();
}
}
private static LinkedHashMap<String, String> loadPendingWorldDeleteMap() throws IOException {
LinkedHashMap<String, String> queue = new LinkedHashMap<>();
if (instance == null) {
return queue;
}
File queueFile = instance.getDataFile(PENDING_WORLD_DELETE_FILE);
if (!queueFile.exists()) {
return queue;
}
try (BufferedReader reader = new BufferedReader(new FileReader(queueFile))) {
String line;
while ((line = reader.readLine()) != null) {
String normalized = normalizeWorldName(line);
if (normalized == null) {
continue;
}
queue.putIfAbsent(normalized.toLowerCase(Locale.ROOT), normalized);
}
}
return queue;
}
private static void writePendingWorldDeleteMap(Map<String, String> queue) throws IOException {
if (instance == null) {
return;
}
File queueFile = instance.getDataFile(PENDING_WORLD_DELETE_FILE);
if (queue.isEmpty()) {
if (queueFile.exists()) {
IO.delete(queueFile);
}
return;
}
File parent = queueFile.getParentFile();
if (parent != null && !parent.exists() && !parent.mkdirs()) {
throw new IOException("Failed to create queue directory: " + parent.getAbsolutePath());
}
try (PrintWriter writer = new PrintWriter(new FileWriter(queueFile))) {
for (String worldName : queue.values()) {
writer.println(worldName);
}
}
}
@Nullable
private static String normalizeWorldName(String worldName) {
if (worldName == null) {
return null;
}
String trimmed = worldName.trim();
if (trimmed.isEmpty()) {
return null;
}
return trimmed;
}
private void autoStartStudio() { private void autoStartStudio() {
if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) { if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) {
Iris.info("Starting up auto Studio!"); Iris.info("Starting up auto Studio!");
@@ -34,6 +34,7 @@ import art.arcane.iris.core.tools.IrisToolbelt;
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;
import art.arcane.iris.engine.object.IrisPosition; import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.engine.object.annotations.Snippet; import art.arcane.iris.engine.object.annotations.Snippet;
import art.arcane.volmlib.util.collection.KMap; import art.arcane.volmlib.util.collection.KMap;
import art.arcane.volmlib.util.collection.KSet; import art.arcane.volmlib.util.collection.KSet;
@@ -41,21 +42,23 @@ import art.arcane.iris.util.context.IrisContext;
import art.arcane.iris.engine.object.IrisJigsawStructurePlacement; import art.arcane.iris.engine.object.IrisJigsawStructurePlacement;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler; import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler;
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 art.arcane.volmlib.util.io.CountingDataInputStream; import art.arcane.volmlib.util.io.CountingDataInputStream;
import art.arcane.volmlib.util.io.IO; import art.arcane.volmlib.util.io.IO;
import art.arcane.iris.util.mantle.TectonicPlate; import art.arcane.iris.util.mantle.TectonicPlate;
import art.arcane.iris.util.math.Position2;
import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.matter.Matter; import art.arcane.iris.util.matter.Matter;
import art.arcane.iris.util.nbt.mca.MCAFile; import art.arcane.iris.util.nbt.mca.MCAFile;
import art.arcane.iris.util.nbt.mca.MCAUtil; import art.arcane.iris.util.nbt.mca.MCAUtil;
import art.arcane.iris.util.parallel.MultiBurst; import art.arcane.iris.util.parallel.MultiBurst;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import art.arcane.iris.util.scheduling.J;
import art.arcane.iris.util.scheduling.jobs.Job; import art.arcane.iris.util.scheduling.jobs.Job;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import net.jpountz.lz4.LZ4BlockInputStream; import net.jpountz.lz4.LZ4BlockInputStream;
@@ -72,38 +75,41 @@ import java.net.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutorService; import java.util.concurrent.*;
import java.util.concurrent.Executors; import java.util.concurrent.atomic.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream; import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"}) @Director(name = "Developer", origin = DirectorOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
public class CommandDeveloper implements DecreeExecutor { public class CommandDeveloper implements DecreeExecutor {
private static final long DELETE_CHUNK_HEARTBEAT_MS = 5000L;
private static final int DELETE_CHUNK_MAX_ATTEMPTS = 2;
private static final int DELETE_CHUNK_STACK_LIMIT = 20;
private static final Set<String> ACTIVE_DELETE_CHUNK_WORLDS = ConcurrentHashMap.newKeySet();
private CommandTurboPregen turboPregen; private CommandTurboPregen turboPregen;
private CommandLazyPregen lazyPregen; private CommandLazyPregen lazyPregen;
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true) @Director(description = "Get Loaded TectonicPlates Count", origin = DirectorOrigin.BOTH, sync = true)
public void EngineStatus() { public void EngineStatus() {
Iris.service(IrisEngineSVC.class) Iris.service(IrisEngineSVC.class)
.engineStatus(sender()); .engineStatus(sender());
} }
@Decree(description = "Send a test exception to sentry") @Director(description = "Send a test exception to sentry")
public void Sentry() { public void Sentry() {
Engine engine = engine(); Engine engine = engine();
if (engine != null) IrisContext.getOr(engine); if (engine != null) IrisContext.getOr(engine);
Iris.reportError(new Exception("This is a test")); Iris.reportError(new Exception("This is a test"));
} }
@Decree(description = "QOL command to open an overworld studio world", sync = true) @Director(description = "QOL command to open an overworld studio world", sync = true)
public void so() { public void so() {
sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)"); sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)");
Iris.service(StudioSVC.class).open(sender(), 1337, "overworld"); Iris.service(StudioSVC.class).open(sender(), 1337, "overworld");
} }
@Decree(description = "Set aura spins") @Director(description = "Set aura spins")
public void aura( public void aura(
@Param(description = "The h color value", defaultValue = "-20") @Param(description = "The h color value", defaultValue = "-20")
int h, int h,
@@ -119,7 +125,7 @@ public class CommandDeveloper implements DecreeExecutor {
sender().sendMessage("<rainbow>Aura Spins updated to " + h + " " + s + " " + b); sender().sendMessage("<rainbow>Aura Spins updated to " + h + " " + s + " " + b);
} }
@Decree(description = "Bitwise calculations") @Director(description = "Bitwise calculations")
public void bitwise( public void bitwise(
@Param(description = "The first value to run calculations on") @Param(description = "The first value to run calculations on")
int value1, int value1,
@@ -144,7 +150,7 @@ public class CommandDeveloper implements DecreeExecutor {
sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "").replaceAll(">", "").replaceAll("%", "") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v); sender().sendMessage(C.GREEN + "" + value1 + " " + C.GREEN + operator.replaceAll("<", "").replaceAll(">", "").replaceAll("%", "") + " " + C.GREEN + value2 + C.GREEN + " returns " + C.GREEN + v);
} }
@Decree(description = "Update the pack of a world (UNSAFE!)", name = "update-world", aliases = "^world") @Director(description = "Update the pack of a world (UNSAFE!)", name = "update-world", aliases = "^world")
public void updateWorld( public void updateWorld(
@Param(description = "The world to update", contextual = true) @Param(description = "The world to update", contextual = true)
World world, World world,
@@ -180,7 +186,7 @@ public class CommandDeveloper implements DecreeExecutor {
Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder); Iris.service(StudioSVC.class).installIntoWorld(sender(), pack.getLoadKey(), folder);
} }
@Decree(description = "Dev cmd to fix all the broken objects caused by faulty shrinkwarp") @Director(description = "Dev cmd to fix all the broken objects caused by faulty shrinkwarp")
public void fixObjects( public void fixObjects(
@Param(aliases = "dimension", description = "The dimension type to create the world with") @Param(aliases = "dimension", description = "The dimension type to create the world with")
IrisDimension type IrisDimension type
@@ -298,7 +304,7 @@ public class CommandDeveloper implements DecreeExecutor {
}); });
} }
@Decree(description = "Test") @Director(description = "Test")
public void mantle(@Param(defaultValue = "false") boolean plate, @Param(defaultValue = "21474836474") String name) throws Throwable { public void mantle(@Param(defaultValue = "false") boolean plate, @Param(defaultValue = "21474836474") String name) throws Throwable {
var base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin"); var base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.bin");
var section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin"); var section = Iris.instance.getDataFile("dump", "pv." + name + ".section.bin");
@@ -325,7 +331,7 @@ public class CommandDeveloper implements DecreeExecutor {
Files.write(target.toPath(), bytes); Files.write(target.toPath(), bytes);
} }
@Decree(description = "Test") @Director(description = "Test")
public void dumpThreads() { public void dumpThreads() {
try { try {
File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt"); File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt");
@@ -362,7 +368,7 @@ public class CommandDeveloper implements DecreeExecutor {
} }
@SneakyThrows @SneakyThrows
@Decree(description = "Generate Iris structures for all loaded datapack structures") @Director(description = "Generate Iris structures for all loaded datapack structures")
public void generateStructures( public void generateStructures(
@Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "null", customHandler = NullableDimensionHandler.class) @Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "null", customHandler = NullableDimensionHandler.class)
IrisDimension dimension, IrisDimension dimension,
@@ -485,7 +491,7 @@ public class CommandDeveloper implements DecreeExecutor {
data.hotloaded(); data.hotloaded();
} }
@Decree(description = "Test") @Director(description = "Test")
public void packBenchmark( public void packBenchmark(
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld") @Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
IrisDimension dimension, IrisDimension dimension,
@@ -497,7 +503,7 @@ public class CommandDeveloper implements DecreeExecutor {
new IrisPackBenchmarking(dimension, radius, gui); new IrisPackBenchmarking(dimension, radius, gui);
} }
@Decree(description = "Upgrade to another Minecraft version") @Director(description = "Upgrade to another Minecraft version")
public void upgrade( public void upgrade(
@Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) { @Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) {
sender().sendMessage(C.GREEN + "Upgrading to " + version.getVersion() + "..."); sender().sendMessage(C.GREEN + "Upgrading to " + version.getVersion() + "...");
@@ -505,7 +511,7 @@ public class CommandDeveloper implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Done upgrading! You can now update your server version to " + version.getVersion()); sender().sendMessage(C.GREEN + "Done upgrading! You can now update your server version to " + version.getVersion());
} }
@Decree(description = "test") @Director(description = "test")
public void mca ( public void mca (
@Param(description = "String") String world) { @Param(description = "String") String world) {
try { try {
@@ -519,7 +525,550 @@ public class CommandDeveloper implements DecreeExecutor {
} }
@Decree(description = "UnloadChunks for good reasons.") @Director(description = "Delete nearby chunk blocks for regen testing", name = "delete-chunk", aliases = {"delchunk", "dc"}, origin = DirectorOrigin.PLAYER, sync = true)
public void deleteChunk(
@Param(description = "Radius in chunks around your current chunk", defaultValue = "0")
int radius,
@Param(description = "How many chunks to process in parallel (0 = auto)", aliases = {"threads", "concurrency"}, defaultValue = "0")
int parallelism
) {
if (radius < 0) {
sender().sendMessage(C.RED + "Radius must be 0 or greater.");
return;
}
World world = player().getWorld();
if (!IrisToolbelt.isIrisWorld(world)) {
sender().sendMessage(C.RED + "This is not an Iris world.");
return;
}
String worldKey = world.getName().toLowerCase(Locale.ROOT);
if (!ACTIVE_DELETE_CHUNK_WORLDS.add(worldKey)) {
sender().sendMessage(C.RED + "A delete-chunk run is already active for this world.");
return;
}
int threads = resolveDeleteChunkThreadCount(parallelism);
int centerX = player().getLocation().getBlockX() >> 4;
int centerZ = player().getLocation().getBlockZ() >> 4;
List<Position2> targets = buildDeleteChunkTargets(centerX, centerZ, radius);
int totalChunks = targets.size();
String runId = world.getName() + "-" + System.currentTimeMillis();
PlatformChunkGenerator access = IrisToolbelt.access(world);
if (access == null || access.getEngine() == null) {
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
sender().sendMessage(C.RED + "The engine access for this world is null.");
return;
}
art.arcane.iris.util.mantle.Mantle mantle = access.getEngine().getMantle().getMantle();
VolmitSender sender = sender();
sender.sendMessage(C.GREEN + "Deleting blocks in " + C.GOLD + totalChunks + C.GREEN + " chunk(s) with " + C.GOLD + threads + C.GREEN + " worker(s).");
if (J.isFolia()) {
sender.sendMessage(C.YELLOW + "Folia maintenance mode enabled for lock-safe chunk wipe + mantle purge.");
}
sender.sendMessage(C.YELLOW + "Delete-chunk run id: " + C.GOLD + runId + C.YELLOW + ".");
Iris.info("Delete-chunk run start: id=" + runId
+ " world=" + world.getName()
+ " center=" + centerX + "," + centerZ
+ " radius=" + radius
+ " workers=" + threads
+ " chunks=" + totalChunks);
Set<Thread> workerThreads = ConcurrentHashMap.newKeySet();
AtomicInteger workerCounter = new AtomicInteger();
ThreadFactory threadFactory = runnable -> {
Thread thread = new Thread(runnable, "Iris-DeleteChunk-" + runId + "-" + workerCounter.incrementAndGet());
thread.setDaemon(true);
workerThreads.add(thread);
return thread;
};
Thread orchestrator = new Thread(() -> runDeleteChunkOrchestrator(
sender,
world,
mantle,
targets,
threads,
runId,
worldKey,
workerThreads,
threadFactory
), "Iris-DeleteChunk-Orchestrator-" + runId);
orchestrator.setDaemon(true);
try {
orchestrator.start();
Iris.info("Delete-chunk worker dispatched on dedicated thread=" + orchestrator.getName() + " id=" + runId + ".");
} catch (Throwable e) {
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
sender.sendMessage(C.RED + "Failed to start delete-chunk worker thread. See console.");
Iris.reportError(e);
}
}
private int resolveDeleteChunkThreadCount(int parallelism) {
int threads = parallelism <= 0 ? Runtime.getRuntime().availableProcessors() : parallelism;
if (J.isFolia() && parallelism <= 0) {
threads = 1;
}
return Math.max(1, threads);
}
private List<Position2> buildDeleteChunkTargets(int centerX, int centerZ, int radius) {
int expected = (radius * 2 + 1) * (radius * 2 + 1);
List<Position2> targets = new ArrayList<>(expected);
for (int ring = 0; ring <= radius; ring++) {
for (int x = -ring; x <= ring; x++) {
for (int z = -ring; z <= ring; z++) {
if (Math.max(Math.abs(x), Math.abs(z)) != ring) {
continue;
}
targets.add(new Position2(centerX + x, centerZ + z));
}
}
}
return targets;
}
private void runDeleteChunkOrchestrator(
VolmitSender sender,
World world,
art.arcane.iris.util.mantle.Mantle mantle,
List<Position2> targets,
int threadCount,
String runId,
String worldKey,
Set<Thread> workerThreads,
ThreadFactory threadFactory
) {
long runStart = System.currentTimeMillis();
AtomicReference<String> phase = new AtomicReference<>("bootstrap");
AtomicLong phaseSince = new AtomicLong(runStart);
AtomicBoolean runDone = new AtomicBoolean(false);
Thread watchdog = createDeleteChunkSetupWatchdog(world, runId, runDone, phase, phaseSince);
watchdog.start();
IrisToolbelt.beginWorldMaintenance(world, "delete-chunk");
try (ThreadPoolExecutor pool = (ThreadPoolExecutor) Executors.newFixedThreadPool(threadCount, threadFactory)) {
setDeleteChunkPhase(phase, phaseSince, "dispatch", world, runId);
DeleteChunkSummary summary = executeDeleteChunkQueue(world, mantle, targets, pool, workerThreads, runId);
if (summary.failedChunks() <= 0) {
sender.sendMessage(C.GREEN + "Deleted blocks in " + C.GOLD + summary.successChunks() + C.GREEN + "/" + C.GOLD + summary.totalChunks() + C.GREEN + " chunk(s).");
return;
}
sender.sendMessage(C.RED + "Delete-chunk completed with " + C.GOLD + summary.failedChunks() + C.RED + " failed chunk(s).");
sender.sendMessage(C.YELLOW + "Successful chunks: " + C.GOLD + summary.successChunks() + C.YELLOW + "/" + C.GOLD + summary.totalChunks() + C.YELLOW + ".");
sender.sendMessage(C.YELLOW + "Retry attempts used: " + C.GOLD + summary.retryCount() + C.YELLOW + ".");
if (!summary.failedPreview().isEmpty()) {
sender.sendMessage(C.YELLOW + "Failed chunks sample: " + C.GOLD + summary.failedPreview());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
sender.sendMessage(C.RED + "Delete-chunk run was interrupted.");
Iris.warn("Delete-chunk run interrupted: id=" + runId + " world=" + world.getName());
} catch (Throwable e) {
sender.sendMessage(C.RED + "Delete-chunk run failed. See console.");
Iris.reportError(e);
} finally {
runDone.set(true);
watchdog.interrupt();
IrisToolbelt.endWorldMaintenance(world, "delete-chunk");
ACTIVE_DELETE_CHUNK_WORLDS.remove(worldKey);
Iris.info("Delete-chunk run closed: id=" + runId + " world=" + world.getName() + " totalMs=" + (System.currentTimeMillis() - runStart));
}
}
private DeleteChunkSummary executeDeleteChunkQueue(
World world,
art.arcane.iris.util.mantle.Mantle mantle,
List<Position2> targets,
ThreadPoolExecutor pool,
Set<Thread> workerThreads,
String runId
) throws InterruptedException {
ArrayDeque<DeleteChunkTask> pending = new ArrayDeque<>(targets.size());
long queuedAt = System.currentTimeMillis();
for (Position2 target : targets) {
pending.addLast(new DeleteChunkTask(target.getX(), target.getZ(), 1, queuedAt));
}
ConcurrentMap<String, DeleteChunkActiveTask> activeTasks = new ConcurrentHashMap<>();
ExecutorCompletionService<DeleteChunkResult> completion = new ExecutorCompletionService<>(pool);
List<Position2> failedChunks = new ArrayList<>();
int totalChunks = targets.size();
int successChunks = 0;
int failedCount = 0;
int retryCount = 0;
long submittedTasks = 0L;
long finishedTasks = 0L;
int completedChunks = 0;
int inFlight = 0;
int unchangedHeartbeats = 0;
int lastCompleted = -1;
long lastDump = 0L;
while (inFlight < pool.getMaximumPoolSize() && !pending.isEmpty()) {
DeleteChunkTask task = pending.removeFirst();
completion.submit(() -> runDeleteChunkTask(task, world, mantle, activeTasks));
inFlight++;
submittedTasks++;
}
while (completedChunks < totalChunks) {
Future<DeleteChunkResult> future = completion.poll(DELETE_CHUNK_HEARTBEAT_MS, TimeUnit.MILLISECONDS);
if (future == null) {
if (completedChunks == lastCompleted) {
unchangedHeartbeats++;
} else {
unchangedHeartbeats = 0;
lastCompleted = completedChunks;
}
Iris.warn("Delete-chunk heartbeat: id=" + runId
+ " completed=" + completedChunks + "/" + totalChunks
+ " remaining=" + (totalChunks - completedChunks)
+ " queued=" + pending.size()
+ " inFlight=" + inFlight
+ " submitted=" + submittedTasks
+ " finishedTasks=" + finishedTasks
+ " retries=" + retryCount
+ " failed=" + failedCount
+ " poolActive=" + pool.getActiveCount()
+ " poolQueue=" + pool.getQueue().size()
+ " poolDone=" + pool.getCompletedTaskCount()
+ " activeTasks=" + formatDeleteChunkActiveTasks(activeTasks));
if (unchangedHeartbeats >= 3 && System.currentTimeMillis() - lastDump >= 10000L) {
lastDump = System.currentTimeMillis();
Iris.warn("Delete-chunk appears stalled; dumping worker stack traces for id=" + runId + ".");
dumpDeleteChunkWorkerStacks(workerThreads, world.getName());
}
continue;
}
DeleteChunkResult result;
try {
result = future.get();
} catch (ExecutionException e) {
Throwable cause = e.getCause() == null ? e : e.getCause();
throw new IllegalStateException("Delete-chunk worker failed unexpectedly for run " + runId, cause);
}
inFlight--;
finishedTasks++;
long duration = result.finishedAtMs() - result.startedAtMs();
if (result.success()) {
completedChunks++;
successChunks++;
if (result.task().attempt() > 1) {
Iris.warn("Delete-chunk recovered after retry: id=" + runId
+ " chunk=" + result.task().chunkX() + "," + result.task().chunkZ()
+ " attempt=" + result.task().attempt()
+ " durationMs=" + duration);
} else if (duration >= 5000L) {
Iris.warn("Delete-chunk slow: id=" + runId
+ " chunk=" + result.task().chunkX() + "," + result.task().chunkZ()
+ " durationMs=" + duration
+ " loadedAtStart=" + result.loadedAtStart());
}
} else if (result.task().attempt() < DELETE_CHUNK_MAX_ATTEMPTS) {
retryCount++;
DeleteChunkTask retryTask = result.task().retry(System.currentTimeMillis());
pending.addLast(retryTask);
Iris.warn("Delete-chunk retry scheduled: id=" + runId
+ " chunk=" + result.task().chunkX() + "," + result.task().chunkZ()
+ " failedAttempt=" + result.task().attempt()
+ " nextAttempt=" + retryTask.attempt()
+ " error=" + result.errorSummary());
} else {
completedChunks++;
failedCount++;
Position2 failed = new Position2(result.task().chunkX(), result.task().chunkZ());
failedChunks.add(failed);
Iris.warn("Delete-chunk terminal failure: id=" + runId
+ " chunk=" + result.task().chunkX() + "," + result.task().chunkZ()
+ " attempts=" + result.task().attempt()
+ " error=" + result.errorSummary());
if (result.error() != null) {
Iris.reportError(result.error());
}
}
while (inFlight < pool.getMaximumPoolSize() && !pending.isEmpty()) {
DeleteChunkTask task = pending.removeFirst();
completion.submit(() -> runDeleteChunkTask(task, world, mantle, activeTasks));
inFlight++;
submittedTasks++;
}
}
String preview = formatDeleteChunkFailedPreview(failedChunks);
Iris.info("Delete-chunk run complete: id=" + runId
+ " world=" + world.getName()
+ " total=" + totalChunks
+ " success=" + successChunks
+ " failed=" + failedCount
+ " retries=" + retryCount
+ " submittedTasks=" + submittedTasks
+ " finishedTasks=" + finishedTasks
+ " failedPreview=" + preview);
return new DeleteChunkSummary(totalChunks, successChunks, failedCount, retryCount, preview);
}
private DeleteChunkResult runDeleteChunkTask(
DeleteChunkTask task,
World world,
art.arcane.iris.util.mantle.Mantle mantle,
ConcurrentMap<String, DeleteChunkActiveTask> activeTasks
) {
String worker = Thread.currentThread().getName();
long startedAt = System.currentTimeMillis();
boolean loadedAtStart = false;
try {
loadedAtStart = world.isChunkLoaded(task.chunkX(), task.chunkZ());
} catch (Throwable ignored) {
}
activeTasks.put(worker, new DeleteChunkActiveTask(task.chunkX(), task.chunkZ(), task.attempt(), startedAt, loadedAtStart));
try {
DeleteChunkRegionResult regionResult = wipeChunkRegion(world, task.chunkX(), task.chunkZ());
if (!regionResult.success()) {
return DeleteChunkResult.failure(task, worker, startedAt, System.currentTimeMillis(), loadedAtStart, regionResult.error());
}
mantle.deleteChunk(task.chunkX(), task.chunkZ());
return DeleteChunkResult.success(task, worker, startedAt, System.currentTimeMillis(), loadedAtStart);
} catch (Throwable e) {
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
return DeleteChunkResult.failure(task, worker, startedAt, System.currentTimeMillis(), loadedAtStart, e);
} finally {
activeTasks.remove(worker);
}
}
private DeleteChunkRegionResult wipeChunkRegion(World world, int chunkX, int chunkZ) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
AtomicReference<Throwable> failure = new AtomicReference<>();
if (!J.runRegion(world, chunkX, chunkZ, () -> {
try {
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
for (org.bukkit.entity.Entity entity : chunk.getEntities()) {
if (!(entity instanceof org.bukkit.entity.Player)) {
entity.remove();
}
}
int minY = world.getMinHeight();
int maxY = world.getMaxHeight();
for (int xx = 0; xx < 16; xx++) {
for (int zz = 0; zz < 16; zz++) {
for (int yy = minY; yy < maxY; yy++) {
chunk.getBlock(xx, yy, zz).setType(org.bukkit.Material.AIR, false);
}
}
}
} catch (Throwable e) {
failure.set(e);
} finally {
latch.countDown();
}
})) {
return DeleteChunkRegionResult.fail(new IllegalStateException("Failed to schedule region task for chunk " + chunkX + "," + chunkZ));
}
if (!latch.await(30, TimeUnit.SECONDS)) {
return DeleteChunkRegionResult.fail(new TimeoutException("Timed out waiting for region task at chunk " + chunkX + "," + chunkZ));
}
Throwable thrown = failure.get();
if (thrown != null) {
return DeleteChunkRegionResult.fail(thrown);
}
return DeleteChunkRegionResult.ok();
}
private Thread createDeleteChunkSetupWatchdog(
World world,
String runId,
AtomicBoolean runDone,
AtomicReference<String> phase,
AtomicLong phaseSince
) {
Thread watchdog = new Thread(() -> {
while (!runDone.get()) {
try {
Thread.sleep(DELETE_CHUNK_HEARTBEAT_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
if (!runDone.get()) {
long elapsed = System.currentTimeMillis() - phaseSince.get();
Iris.warn("Delete-chunk setup heartbeat: id=" + runId
+ " phase=" + phase.get()
+ " elapsedMs=" + elapsed
+ " world=" + world.getName());
}
}
}, "Iris-DeleteChunk-SetupWatchdog-" + runId);
watchdog.setDaemon(true);
return watchdog;
}
private void setDeleteChunkPhase(
AtomicReference<String> phase,
AtomicLong phaseSince,
String next,
World world,
String runId
) {
phase.set(next);
phaseSince.set(System.currentTimeMillis());
Iris.info("Delete-chunk phase: id=" + runId + " phase=" + next + " world=" + world.getName());
}
private String formatDeleteChunkFailedPreview(List<Position2> failedChunks) {
if (failedChunks.isEmpty()) {
return "[]";
}
StringBuilder builder = new StringBuilder("[");
int index = 0;
for (Position2 chunk : failedChunks) {
if (index > 0) {
builder.append(", ");
}
if (index >= 10) {
builder.append("...");
break;
}
builder.append(chunk.getX()).append(",").append(chunk.getZ());
index++;
}
builder.append("]");
return builder.toString();
}
private String formatDeleteChunkActiveTasks(ConcurrentMap<String, DeleteChunkActiveTask> activeTasks) {
if (activeTasks.isEmpty()) {
return "{}";
}
StringBuilder builder = new StringBuilder("{");
int count = 0;
long now = System.currentTimeMillis();
for (Map.Entry<String, DeleteChunkActiveTask> entry : activeTasks.entrySet()) {
if (count > 0) {
builder.append(", ");
}
if (count >= 8) {
builder.append("...");
break;
}
DeleteChunkActiveTask activeTask = entry.getValue();
builder.append(entry.getKey())
.append("=")
.append(activeTask.chunkX())
.append(",")
.append(activeTask.chunkZ())
.append("@")
.append(activeTask.attempt())
.append("/")
.append(now - activeTask.startedAtMs())
.append("ms")
.append(activeTask.loadedAtStart() ? ":loaded" : ":cold");
count++;
}
builder.append("}");
return builder.toString();
}
private void dumpDeleteChunkWorkerStacks(Set<Thread> explicitThreads, String worldName) {
Set<Thread> threads = new LinkedHashSet<>();
threads.addAll(explicitThreads);
for (Thread thread : Thread.getAllStackTraces().keySet()) {
if (thread == null || !thread.isAlive()) {
continue;
}
String name = thread.getName();
if (name.startsWith("Iris-DeleteChunk-")
|| name.startsWith("Iris EngineSVC-")
|| name.startsWith("Iris World Manager")
|| name.contains(worldName)) {
threads.add(thread);
}
}
for (Thread thread : threads) {
if (thread == null || !thread.isAlive()) {
continue;
}
Iris.warn("Delete-chunk worker thread=" + thread.getName() + " state=" + thread.getState());
StackTraceElement[] trace = thread.getStackTrace();
int limit = Math.min(trace.length, DELETE_CHUNK_STACK_LIMIT);
for (int i = 0; i < limit; i++) {
Iris.warn(" at " + trace[i]);
}
}
}
private record DeleteChunkTask(int chunkX, int chunkZ, int attempt, long queuedAtMs) {
private DeleteChunkTask retry(long now) {
return new DeleteChunkTask(chunkX, chunkZ, attempt + 1, now);
}
}
private record DeleteChunkActiveTask(int chunkX, int chunkZ, int attempt, long startedAtMs, boolean loadedAtStart) {
}
private record DeleteChunkResult(
DeleteChunkTask task,
String worker,
long startedAtMs,
long finishedAtMs,
boolean loadedAtStart,
boolean success,
Throwable error
) {
private static DeleteChunkResult success(DeleteChunkTask task, String worker, long startedAtMs, long finishedAtMs, boolean loadedAtStart) {
return new DeleteChunkResult(task, worker, startedAtMs, finishedAtMs, loadedAtStart, true, null);
}
private static DeleteChunkResult failure(DeleteChunkTask task, String worker, long startedAtMs, long finishedAtMs, boolean loadedAtStart, Throwable error) {
return new DeleteChunkResult(task, worker, startedAtMs, finishedAtMs, loadedAtStart, false, error);
}
private String errorSummary() {
if (error == null) {
return "unknown";
}
String message = error.getMessage();
if (message == null || message.isEmpty()) {
return error.getClass().getSimpleName();
}
return error.getClass().getSimpleName() + ": " + message;
}
}
private record DeleteChunkRegionResult(boolean success, Throwable error) {
private static DeleteChunkRegionResult ok() {
return new DeleteChunkRegionResult(true, null);
}
private static DeleteChunkRegionResult fail(Throwable error) {
return new DeleteChunkRegionResult(false, error);
}
}
private record DeleteChunkSummary(int totalChunks, int successChunks, int failedChunks, int retryCount, String failedPreview) {
}
@Director(description = "UnloadChunks for good reasons.")
public void unloadchunks() { public void unloadchunks() {
List<World> IrisWorlds = new ArrayList<>(); List<World> IrisWorlds = new ArrayList<>();
int chunksUnloaded = 0; int chunksUnloaded = 0;
@@ -545,7 +1094,7 @@ public class CommandDeveloper implements DecreeExecutor {
} }
@Decree @Director
public void objects(@Param(defaultValue = "overworld") IrisDimension dimension) { public void objects(@Param(defaultValue = "overworld") IrisDimension dimension) {
var loader = dimension.getLoader().getObjectLoader(); var loader = dimension.getLoader().getObjectLoader();
var sender = sender(); var sender = sender();
@@ -562,7 +1111,7 @@ public class CommandDeveloper implements DecreeExecutor {
sender.sendMessage(C.RED + "Failed to load " + failed.get() + " of " + keys.length + " objects"); sender.sendMessage(C.RED + "Failed to load " + failed.get() + " of " + keys.length + " objects");
} }
@Decree(description = "Test", aliases = {"ip"}) @Director(description = "Test", aliases = {"ip"})
public void network() { public void network() {
try { try {
Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
@@ -578,7 +1127,7 @@ public class CommandDeveloper implements DecreeExecutor {
} }
} }
@Decree(description = "Test the compression algorithms") @Director(description = "Test the compression algorithms")
public void compression( public void compression(
@Param(description = "base IrisWorld") World world, @Param(description = "base IrisWorld") World world,
@Param(description = "raw TectonicPlate File") String path, @Param(description = "raw TectonicPlate File") String path,
@@ -661,4 +1210,3 @@ public class CommandDeveloper implements DecreeExecutor {
}); });
} }
} }
@@ -22,15 +22,15 @@ import art.arcane.iris.Iris;
import art.arcane.iris.core.service.StudioSVC; import art.arcane.iris.core.service.StudioSVC;
import art.arcane.iris.engine.object.*; import art.arcane.iris.engine.object.*;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import java.awt.*; import java.awt.*;
@Decree(name = "edit", origin = DecreeOrigin.PLAYER, studio = true, description = "Edit something") @Director(name = "edit", origin = DirectorOrigin.PLAYER, studio = true, description = "Edit something")
public class CommandEdit implements DecreeExecutor { public class CommandEdit implements DecreeExecutor {
private boolean noStudio() { private boolean noStudio() {
@@ -60,7 +60,7 @@ public class CommandEdit implements DecreeExecutor {
} }
@Decree(description = "Edit the biome you specified", aliases = {"b"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the biome you specified", aliases = {"b"}, origin = DirectorOrigin.PLAYER)
public void biome(@Param(contextual = false, description = "The biome to edit") IrisBiome biome) { public void biome(@Param(contextual = false, description = "The biome to edit") IrisBiome biome) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -78,7 +78,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the region you specified", aliases = {"r"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the region you specified", aliases = {"r"}, origin = DirectorOrigin.PLAYER)
public void region(@Param(contextual = false, description = "The region to edit") IrisRegion region) { public void region(@Param(contextual = false, description = "The region to edit") IrisRegion region) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -96,7 +96,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the dimension you specified", aliases = {"d"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the dimension you specified", aliases = {"d"}, origin = DirectorOrigin.PLAYER)
public void dimension(@Param(contextual = false, description = "The dimension to edit") IrisDimension dimension) { public void dimension(@Param(contextual = false, description = "The dimension to edit") IrisDimension dimension) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -114,7 +114,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the cave file you specified", aliases = {"c"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the cave file you specified", aliases = {"c"}, origin = DirectorOrigin.PLAYER)
public void cave(@Param(contextual = false, description = "The cave to edit") IrisCave cave) { public void cave(@Param(contextual = false, description = "The cave to edit") IrisCave cave) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -132,7 +132,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the structure file you specified", aliases = {"jigsawstructure", "structure"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the structure file you specified", aliases = {"jigsawstructure", "structure"}, origin = DirectorOrigin.PLAYER)
public void jigsaw(@Param(contextual = false, description = "The jigsaw structure to edit") IrisJigsawStructure jigsaw) { public void jigsaw(@Param(contextual = false, description = "The jigsaw structure to edit") IrisJigsawStructure jigsaw) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -150,7 +150,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the pool file you specified", aliases = {"jigsawpool", "pool"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the pool file you specified", aliases = {"jigsawpool", "pool"}, origin = DirectorOrigin.PLAYER)
public void jigsawPool(@Param(contextual = false, description = "The jigsaw pool to edit") IrisJigsawPool pool) { public void jigsawPool(@Param(contextual = false, description = "The jigsaw pool to edit") IrisJigsawPool pool) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -168,7 +168,7 @@ public class CommandEdit implements DecreeExecutor {
} }
} }
@Decree(description = "Edit the jigsaw piece file you specified", aliases = {"jigsawpiece", "piece"}, origin = DecreeOrigin.PLAYER) @Director(description = "Edit the jigsaw piece file you specified", aliases = {"jigsawpiece", "piece"}, origin = DirectorOrigin.PLAYER)
public void jigsawPiece(@Param(contextual = false, description = "The jigsaw piece to edit") IrisJigsawPiece piece) { public void jigsawPiece(@Param(contextual = false, description = "The jigsaw piece to edit") IrisJigsawPiece piece) {
if (noStudio()) { if (noStudio()) {
return; return;
@@ -23,15 +23,15 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisJigsawStructure; import art.arcane.iris.engine.object.IrisJigsawStructure;
import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler; import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
@Decree(name = "find", origin = DecreeOrigin.PLAYER, description = "Iris Find commands", aliases = "goto") @Director(name = "find", origin = DirectorOrigin.PLAYER, description = "Iris Find commands", aliases = "goto")
public class CommandFind implements DecreeExecutor { public class CommandFind implements DecreeExecutor {
@Decree(description = "Find a biome") @Director(description = "Find a biome")
public void biome( public void biome(
@Param(description = "The biome to look for") @Param(description = "The biome to look for")
IrisBiome biome, IrisBiome biome,
@@ -48,7 +48,7 @@ public class CommandFind implements DecreeExecutor {
e.gotoBiome(biome, player(), teleport); e.gotoBiome(biome, player(), teleport);
} }
@Decree(description = "Find a region") @Director(description = "Find a region")
public void region( public void region(
@Param(description = "The region to look for") @Param(description = "The region to look for")
IrisRegion region, IrisRegion region,
@@ -65,7 +65,7 @@ public class CommandFind implements DecreeExecutor {
e.gotoRegion(region, player(), teleport); e.gotoRegion(region, player(), teleport);
} }
@Decree(description = "Find a structure") @Director(description = "Find a structure")
public void structure( public void structure(
@Param(description = "The structure to look for") @Param(description = "The structure to look for")
IrisJigsawStructure structure, IrisJigsawStructure structure,
@@ -82,7 +82,7 @@ public class CommandFind implements DecreeExecutor {
e.gotoJigsaw(structure, player(), teleport); e.gotoJigsaw(structure, player(), teleport);
} }
@Decree(description = "Find a point of interest.") @Director(description = "Find a point of interest.")
public void poi( public void poi(
@Param(description = "The type of PoI to look for.") @Param(description = "The type of PoI to look for.")
String type, String type,
@@ -98,7 +98,7 @@ public class CommandFind implements DecreeExecutor {
e.gotoPOI(type, player(), teleport); e.gotoPOI(type, player(), teleport);
} }
@Decree(description = "Find an object") @Director(description = "Find an object")
public void object( public void object(
@Param(description = "The object to look for", customHandler = ObjectHandler.class) @Param(description = "The object to look for", customHandler = ObjectHandler.class)
String object, String object,
File diff suppressed because it is too large Load Diff
@@ -28,9 +28,9 @@ import art.arcane.iris.engine.object.IrisJigsawStructure;
import art.arcane.iris.engine.object.IrisObject; import art.arcane.iris.engine.object.IrisObject;
import art.arcane.iris.engine.object.IrisPosition; import art.arcane.iris.engine.object.IrisPosition;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler; import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
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;
@@ -40,9 +40,9 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import java.io.File; import java.io.File;
@Decree(name = "jigsaw", origin = DecreeOrigin.PLAYER, studio = true, description = "Iris jigsaw commands") @Director(name = "jigsaw", origin = DirectorOrigin.PLAYER, studio = true, description = "Iris jigsaw commands")
public class CommandJigsaw implements DecreeExecutor { public class CommandJigsaw implements DecreeExecutor {
@Decree(description = "Edit a jigsaw piece") @Director(description = "Edit a jigsaw piece")
public void edit( public void edit(
@Param(description = "The jigsaw piece to edit") @Param(description = "The jigsaw piece to edit")
IrisJigsawPiece piece IrisJigsawPiece piece
@@ -51,7 +51,7 @@ public class CommandJigsaw implements DecreeExecutor {
new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject(), data()), dest); new JigsawEditor(player(), piece, IrisData.loadAnyObject(piece.getObject(), data()), dest);
} }
@Decree(description = "Place a jigsaw structure") @Director(description = "Place a jigsaw structure")
public void place( public void place(
@Param(description = "The jigsaw structure to place") @Param(description = "The jigsaw structure to place")
IrisJigsawStructure structure IrisJigsawStructure structure
@@ -69,7 +69,7 @@ public class CommandJigsaw implements DecreeExecutor {
} }
} }
@Decree(description = "Create a jigsaw piece") @Director(description = "Create a jigsaw piece")
public void create( public void create(
@Param(description = "The name of the jigsaw piece") @Param(description = "The name of the jigsaw piece")
String piece, String piece,
@@ -93,7 +93,7 @@ public class CommandJigsaw implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Remember to use /iris jigsaw save"); sender().sendMessage(C.GREEN + "Remember to use /iris jigsaw save");
} }
@Decree(description = "Exit the current jigsaw editor") @Director(description = "Exit the current jigsaw editor")
public void exit() { public void exit() {
JigsawEditor editor = JigsawEditor.editors.get(player()); JigsawEditor editor = JigsawEditor.editors.get(player());
@@ -106,7 +106,7 @@ public class CommandJigsaw implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Exited Jigsaw Editor"); sender().sendMessage(C.GREEN + "Exited Jigsaw Editor");
} }
@Decree(description = "Save & Exit the current jigsaw editor") @Director(description = "Save & Exit the current jigsaw editor")
public void save() { public void save() {
JigsawEditor editor = JigsawEditor.editors.get(player()); JigsawEditor editor = JigsawEditor.editors.get(player());
@@ -25,8 +25,8 @@ import art.arcane.iris.core.pregenerator.LazyPregenerator;
import art.arcane.iris.core.pregenerator.PregenTask; import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import art.arcane.iris.util.math.Position2; import art.arcane.iris.util.math.Position2;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
@@ -36,10 +36,10 @@ import org.bukkit.util.Vector;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@Decree(name = "lazypregen", aliases = "lazy", description = "Pregenerate your Iris worlds!") @Director(name = "lazypregen", aliases = "lazy", description = "Pregenerate your Iris worlds!")
public class CommandLazyPregen implements DecreeExecutor { public class CommandLazyPregen implements DecreeExecutor {
public String worldName; public String worldName;
@Decree(description = "Pregenerate a world") @Director(description = "Pregenerate a world")
public void start( public void start(
@Param(description = "The radius of the pregen in blocks", aliases = "size") @Param(description = "The radius of the pregen in blocks", aliases = "size")
int radius, int radius,
@@ -92,7 +92,7 @@ public class CommandLazyPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Stop the active pregeneration task", aliases = "x") @Director(description = "Stop the active pregeneration task", aliases = "x")
public void stop( public void stop(
@Param(aliases = "world", description = "The world to pause") @Param(aliases = "world", description = "The world to pause")
World world World world
@@ -105,7 +105,7 @@ public class CommandLazyPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) @Director(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
public void pause( public void pause(
@Param(aliases = "world", description = "The world to pause") @Param(aliases = "world", description = "The world to pause")
World world World world
@@ -31,9 +31,9 @@ import art.arcane.volmlib.util.data.Cuboid;
import art.arcane.iris.util.data.IrisCustomData; import art.arcane.iris.util.data.IrisCustomData;
import art.arcane.iris.util.data.registry.Materials; import art.arcane.iris.util.data.registry.Materials;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler; import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import art.arcane.iris.util.math.Direction; import art.arcane.iris.util.math.Direction;
@@ -49,7 +49,7 @@ import java.io.IOException;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.*; import java.util.*;
@Decree(name = "object", aliases = "o", origin = DecreeOrigin.PLAYER, studio = true, description = "Iris object manipulation") @Director(name = "object", aliases = "o", origin = DirectorOrigin.PLAYER, studio = true, description = "Iris object manipulation")
public class CommandObject implements DecreeExecutor { public class CommandObject implements DecreeExecutor {
private static final Set<Material> skipBlocks = Set.of(Materials.GRASS, Material.SNOW, Material.VINE, Material.TORCH, Material.DEAD_BUSH, private static final Set<Material> skipBlocks = Set.of(Materials.GRASS, Material.SNOW, Material.VINE, Material.TORCH, Material.DEAD_BUSH,
@@ -140,7 +140,7 @@ public class CommandObject implements DecreeExecutor {
}; };
} }
@Decree(description = "Check the composition of an object") @Director(description = "Check the composition of an object")
public void analyze( public void analyze(
@Param(description = "The object to analyze", customHandler = ObjectHandler.class) @Param(description = "The object to analyze", customHandler = ObjectHandler.class)
String object String object
@@ -208,7 +208,7 @@ public class CommandObject implements DecreeExecutor {
} }
} }
@Decree(description = "Shrink an object to its minimum size") @Director(description = "Shrink an object to its minimum size")
public void shrink(@Param(description = "The object to shrink", customHandler = ObjectHandler.class) String object) { public void shrink(@Param(description = "The object to shrink", customHandler = ObjectHandler.class) String object) {
IrisObject o = IrisData.loadAnyObject(object, data()); IrisObject o = IrisData.loadAnyObject(object, data());
sender().sendMessage("Current Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD()); sender().sendMessage("Current Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD());
@@ -222,7 +222,7 @@ public class CommandObject implements DecreeExecutor {
} }
} }
@Decree(description = "Convert .schem files in the 'convert' folder to .iob files.") @Director(description = "Convert .schem files in the 'convert' folder to .iob files.")
public void convert () { public void convert () {
try { try {
IrisConverter.convertSchematics(sender()); IrisConverter.convertSchematics(sender());
@@ -232,13 +232,13 @@ public class CommandObject implements DecreeExecutor {
} }
@Decree(description = "Get a powder that reveals objects", studio = true, aliases = "d") @Director(description = "Get a powder that reveals objects", studio = true, aliases = "d")
public void dust() { public void dust() {
player().getInventory().addItem(WandSVC.createDust()); player().getInventory().addItem(WandSVC.createDust());
sender().playSound(Sound.AMBIENT_SOUL_SAND_VALLEY_ADDITIONS, 1f, 1.5f); sender().playSound(Sound.AMBIENT_SOUL_SAND_VALLEY_ADDITIONS, 1f, 1.5f);
} }
@Decree(description = "Contract a selection based on your looking direction", aliases = "-") @Director(description = "Contract a selection based on your looking direction", aliases = "-")
public void contract( public void contract(
@Param(description = "The amount to inset by", defaultValue = "1") @Param(description = "The amount to inset by", defaultValue = "1")
int amount int amount
@@ -267,7 +267,7 @@ public class CommandObject implements DecreeExecutor {
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f); sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
} }
@Decree(description = "Set point 1 to look", aliases = "p1") @Director(description = "Set point 1 to look", aliases = "p1")
public void position1( public void position1(
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true") @Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
boolean here boolean here
@@ -293,7 +293,7 @@ public class CommandObject implements DecreeExecutor {
} }
} }
@Decree(description = "Set point 2 to look", aliases = "p2") @Director(description = "Set point 2 to look", aliases = "p2")
public void position2( public void position2(
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true") @Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
boolean here boolean here
@@ -320,7 +320,7 @@ public class CommandObject implements DecreeExecutor {
} }
} }
@Decree(description = "Paste an object", sync = true) @Director(description = "Paste an object", sync = true)
public void paste( public void paste(
@Param(description = "The object to paste", customHandler = ObjectHandler.class) @Param(description = "The object to paste", customHandler = ObjectHandler.class)
String object, String object,
@@ -381,7 +381,7 @@ public class CommandObject implements DecreeExecutor {
} }
} }
@Decree(description = "Save an object") @Director(description = "Save an object")
public void save( public void save(
@Param(description = "The dimension to store the object in", contextual = true) @Param(description = "The dimension to store the object in", contextual = true)
IrisDimension dimension, IrisDimension dimension,
@@ -416,7 +416,7 @@ public class CommandObject implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Successfully object to saved: " + dimension.getLoadKey() + "/objects/" + name); sender().sendMessage(C.GREEN + "Successfully object to saved: " + dimension.getLoadKey() + "/objects/" + name);
} }
@Decree(description = "Shift a selection in your looking direction", aliases = "-") @Director(description = "Shift a selection in your looking direction", aliases = "-")
public void shift( public void shift(
@Param(description = "The amount to shift by", defaultValue = "1") @Param(description = "The amount to shift by", defaultValue = "1")
int amount int amount
@@ -447,7 +447,7 @@ public class CommandObject implements DecreeExecutor {
sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f); sender().playSound(Sound.ENTITY_ITEM_FRAME_ROTATE_ITEM, 1f, 0.55f);
} }
@Decree(description = "Undo a number of pastes", aliases = "-") @Director(description = "Undo a number of pastes", aliases = "-")
public void undo( public void undo(
@Param(description = "The amount of pastes to undo", defaultValue = "1") @Param(description = "The amount of pastes to undo", defaultValue = "1")
int amount int amount
@@ -458,7 +458,7 @@ public class CommandObject implements DecreeExecutor {
sender().sendMessage(C.BLUE + "Reverted " + actualReverts + C.BLUE +" pastes!"); sender().sendMessage(C.BLUE + "Reverted " + actualReverts + C.BLUE +" pastes!");
} }
@Decree(description = "Gets an object wand and grabs the current WorldEdit selection.", aliases = "we", origin = DecreeOrigin.PLAYER, studio = true) @Director(description = "Gets an object wand and grabs the current WorldEdit selection.", aliases = "we", origin = DirectorOrigin.PLAYER, studio = true)
public void we() { public void we() {
if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) { if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) {
sender().sendMessage(C.RED + "You can't get a WorldEdit selection without WorldEdit, you know."); sender().sendMessage(C.RED + "You can't get a WorldEdit selection without WorldEdit, you know.");
@@ -476,14 +476,14 @@ public class CommandObject implements DecreeExecutor {
sender().sendMessage(C.GREEN + "A fresh wand with your current WorldEdit selection on it!"); sender().sendMessage(C.GREEN + "A fresh wand with your current WorldEdit selection on it!");
} }
@Decree(description = "Get an object wand", sync = true) @Director(description = "Get an object wand", sync = true)
public void wand() { public void wand() {
player().getInventory().addItem(WandSVC.createWand()); player().getInventory().addItem(WandSVC.createWand());
sender().playSound(Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f, 1.5f); sender().playSound(Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f, 1.5f);
sender().sendMessage(C.GREEN + "Poof! Good luck building!"); sender().sendMessage(C.GREEN + "Poof! Good luck building!");
} }
@Decree(name = "x&y", description = "Autoselect up, down & out", sync = true) @Director(name = "x&y", description = "Autoselect up, down & out", sync = true)
public void xay() { public void xay() {
if (!WandSVC.isHoldingWand(player())) { if (!WandSVC.isHoldingWand(player())) {
sender().sendMessage(C.YELLOW + "Hold your wand!"); sender().sendMessage(C.YELLOW + "Hold your wand!");
@@ -534,7 +534,7 @@ public class CommandObject implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Auto-select complete!"); sender().sendMessage(C.GREEN + "Auto-select complete!");
} }
@Decree(name = "x+y", description = "Autoselect up & out", sync = true) @Director(name = "x+y", description = "Autoselect up & out", sync = true)
public void xpy() { public void xpy() {
if (!WandSVC.isHoldingWand(player())) { if (!WandSVC.isHoldingWand(player())) {
sender().sendMessage(C.YELLOW + "Hold your wand!"); sender().sendMessage(C.YELLOW + "Hold your wand!");
@@ -23,16 +23,16 @@ import art.arcane.iris.core.gui.PregeneratorJob;
import art.arcane.iris.core.pregenerator.PregenTask; import art.arcane.iris.core.pregenerator.PregenTask;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import art.arcane.iris.util.math.Position2; import art.arcane.iris.util.math.Position2;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.util.Vector; import org.bukkit.util.Vector;
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!") @Director(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
public class CommandPregen implements DecreeExecutor { public class CommandPregen implements DecreeExecutor {
@Decree(description = "Pregenerate a world") @Director(description = "Pregenerate a world")
public void start( public void start(
@Param(description = "The radius of the pregen in blocks", aliases = "size") @Param(description = "The radius of the pregen in blocks", aliases = "size")
int radius, int radius,
@@ -66,7 +66,7 @@ public class CommandPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Stop the active pregeneration task", aliases = "x") @Director(description = "Stop the active pregeneration task", aliases = "x")
public void stop() { public void stop() {
if (PregeneratorJob.shutdownInstance()) { if (PregeneratorJob.shutdownInstance()) {
Iris.info( C.BLUE + "Finishing up mca region..."); Iris.info( C.BLUE + "Finishing up mca region...");
@@ -75,7 +75,7 @@ public class CommandPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) @Director(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
public void pause() { public void pause() {
if (PregeneratorJob.pauseResume()) { if (PregeneratorJob.pauseResume()) {
sender().sendMessage(C.GREEN + "Paused/unpaused pregeneration task, now: " + (PregeneratorJob.isPaused() ? "Paused" : "Running") + "."); sender().sendMessage(C.GREEN + "Paused/unpaused pregeneration task, now: " + (PregeneratorJob.isPaused() ? "Paused" : "Running") + ".");
@@ -37,9 +37,9 @@ import art.arcane.iris.util.decree.DecreeContext;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.iris.util.decree.handlers.DimensionHandler; import art.arcane.iris.util.decree.handlers.DimensionHandler;
import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler; import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
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 art.arcane.volmlib.util.function.Function2; import art.arcane.volmlib.util.function.Function2;
@@ -79,12 +79,11 @@ import java.time.temporal.ChronoUnit;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date; import java.util.Date;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier; import java.util.function.Supplier;
@Decree(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true) @Director(name = "studio", aliases = {"std", "s"}, description = "Studio Commands", studio = true)
public class CommandStudio implements DecreeExecutor { public class CommandStudio implements DecreeExecutor {
private CommandFind find; private CommandFind find;
private CommandEdit edit; private CommandEdit edit;
@@ -95,7 +94,7 @@ public class CommandStudio implements DecreeExecutor {
} }
//TODO fix pack trimming //TODO fix pack trimming
@Decree(description = "Download a project.", aliases = "dl") @Director(description = "Download a project.", aliases = "dl")
public void download( public void download(
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project") @Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
String pack, String pack,
@@ -109,7 +108,7 @@ public class CommandStudio implements DecreeExecutor {
new CommandIris().download(pack, branch, overwrite); new CommandIris().download(pack, branch, overwrite);
} }
@Decree(description = "Open a new studio world", aliases = "o", sync = true) @Director(description = "Open a new studio world", aliases = "o", sync = true)
public void open( public void open(
@Param(defaultValue = "default", description = "The dimension to open a studio for", aliases = "dim", customHandler = DimensionHandler.class) @Param(defaultValue = "default", description = "The dimension to open a studio for", aliases = "dim", customHandler = DimensionHandler.class)
IrisDimension dimension, IrisDimension dimension,
@@ -126,7 +125,7 @@ public class CommandStudio implements DecreeExecutor {
Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey()); Iris.service(StudioSVC.class).open(sender(), seed, dimension.getLoadKey());
} }
@Decree(description = "Open VSCode for a dimension", aliases = {"vsc", "edit"}) @Director(description = "Open VSCode for a dimension", aliases = {"vsc", "edit"})
public void vscode( public void vscode(
@Param(defaultValue = "default", description = "The dimension to open VSCode for", aliases = "dim", customHandler = DimensionHandler.class) @Param(defaultValue = "default", description = "The dimension to open VSCode for", aliases = "dim", customHandler = DimensionHandler.class)
IrisDimension dimension IrisDimension dimension
@@ -135,7 +134,7 @@ public class CommandStudio implements DecreeExecutor {
Iris.service(StudioSVC.class).openVSCode(sender(), dimension.getLoadKey()); Iris.service(StudioSVC.class).openVSCode(sender(), dimension.getLoadKey());
} }
@Decree(description = "Close an open studio project", aliases = {"x", "c"}, sync = true) @Director(description = "Close an open studio project", aliases = {"x", "c"}, sync = true)
public void close() { public void close() {
if (!Iris.service(StudioSVC.class).isProjectOpen()) { if (!Iris.service(StudioSVC.class).isProjectOpen()) {
sender().sendMessage(C.RED + "No open studio projects."); sender().sendMessage(C.RED + "No open studio projects.");
@@ -146,7 +145,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Project Closed."); sender().sendMessage(C.GREEN + "Project Closed.");
} }
@Decree(description = "Create a new studio project", aliases = "+", sync = true) @Director(description = "Create a new studio project", aliases = "+", sync = true)
public void create( public void create(
@Param(description = "The name of this new Iris Project.") @Param(description = "The name of this new Iris Project.")
String name, String name,
@@ -163,7 +162,7 @@ public class CommandStudio implements DecreeExecutor {
} }
} }
@Decree(description = "Get the version of a pack") @Director(description = "Get the version of a pack")
public void version( public void version(
@Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true, customHandler = DimensionHandler.class) @Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true, customHandler = DimensionHandler.class)
IrisDimension dimension IrisDimension dimension
@@ -171,7 +170,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "The \"" + dimension.getName() + "\" pack has version: " + dimension.getVersion()); sender().sendMessage(C.GREEN + "The \"" + dimension.getName() + "\" pack has version: " + dimension.getVersion());
} }
@Decree(name = "regen", description = "Regenerate nearby chunks.", aliases = "rg", sync = true, origin = DecreeOrigin.PLAYER) @Director(name = "regen", description = "Regenerate nearby chunks.", aliases = "rg", sync = true, origin = DirectorOrigin.PLAYER)
public void regen( public void regen(
@Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5") @Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5")
int radius int radius
@@ -183,46 +182,60 @@ public class CommandStudio implements DecreeExecutor {
VolmitSender sender = sender(); VolmitSender sender = sender();
var loc = player().getLocation().clone(); var loc = player().getLocation().clone();
final int threadCount = J.isFolia() ? 1 : Runtime.getRuntime().availableProcessors();
J.a(() -> { String orchestratorName = "Iris-Studio-Regen-Orchestrator-" + world.getName() + "-" + System.nanoTime();
Thread orchestrator = new Thread(() -> {
PlatformChunkGenerator plat = IrisToolbelt.access(world); PlatformChunkGenerator plat = IrisToolbelt.access(world);
Engine engine = plat.getEngine(); Engine engine = plat.getEngine();
DecreeContext.touch(sender); DecreeContext.touch(sender);
IrisToolbelt.beginWorldMaintenance(world, "studio-regen");
try (SyncExecutor executor = new SyncExecutor(20); try (SyncExecutor executor = new SyncExecutor(20);
var service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) var service = Executors.newFixedThreadPool(threadCount)
) { ) {
int x = loc.getBlockX() >> 4; int x = loc.getBlockX() >> 4;
int z = loc.getBlockZ() >> 4; int z = loc.getBlockZ() >> 4;
int rad = engine.getMantle().getRadius(); int rad = 0;
var mantle = engine.getMantle().getMantle();
var chunkMap = new KMap<Position2, MantleChunk>(); var chunkMap = new KMap<Position2, MantleChunk>();
ParallelRadiusJob prep = new ParallelRadiusJob(Integer.MAX_VALUE, service) { boolean foliaFastRegen = J.isFolia();
@Override if (foliaFastRegen) {
protected void execute(int rX, int rZ) { sender.sendMessage(C.YELLOW + "Folia safe default: using 1 regen worker in studio.");
if (Math.abs(rX) <= radius && Math.abs(rZ) <= radius) { }
mantle.deleteChunk(rX + x, rZ + z); if (!foliaFastRegen) {
return; rad = engine.getMantle().getRadius();
final var mantle = engine.getMantle().getMantle();
ParallelRadiusJob prep = new ParallelRadiusJob(threadCount, service) {
@Override
protected void execute(int rX, int rZ) {
if (Math.abs(rX) <= radius && Math.abs(rZ) <= radius) {
mantle.deleteChunk(rX + x, rZ + z);
return;
}
rX += x;
rZ += z;
chunkMap.put(new Position2(rX, rZ), mantle.getChunk(rX, rZ));
mantle.deleteChunk(rX, rZ);
} }
rX += x;
rZ += z;
chunkMap.put(new Position2(rX, rZ), mantle.getChunk(rX, rZ));
mantle.deleteChunk(rX, rZ);
}
@Override @Override
public String getName() { public String getName() {
return "Preparing Mantle"; return "Preparing Mantle";
} }
}.retarget(radius + rad, 0, 0); }.retarget(radius + rad, 0, 0);
CountDownLatch pLatch = new CountDownLatch(1); sender.sendMessage(C.YELLOW + "Preparing mantle data for studio regen...");
prep.execute(sender(), pLatch::countDown); prep.execute();
pLatch.await(); } else {
sender.sendMessage(C.YELLOW + "Folia fast regen: skipping outer mantle preservation stage.");
}
ParallelRadiusJob job = new ParallelRadiusJob(Integer.MAX_VALUE, service) { ParallelRadiusJob job = new ParallelRadiusJob(threadCount, service) {
@Override @Override
protected void execute(int x, int z) { protected void execute(int x, int z) {
if (foliaFastRegen) {
Iris.verbose("Folia fast studio regen skipping mantle delete for " + x + "," + z + ".");
}
plat.injectChunkReplacement(world, x, z, executor); plat.injectChunkReplacement(world, x, z, executor);
} }
@@ -231,28 +244,38 @@ public class CommandStudio implements DecreeExecutor {
return "Regenerating"; return "Regenerating";
} }
}.retarget(radius, x, z); }.retarget(radius, x, z);
CountDownLatch latch = new CountDownLatch(1); job.execute();
job.execute(sender(), latch::countDown);
latch.await();
chunkMap.forEach((pos, chunk) -> if (!foliaFastRegen) {
mantle.getChunk(pos.getX(), pos.getZ()).copyFrom(chunk)); var mantle = engine.getMantle().getMantle();
chunkMap.forEach((pos, chunk) ->
mantle.getChunk(pos.getX(), pos.getZ()).copyFrom(chunk));
}
} catch (Throwable e) { } catch (Throwable e) {
sender().sendMessage("Error while regenerating chunks"); sender().sendMessage("Error while regenerating chunks");
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
IrisToolbelt.endWorldMaintenance(world, "studio-regen");
DecreeContext.remove(); DecreeContext.remove();
} }
}); }, orchestratorName);
orchestrator.setDaemon(true);
try {
orchestrator.start();
Iris.info("Studio regen worker dispatched on dedicated thread=" + orchestratorName + ".");
} catch (Throwable e) {
sender.sendMessage(C.RED + "Failed to start studio regen worker thread. See console.");
Iris.reportError(e);
}
} }
@Decree(description = "Convert objects in the \"convert\" folder") @Director(description = "Convert objects in the \"convert\" folder")
public void convert() { public void convert() {
Iris.service(ConversionSVC.class).check(sender()); Iris.service(ConversionSVC.class).check(sender());
//IrisConverter.convertSchematics(sender()); //IrisConverter.convertSchematics(sender());
} }
@Decree(description = "Execute a script", aliases = "run", origin = DecreeOrigin.PLAYER) @Director(description = "Execute a script", aliases = "run", origin = DirectorOrigin.PLAYER)
public void execute( public void execute(
@Param(description = "The script to run") @Param(description = "The script to run")
IrisScript script IrisScript script
@@ -260,14 +283,14 @@ public class CommandStudio implements DecreeExecutor {
engine().getExecution().execute(script.getLoadKey()); engine().getExecution().execute(script.getLoadKey());
} }
@Decree(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n"}) @Director(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n"})
public void noise() { public void noise() {
if (noGUI()) return; if (noGUI()) return;
sender().sendMessage(C.GREEN + "Opening Noise Explorer!"); sender().sendMessage(C.GREEN + "Opening Noise Explorer!");
NoiseExplorerGUI.launch(); NoiseExplorerGUI.launch();
} }
@Decree(description = "Charges all spawners in the area", aliases = "zzt", origin = DecreeOrigin.PLAYER) @Director(description = "Charges all spawners in the area", aliases = "zzt", origin = DirectorOrigin.PLAYER)
public void charge() { public void charge() {
if (!IrisToolbelt.isIrisWorld(world())) { if (!IrisToolbelt.isIrisWorld(world())) {
sender().sendMessage(C.RED + "You must be in an Iris world to charge spawners!"); sender().sendMessage(C.RED + "You must be in an Iris world to charge spawners!");
@@ -277,7 +300,7 @@ public class CommandStudio implements DecreeExecutor {
engine().getWorldManager().chargeEnergy(); engine().getWorldManager().chargeEnergy();
} }
@Decree(description = "Preview noise gens (External GUI)", aliases = {"generator", "gen"}) @Director(description = "Preview noise gens (External GUI)", aliases = {"generator", "gen"})
public void explore( public void explore(
@Param(description = "The generator to explore", contextual = true) @Param(description = "The generator to explore", contextual = true)
IrisGenerator generator, IrisGenerator generator,
@@ -298,7 +321,7 @@ public class CommandStudio implements DecreeExecutor {
NoiseExplorerGUI.launch(l, "Custom Generator"); NoiseExplorerGUI.launch(l, "Custom Generator");
} }
@Decree(description = "Hotload a studio", aliases = {"reload", "h"}) @Director(description = "Hotload a studio", aliases = {"reload", "h"})
public void hotload() { public void hotload() {
if (!Iris.service(StudioSVC.class).isProjectOpen()) { if (!Iris.service(StudioSVC.class).isProjectOpen()) {
sender().sendMessage(C.RED + "No studio world open!"); sender().sendMessage(C.RED + "No studio world open!");
@@ -308,7 +331,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Hotloaded"); sender().sendMessage(C.GREEN + "Hotloaded");
} }
@Decree(description = "Show loot if a chest were right here", origin = DecreeOrigin.PLAYER, sync = true) @Director(description = "Show loot if a chest were right here", origin = DirectorOrigin.PLAYER, sync = true)
public void loot( public void loot(
@Param(description = "Fast insertion of items in virtual inventory (may cause performance drop)", defaultValue = "false") @Param(description = "Fast insertion of items in virtual inventory (may cause performance drop)", defaultValue = "false")
boolean fast, boolean fast,
@@ -355,7 +378,7 @@ public class CommandStudio implements DecreeExecutor {
player().openInventory(inv); player().openInventory(inv);
} }
@Decree(description = "Calculate the chance for each region to generate", origin = DecreeOrigin.PLAYER) @Director(description = "Calculate the chance for each region to generate", origin = DirectorOrigin.PLAYER)
public void regions(@Param(description = "The radius in chunks", defaultValue = "500") int radius) { public void regions(@Param(description = "The radius in chunks", defaultValue = "500") int radius) {
var engine = engine(); var engine = engine();
if (engine == null) { if (engine == null) {
@@ -392,7 +415,7 @@ public class CommandStudio implements DecreeExecutor {
}); });
} }
@Decree(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DecreeOrigin.PLAYER) @Director(description = "Get all structures in a radius of chunks", aliases = "dist", origin = DirectorOrigin.PLAYER)
public void distances(@Param(description = "The radius in chunks") int radius) { public void distances(@Param(description = "The radius in chunks") int radius) {
var engine = engine(); var engine = engine();
if (engine == null) { if (engine == null) {
@@ -458,7 +481,7 @@ public class CommandStudio implements DecreeExecutor {
} }
@Decree(description = "Render a world map (External GUI)", aliases = "render") @Director(description = "Render a world map (External GUI)", aliases = "render")
public void map( public void map(
@Param(name = "world", description = "The world to open the generator for", contextual = true) @Param(name = "world", description = "The world to open the generator for", contextual = true)
World world World world
@@ -474,7 +497,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Opening map!"); sender().sendMessage(C.GREEN + "Opening map!");
} }
@Decree(description = "Package a dimension into a compressed format", aliases = "package") @Director(description = "Package a dimension into a compressed format", aliases = "package")
public void pkg( public void pkg(
@Param(name = "dimension", description = "The dimension pack to compress", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class) @Param(name = "dimension", description = "The dimension pack to compress", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
IrisDimension dimension, IrisDimension dimension,
@@ -486,7 +509,7 @@ public class CommandStudio implements DecreeExecutor {
Iris.service(StudioSVC.class).compilePackage(sender(), dimension.getLoadKey(), obfuscate, minify); Iris.service(StudioSVC.class).compilePackage(sender(), dimension.getLoadKey(), obfuscate, minify);
} }
@Decree(description = "Profiles the performance of a dimension", origin = DecreeOrigin.PLAYER) @Director(description = "Profiles the performance of a dimension", origin = DirectorOrigin.PLAYER)
public void profile( public void profile(
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class) @Param(description = "The dimension to profile", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
IrisDimension dimension IrisDimension dimension
@@ -675,7 +698,7 @@ public class CommandStudio implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Done! " + report.getPath()); sender().sendMessage(C.GREEN + "Done! " + report.getPath());
} }
@Decree(description = "Spawn an Iris entity", aliases = "summon", origin = DecreeOrigin.PLAYER) @Director(description = "Spawn an Iris entity", aliases = "summon", origin = DirectorOrigin.PLAYER)
public void spawn( public void spawn(
@Param(description = "The entity to spawn") @Param(description = "The entity to spawn")
IrisEntity entity, IrisEntity entity,
@@ -688,7 +711,7 @@ public class CommandStudio implements DecreeExecutor {
entity.spawn(engine(), new Location(world(), location.getX(), location.getY(), location.getZ())); entity.spawn(engine(), new Location(world(), location.getX(), location.getY(), location.getZ()));
} }
@Decree(description = "Teleport to the active studio world", aliases = "stp", origin = DecreeOrigin.PLAYER, sync = true) @Director(description = "Teleport to the active studio world", aliases = "stp", origin = DirectorOrigin.PLAYER, sync = true)
public void tpstudio() { public void tpstudio() {
if (!Iris.service(StudioSVC.class).isProjectOpen()) { if (!Iris.service(StudioSVC.class).isProjectOpen()) {
sender().sendMessage(C.RED + "No studio world is open!"); sender().sendMessage(C.RED + "No studio world is open!");
@@ -711,7 +734,7 @@ public class CommandStudio implements DecreeExecutor {
).thenRun(() -> player.setGameMode(GameMode.SPECTATOR)); ).thenRun(() -> player.setGameMode(GameMode.SPECTATOR));
} }
@Decree(description = "Update your dimension projects VSCode workspace") @Director(description = "Update your dimension projects VSCode workspace")
public void update( public void update(
@Param(description = "The dimension to update the workspace of", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class) @Param(description = "The dimension to update the workspace of", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
IrisDimension dimension IrisDimension dimension
@@ -724,7 +747,7 @@ public class CommandStudio implements DecreeExecutor {
} }
} }
@Decree(aliases = "find-objects", description = "Get information about nearby structures") @Director(aliases = "find-objects", description = "Get information about nearby structures")
public void objects() { public void objects() {
if (!IrisToolbelt.isIrisWorld(player().getWorld())) { if (!IrisToolbelt.isIrisWorld(player().getWorld())) {
sender().sendMessage(C.RED + "You must be in an Iris world"); sender().sendMessage(C.RED + "You must be in an Iris world");
@@ -23,8 +23,8 @@ import art.arcane.iris.core.pregenerator.LazyPregenerator;
import art.arcane.iris.core.pregenerator.TurboPregenerator; import art.arcane.iris.core.pregenerator.TurboPregenerator;
import art.arcane.iris.core.pregenerator.TurboPregenerator; import art.arcane.iris.core.pregenerator.TurboPregenerator;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
@@ -33,10 +33,10 @@ import org.bukkit.util.Vector;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@Decree(name = "turbopregen", aliases = "turbo", description = "Pregenerate your Iris worlds!") @Director(name = "turbopregen", aliases = "turbo", description = "Pregenerate your Iris worlds!")
public class CommandTurboPregen implements DecreeExecutor { public class CommandTurboPregen implements DecreeExecutor {
public String worldName; public String worldName;
@Decree(description = "Pregenerate a world") @Director(description = "Pregenerate a world")
public void start( public void start(
@Param(description = "The radius of the pregen in blocks", aliases = "size") @Param(description = "The radius of the pregen in blocks", aliases = "size")
int radius, int radius,
@@ -90,7 +90,7 @@ public class CommandTurboPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Stop the active pregeneration task", aliases = "x") @Director(description = "Stop the active pregeneration task", aliases = "x")
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException { public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance(); TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName()); File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
@@ -108,7 +108,7 @@ public class CommandTurboPregen implements DecreeExecutor {
} }
} }
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"}) @Director(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
public void pause( public void pause(
@Param(aliases = "world", description = "The world to pause") @Param(aliases = "world", description = "The world to pause")
World world World world
@@ -25,18 +25,18 @@ import art.arcane.iris.Iris;
import art.arcane.iris.core.pregenerator.ChunkUpdater; import art.arcane.iris.core.pregenerator.ChunkUpdater;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
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;
@Decree(name = "updater", origin = DecreeOrigin.BOTH, description = "Iris World Updater") @Director(name = "updater", origin = DirectorOrigin.BOTH, description = "Iris World Updater")
public class CommandUpdater implements DecreeExecutor { public class CommandUpdater implements DecreeExecutor {
private final Object lock = new Object(); private final Object lock = new Object();
private transient ChunkUpdater chunkUpdater; private transient ChunkUpdater chunkUpdater;
@Decree(description = "Updates all chunk in the specified world") @Director(description = "Updates all chunk in the specified world")
public void start( public void start(
@Param(description = "World to update chunks at", contextual = true) @Param(description = "World to update chunks at", contextual = true)
World world World world
@@ -61,7 +61,7 @@ public class CommandUpdater implements DecreeExecutor {
} }
@Synchronized("lock") @Synchronized("lock")
@Decree(description = "Pause the updater") @Director(description = "Pause the updater")
public void pause( ) { public void pause( ) {
if (chunkUpdater == null) { if (chunkUpdater == null) {
sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?"); sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?");
@@ -84,7 +84,7 @@ public class CommandUpdater implements DecreeExecutor {
} }
@Synchronized("lock") @Synchronized("lock")
@Decree(description = "Stops the updater") @Director(description = "Stops the updater")
public void stop() { public void stop() {
if (chunkUpdater == null) { if (chunkUpdater == null) {
sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?"); sender().sendMessage(C.GOLD + "You cant stop something that doesnt exist?");
@@ -27,9 +27,9 @@ import art.arcane.iris.engine.object.IrisBiome;
import art.arcane.iris.engine.object.IrisRegion; import art.arcane.iris.engine.object.IrisRegion;
import art.arcane.iris.util.data.B; import art.arcane.iris.util.data.B;
import art.arcane.iris.util.decree.DecreeExecutor; import art.arcane.iris.util.decree.DecreeExecutor;
import art.arcane.volmlib.util.decree.DecreeOrigin; import art.arcane.volmlib.util.director.DirectorOrigin;
import art.arcane.volmlib.util.decree.annotations.Decree; import art.arcane.volmlib.util.director.annotations.Director;
import art.arcane.volmlib.util.decree.annotations.Param; import art.arcane.volmlib.util.director.annotations.Param;
import art.arcane.iris.util.format.C; import art.arcane.iris.util.format.C;
import art.arcane.volmlib.util.matter.MatterMarker; import art.arcane.volmlib.util.matter.MatterMarker;
import art.arcane.iris.util.scheduling.J; import art.arcane.iris.util.scheduling.J;
@@ -42,9 +42,9 @@ import org.bukkit.block.data.BlockData;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@Decree(name = "what", origin = DecreeOrigin.PLAYER, studio = true, description = "Iris What?") @Director(name = "what", origin = DirectorOrigin.PLAYER, studio = true, description = "Iris What?")
public class CommandWhat implements DecreeExecutor { public class CommandWhat implements DecreeExecutor {
@Decree(description = "What is in my hand?", origin = DecreeOrigin.PLAYER) @Director(description = "What is in my hand?", origin = DirectorOrigin.PLAYER)
public void hand() { public void hand() {
try { try {
BlockData bd = player().getInventory().getItemInMainHand().getType().createBlockData(); BlockData bd = player().getInventory().getItemInMainHand().getType().createBlockData();
@@ -65,7 +65,7 @@ public class CommandWhat implements DecreeExecutor {
} }
} }
@Decree(description = "What biome am i in?", origin = DecreeOrigin.PLAYER) @Director(description = "What biome am i in?", origin = DirectorOrigin.PLAYER)
public void biome() { public void biome() {
try { try {
IrisBiome b = engine().getBiome(player().getLocation().getBlockX(), player().getLocation().getBlockY() - player().getWorld().getMinHeight(), player().getLocation().getBlockZ()); IrisBiome b = engine().getBiome(player().getLocation().getBlockX(), player().getLocation().getBlockY() - player().getWorld().getMinHeight(), player().getLocation().getBlockZ());
@@ -85,7 +85,7 @@ public class CommandWhat implements DecreeExecutor {
} }
} }
@Decree(description = "What region am i in?", origin = DecreeOrigin.PLAYER) @Director(description = "What region am i in?", origin = DirectorOrigin.PLAYER)
public void region() { public void region() {
try { try {
Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16); Chunk chunk = world().getChunkAt(player().getLocation().getBlockZ() / 16, player().getLocation().getBlockZ() / 16);
@@ -98,7 +98,7 @@ public class CommandWhat implements DecreeExecutor {
} }
} }
@Decree(description = "What block am i looking at?", origin = DecreeOrigin.PLAYER) @Director(description = "What block am i looking at?", origin = DirectorOrigin.PLAYER)
public void block() { public void block() {
BlockData bd; BlockData bd;
try { try {
@@ -143,7 +143,7 @@ public class CommandWhat implements DecreeExecutor {
} }
} }
@Decree(description = "Show markers in chunk", origin = DecreeOrigin.PLAYER) @Director(description = "Show markers in chunk", origin = DirectorOrigin.PLAYER)
public void markers(@Param(description = "Marker name such as cave_floor or cave_ceiling") String marker) { public void markers(@Param(description = "Marker name such as cave_floor or cave_ceiling") String marker) {
Chunk c = player().getLocation().getChunk(); Chunk c = player().getLocation().getChunk();
@@ -34,6 +34,7 @@ import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.math.Position2; import art.arcane.iris.util.math.Position2;
import art.arcane.volmlib.util.scheduling.ChronoLatch; import art.arcane.volmlib.util.scheduling.ChronoLatch;
import art.arcane.iris.util.scheduling.J; import art.arcane.iris.util.scheduling.J;
import org.bukkit.World;
import javax.swing.*; import javax.swing.*;
import java.awt.*; import java.awt.*;
@@ -158,6 +159,15 @@ public class PregeneratorJob implements PregenListener {
return inst == null ? -1L : Math.max(0L, inst.lastChunksRemaining); return inst == null ? -1L : Math.max(0L, inst.lastChunksRemaining);
} }
public boolean targetsWorld(World world) {
if (world == null || engine == null || engine.getWorld() == null) {
return false;
}
String targetName = engine.getWorld().name();
return targetName != null && targetName.equalsIgnoreCase(world.getName());
}
private static Color parseColor(String c) { private static Color parseColor(String c) {
String v = (c.startsWith("#") ? c : "#" + c).trim(); String v = (c.startsWith("#") ? c : "#" + c).trim();
try { try {
@@ -261,6 +261,10 @@ public class ChunkUpdater {
} }
private void unloadAndSaveAllChunks() { private void unloadAndSaveAllChunks() {
if (J.isFolia()) {
return;
}
try { try {
J.sfut(() -> { J.sfut(() -> {
if (world == null) { if (world == null) {
@@ -23,7 +23,6 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.pregenerator.PregenListener; import art.arcane.iris.core.pregenerator.PregenListener;
import art.arcane.iris.core.pregenerator.PregeneratorMethod; import art.arcane.iris.core.pregenerator.PregeneratorMethod;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.mantle.Mantle; import art.arcane.iris.util.mantle.Mantle;
import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.parallel.MultiBurst; import art.arcane.iris.util.parallel.MultiBurst;
@@ -34,19 +33,30 @@ import org.bukkit.World;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public class AsyncPregenMethod implements PregeneratorMethod { public class AsyncPregenMethod implements PregeneratorMethod {
private static final AtomicInteger THREAD_COUNT = new AtomicInteger(); private static final AtomicInteger THREAD_COUNT = new AtomicInteger();
private static final int FOLIA_MAX_CONCURRENCY = 32;
private static final long CHUNK_LOAD_TIMEOUT_SECONDS = 15L;
private final World world; private final World world;
private final Executor executor; private final Executor executor;
private final Semaphore semaphore; private final Semaphore semaphore;
private final int threads; private final int threads;
private final boolean urgent; private final boolean urgent;
private final Map<Chunk, Long> lastUse; private final Map<Chunk, Long> lastUse;
private final AtomicInteger inFlight = new AtomicInteger();
private final AtomicLong submitted = new AtomicLong();
private final AtomicLong completed = new AtomicLong();
private final AtomicLong failed = new AtomicLong();
private final AtomicLong lastProgressAt = new AtomicLong(M.ms());
private final AtomicLong lastPermitWaitLog = new AtomicLong(0L);
public AsyncPregenMethod(World world, int unusedThreads) { public AsyncPregenMethod(World world, int unusedThreads) {
if (!PaperLib.isPaper()) { if (!PaperLib.isPaper()) {
@@ -54,14 +64,29 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} }
this.world = world; this.world = world;
this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor(); if (J.isFolia()) {
this.threads = IrisSettings.get().getPregen().getMaxConcurrency(); this.executor = new FoliaRegionExecutor();
} else {
boolean useTicketQueue = IrisSettings.get().getPregen().isUseTicketQueue();
this.executor = useTicketQueue ? new TicketExecutor() : new ServiceExecutor();
}
int configuredThreads = IrisSettings.get().getPregen().getMaxConcurrency();
if (J.isFolia()) {
configuredThreads = Math.min(configuredThreads, FOLIA_MAX_CONCURRENCY);
}
this.threads = Math.max(1, configuredThreads);
this.semaphore = new Semaphore(this.threads, true); this.semaphore = new Semaphore(this.threads, true);
this.urgent = IrisSettings.get().getPregen().useHighPriority; this.urgent = IrisSettings.get().getPregen().useHighPriority;
this.lastUse = new KMap<>(); this.lastUse = new ConcurrentHashMap<>();
} }
private void unloadAndSaveAllChunks() { private void unloadAndSaveAllChunks() {
if (J.isFolia()) {
// Folia requires world/chunk mutations to be region-owned; periodic global unload/save is unsafe.
lastUse.clear();
return;
}
try { try {
J.sfut(() -> { J.sfut(() -> {
if (world == null) { if (world == null) {
@@ -88,8 +113,71 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} }
} }
private Chunk onChunkFutureFailure(int x, int z, Throwable throwable) {
Throwable root = throwable;
while (root.getCause() != null) {
root = root.getCause();
}
if (root instanceof java.util.concurrent.TimeoutException) {
Iris.warn("Timed out async pregen chunk load at " + x + "," + z + " after " + CHUNK_LOAD_TIMEOUT_SECONDS + "s. " + metricsSnapshot());
} else {
Iris.warn("Failed async pregen chunk load at " + x + "," + z + ". " + metricsSnapshot());
}
Iris.reportError(throwable);
return null;
}
private String metricsSnapshot() {
long stalledFor = Math.max(0L, M.ms() - lastProgressAt.get());
return "world=" + world.getName()
+ " permits=" + semaphore.availablePermits() + "/" + threads
+ " inFlight=" + inFlight.get()
+ " submitted=" + submitted.get()
+ " completed=" + completed.get()
+ " failed=" + failed.get()
+ " stalledForMs=" + stalledFor;
}
private void markSubmitted() {
submitted.incrementAndGet();
inFlight.incrementAndGet();
}
private void markFinished(boolean success) {
if (success) {
completed.incrementAndGet();
} else {
failed.incrementAndGet();
}
lastProgressAt.set(M.ms());
int after = inFlight.decrementAndGet();
if (after < 0) {
inFlight.compareAndSet(after, 0);
}
}
private void logPermitWaitIfNeeded(int x, int z, long waitedMs) {
long now = M.ms();
long last = lastPermitWaitLog.get();
if (now - last < 5000L) {
return;
}
if (lastPermitWaitLog.compareAndSet(last, now)) {
Iris.warn("Async pregen waiting for permit at chunk " + x + "," + z + " waitedMs=" + waitedMs + " " + metricsSnapshot());
}
}
@Override @Override
public void init() { public void init() {
Iris.info("Async pregen init: world=" + world.getName()
+ ", mode=" + (J.isFolia() ? "folia" : "paper")
+ ", threads=" + threads
+ ", urgent=" + urgent
+ ", timeout=" + CHUNK_LOAD_TIMEOUT_SECONDS + "s");
unloadAndSaveAllChunks(); unloadAndSaveAllChunks();
increaseWorkerThreads(); increaseWorkerThreads();
} }
@@ -126,10 +214,16 @@ public class AsyncPregenMethod implements PregeneratorMethod {
public void generateChunk(int x, int z, PregenListener listener) { public void generateChunk(int x, int z, PregenListener listener) {
listener.onChunkGenerating(x, z); listener.onChunkGenerating(x, z);
try { try {
semaphore.acquire(); long waitStart = M.ms();
while (!semaphore.tryAcquire(5, TimeUnit.SECONDS)) {
logPermitWaitIfNeeded(x, z, Math.max(0L, M.ms() - waitStart));
}
} catch (InterruptedException e) { } catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; return;
} }
markSubmitted();
executor.generate(x, z, listener); executor.generate(x, z, listener);
} }
@@ -189,6 +283,40 @@ public class AsyncPregenMethod implements PregeneratorMethod {
default void shutdown() {} default void shutdown() {}
} }
private class FoliaRegionExecutor implements Executor {
@Override
public void generate(int x, int z, PregenListener listener) {
if (!J.runRegion(world, x, z, () -> PaperLib.getChunkAtAsync(world, x, z, true, urgent)
.orTimeout(CHUNK_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
.whenComplete((chunk, throwable) -> {
boolean success = false;
try {
if (throwable != null) {
onChunkFutureFailure(x, z, throwable);
return;
}
listener.onChunkGenerated(x, z);
listener.onChunkCleaned(x, z);
if (chunk != null) {
lastUse.put(chunk, M.ms());
}
success = true;
} catch (Throwable e) {
Iris.reportError(e);
e.printStackTrace();
} finally {
markFinished(success);
semaphore.release();
}
}))) {
markFinished(false);
semaphore.release();
Iris.warn("Failed to schedule Folia region pregen task at " + x + "," + z + ". " + metricsSnapshot());
}
}
}
private class ServiceExecutor implements Executor { private class ServiceExecutor implements Executor {
private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ? private final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
Executors.newVirtualThreadPerTaskExecutor() : Executors.newVirtualThreadPerTaskExecutor() :
@@ -196,18 +324,27 @@ public class AsyncPregenMethod implements PregeneratorMethod {
public void generate(int x, int z, PregenListener listener) { public void generate(int x, int z, PregenListener listener) {
service.submit(() -> { service.submit(() -> {
boolean success = false;
try { try {
PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> { Chunk i = PaperLib.getChunkAtAsync(world, x, z, true, urgent)
listener.onChunkGenerated(x, z); .orTimeout(CHUNK_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
listener.onChunkCleaned(x, z); .exceptionally(e -> onChunkFutureFailure(x, z, e))
if (i == null) return; .get();
lastUse.put(i, M.ms());
}).get(); listener.onChunkGenerated(x, z);
listener.onChunkCleaned(x, z);
if (i == null) {
return;
}
lastUse.put(i, M.ms());
success = true;
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
} catch (Throwable e) { } catch (Throwable e) {
Iris.reportError(e); Iris.reportError(e);
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
markFinished(success);
semaphore.release(); semaphore.release();
} }
}); });
@@ -223,17 +360,21 @@ public class AsyncPregenMethod implements PregeneratorMethod {
@Override @Override
public void generate(int x, int z, PregenListener listener) { public void generate(int x, int z, PregenListener listener) {
PaperLib.getChunkAtAsync(world, x, z, true, urgent) PaperLib.getChunkAtAsync(world, x, z, true, urgent)
.exceptionally(e -> { .orTimeout(CHUNK_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
Iris.reportError(e); .exceptionally(e -> onChunkFutureFailure(x, z, e))
e.printStackTrace();
return null;
})
.thenAccept(i -> { .thenAccept(i -> {
semaphore.release(); boolean success = false;
listener.onChunkGenerated(x, z); try {
listener.onChunkCleaned(x, z); listener.onChunkGenerated(x, z);
if (i == null) return; listener.onChunkCleaned(x, z);
lastUse.put(i, M.ms()); if (i != null) {
lastUse.put(i, M.ms());
}
success = true;
} finally {
markFinished(success);
semaphore.release();
}
}); });
} }
} }
@@ -24,7 +24,6 @@ import art.arcane.iris.core.pregenerator.PregenListener;
import art.arcane.iris.core.pregenerator.PregeneratorMethod; import art.arcane.iris.core.pregenerator.PregeneratorMethod;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.collection.KMap;
import art.arcane.iris.util.mantle.Mantle; import art.arcane.iris.util.mantle.Mantle;
import art.arcane.volmlib.util.math.M; import art.arcane.volmlib.util.math.M;
import art.arcane.iris.util.scheduling.J; import art.arcane.iris.util.scheduling.J;
@@ -35,6 +34,7 @@ import org.bukkit.World;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
public class MedievalPregenMethod implements PregeneratorMethod { public class MedievalPregenMethod implements PregeneratorMethod {
private final World world; private final World world;
@@ -44,7 +44,7 @@ public class MedievalPregenMethod implements PregeneratorMethod {
public MedievalPregenMethod(World world) { public MedievalPregenMethod(World world) {
this.world = world; this.world = world;
futures = new KList<>(); futures = new KList<>();
this.lastUse = new KMap<>(); this.lastUse = new ConcurrentHashMap<>();
} }
private void waitForChunks() { private void waitForChunks() {
@@ -60,6 +60,11 @@ public class MedievalPregenMethod implements PregeneratorMethod {
} }
private void unloadAndSaveAllChunks() { private void unloadAndSaveAllChunks() {
if (J.isFolia()) {
lastUse.clear();
return;
}
try { try {
J.sfut(() -> { J.sfut(() -> {
if (world == null) { if (world == null) {
@@ -30,7 +30,7 @@ import art.arcane.iris.util.format.C;
import art.arcane.iris.util.plugin.IrisService; import art.arcane.iris.util.plugin.IrisService;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import art.arcane.iris.util.scheduling.J; import art.arcane.iris.util.scheduling.J;
import art.arcane.volmlib.util.director.compat.DirectorDecreeEngineFactory; import art.arcane.volmlib.util.director.compat.DirectorEngineFactory;
import art.arcane.volmlib.util.director.context.DirectorContextRegistry; import art.arcane.volmlib.util.director.context.DirectorContextRegistry;
import art.arcane.volmlib.util.director.runtime.DirectorExecutionMode; import art.arcane.volmlib.util.director.runtime.DirectorExecutionMode;
import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult; import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult;
@@ -94,7 +94,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
} }
public DirectorRuntimeEngine getDirector() { public DirectorRuntimeEngine getDirector() {
return directorCache.aquireNastyPrint(() -> DirectorDecreeEngineFactory.create( return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
new CommandIris(), new CommandIris(),
null, null,
buildDirectorContexts(), buildDirectorContexts(),
@@ -281,6 +281,10 @@ public class IrisEngineSVC implements IrisService {
|| engine.getMantle().getMantle().isClosed() || engine.getMantle().getMantle().isClosed()
|| !engine.getMantle().getMantle().shouldReduce(engine)) || !engine.getMantle().getMantle().shouldReduce(engine))
return; return;
World engineWorld = engine.getWorld().realWorld();
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
return;
}
try { try {
engine.getMantle().trim(tectonicLimit()); engine.getMantle().trim(tectonicLimit());
@@ -304,6 +308,10 @@ public class IrisEngineSVC implements IrisService {
|| engine.getMantle().getMantle().isClosed() || engine.getMantle().getMantle().isClosed()
|| !engine.getMantle().getMantle().shouldReduce(engine)) || !engine.getMantle().getMantle().shouldReduce(engine))
return; return;
World engineWorld = engine.getWorld().realWorld();
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
return;
}
try { try {
long unloadStart = System.currentTimeMillis(); long unloadStart = System.currentTimeMillis();
@@ -57,7 +57,7 @@ public class StudioSVC implements IrisService {
@Override @Override
public void onEnable() { public void onEnable() {
J.s(() -> { J.a(() -> {
String pack = IrisSettings.get().getGenerator().getDefaultWorldType(); String pack = IrisSettings.get().getGenerator().getDefaultWorldType();
File f = IrisPack.packsPack(pack); File f = IrisPack.packsPack(pack);
@@ -30,6 +30,7 @@ import art.arcane.iris.core.service.StudioSVC;
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;
import art.arcane.iris.engine.platform.PlatformChunkGenerator; import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.util.scheduling.J;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.World; import org.bukkit.World;
@@ -40,6 +41,8 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Something you really want to wear if working on Iris. Shit gets pretty hectic down there. * Something you really want to wear if working on Iris. Shit gets pretty hectic down there.
@@ -48,6 +51,8 @@ import java.util.Map;
public class IrisToolbelt { public class IrisToolbelt {
@ApiStatus.Internal @ApiStatus.Internal
public static Map<String, Boolean> toolbeltConfiguration = new HashMap<>(); public static Map<String, Boolean> toolbeltConfiguration = new HashMap<>();
private static final Map<String, AtomicInteger> worldMaintenanceDepth = new ConcurrentHashMap<>();
private static final Map<String, AtomicInteger> worldMaintenanceMantleBypassDepth = new ConcurrentHashMap<>();
/** /**
* Will find / download / search for the dimension or return null * Will find / download / search for the dimension or return null
@@ -215,7 +220,8 @@ public class IrisToolbelt {
* @return the pregenerator job (already started) * @return the pregenerator job (already started)
*/ */
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine, boolean cached) { public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine, boolean cached) {
return new PregeneratorJob(task, cached && engine != null ? new CachedPregenMethod(method, engine.getWorld().name()) : method, engine); boolean useCachedWrapper = cached && engine != null && !J.isFolia();
return new PregeneratorJob(task, useCachedWrapper ? new CachedPregenMethod(method, engine.getWorld().name()) : method, engine);
} }
/** /**
@@ -293,6 +299,71 @@ public class IrisToolbelt {
return isIrisWorld(i) && access(i).isStudio(); return isIrisWorld(i) && access(i).isStudio();
} }
public static void beginWorldMaintenance(World world, String reason) {
beginWorldMaintenance(world, reason, false);
}
public static void beginWorldMaintenance(World world, String reason, boolean bypassMantleStages) {
if (world == null) {
return;
}
String name = world.getName();
int depth = worldMaintenanceDepth.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
if (bypassMantleStages) {
worldMaintenanceMantleBypassDepth.computeIfAbsent(name, k -> new AtomicInteger()).incrementAndGet();
}
Iris.info("World maintenance enter: " + name + " reason=" + reason + " depth=" + depth + " bypassMantle=" + bypassMantleStages);
}
public static void endWorldMaintenance(World world, String reason) {
if (world == null) {
return;
}
String name = world.getName();
AtomicInteger depthCounter = worldMaintenanceDepth.get(name);
if (depthCounter == null) {
return;
}
int depth = depthCounter.decrementAndGet();
if (depth <= 0) {
worldMaintenanceDepth.remove(name, depthCounter);
depth = 0;
}
AtomicInteger bypassCounter = worldMaintenanceMantleBypassDepth.get(name);
int bypassDepth = 0;
if (bypassCounter != null) {
bypassDepth = bypassCounter.decrementAndGet();
if (bypassDepth <= 0) {
worldMaintenanceMantleBypassDepth.remove(name, bypassCounter);
bypassDepth = 0;
}
}
Iris.info("World maintenance exit: " + name + " reason=" + reason + " depth=" + depth + " bypassMantleDepth=" + bypassDepth);
}
public static boolean isWorldMaintenanceActive(World world) {
if (world == null) {
return false;
}
AtomicInteger counter = worldMaintenanceDepth.get(world.getName());
return counter != null && counter.get() > 0;
}
public static boolean isWorldMaintenanceBypassingMantleStages(World world) {
if (world == null) {
return false;
}
AtomicInteger counter = worldMaintenanceMantleBypassDepth.get(world.getName());
return counter != null && counter.get() > 0;
}
public static void retainMantleDataForSlice(String className) { public static void retainMantleDataForSlice(String className) {
toolbeltConfiguration.put("retain.mantle." + className, Boolean.TRUE); toolbeltConfiguration.put("retain.mantle." + className, Boolean.TRUE);
} }
@@ -30,6 +30,7 @@ import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.project.IrisProject; import art.arcane.iris.core.project.IrisProject;
import art.arcane.iris.core.scripting.environment.EngineEnvironment; import art.arcane.iris.core.scripting.environment.EngineEnvironment;
import art.arcane.iris.core.service.PreservationSVC; import art.arcane.iris.core.service.PreservationSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.data.cache.AtomicCache;
import art.arcane.iris.engine.framework.*; import art.arcane.iris.engine.framework.*;
import art.arcane.iris.engine.mantle.EngineMantle; import art.arcane.iris.engine.mantle.EngineMantle;
@@ -55,6 +56,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@@ -499,7 +501,11 @@ public class IrisEngine implements Engine {
mode.generate(x, z, blocks, vbiomes, multicore); mode.generate(x, z, blocks, vbiomes, multicore);
} }
getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); World realWorld = getWorld().realWorld();
boolean skipRealFlag = J.isFolia() && realWorld != null && IrisToolbelt.isWorldMaintenanceBypassingMantleStages(realWorld);
if (!skipRealFlag) {
getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true);
}
getMetrics().getTotal().put(p.getMilliseconds()); getMetrics().getTotal().put(p.getMilliseconds());
generated.incrementAndGet(); generated.incrementAndGet();
@@ -20,9 +20,11 @@ package art.arcane.iris.engine;
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.gui.PregeneratorJob;
import art.arcane.iris.core.link.Identifier; import art.arcane.iris.core.link.Identifier;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.service.ExternalDataSVC; import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.data.cache.Cache; import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
import art.arcane.iris.engine.framework.EngineAssignedWorldManager; import art.arcane.iris.engine.framework.EngineAssignedWorldManager;
@@ -62,6 +64,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.Predicate; import java.util.function.Predicate;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@@ -80,6 +83,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
private final ChronoLatch chunkDiscovery; private final ChronoLatch chunkDiscovery;
private final KMap<Long, Future<?>> cleanup = new KMap<>(); private final KMap<Long, Future<?>> cleanup = new KMap<>();
private final ScheduledExecutorService cleanupService; private final ScheduledExecutorService cleanupService;
private final Set<Long> mantleWarmupQueue = ConcurrentHashMap.newKeySet();
private final Set<Long> markerFlagQueue = ConcurrentHashMap.newKeySet();
private final Set<Long> discoveredFlagQueue = ConcurrentHashMap.newKeySet();
private final Set<Long> markerScanQueue = ConcurrentHashMap.newKeySet();
private double energy = 25; private double energy = 25;
private int entityCount = 0; private int entityCount = 0;
private long charge = 0; private long charge = 0;
@@ -190,12 +197,15 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
private void discoverChunks() { private void discoverChunks() {
var mantle = getEngine().getMantle().getMantle();
World world = getEngine().getWorld().realWorld(); World world = getEngine().getWorld().realWorld();
if (world == null) { if (world == null) {
return; return;
} }
if (isPregenActiveForThisWorld()) {
return;
}
J.s(() -> { J.s(() -> {
for (Player player : world.getPlayers()) { for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) { if (player == null || !player.isOnline()) {
@@ -208,7 +218,9 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
int radius = 1; int radius = 1;
for (int x = -radius; x <= radius; x++) { for (int x = -radius; x <= radius; x++) {
for (int z = -radius; z <= radius; z++) { for (int z = -radius; z <= radius; z++) {
mantle.getChunk(centerX + x, centerZ + z).flag(MantleFlag.DISCOVERED, true); int chunkX = centerX + x;
int chunkZ = centerZ + z;
raiseDiscoveredChunkFlag(world, chunkX, chunkZ);
} }
} }
}); });
@@ -216,12 +228,45 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
}); });
} }
private void raiseDiscoveredChunkFlag(World world, int chunkX, int chunkZ) {
if (world == null) {
return;
}
if (!J.isFolia()) {
getMantle().getChunk(chunkX, chunkZ).flag(MantleFlag.DISCOVERED, true);
return;
}
long key = Cache.key(chunkX, chunkZ);
if (!discoveredFlagQueue.add(key)) {
return;
}
J.a(() -> {
try {
Mantle mantle = getMantle();
if (!mantle.hasFlag(chunkX, chunkZ, MantleFlag.DISCOVERED)) {
mantle.flag(chunkX, chunkZ, MantleFlag.DISCOVERED, true);
}
} catch (Throwable e) {
Iris.reportError(e);
} finally {
discoveredFlagQueue.remove(key);
}
});
}
private void updateChunks() { private void updateChunks() {
World world = getEngine().getWorld().realWorld(); World world = getEngine().getWorld().realWorld();
if (world == null) { if (world == null) {
return; return;
} }
if (isPregenActiveForThisWorld()) {
return;
}
J.s(() -> { J.s(() -> {
for (Player player : world.getPlayers()) { for (Player player : world.getPlayers()) {
if (player == null || !player.isOnline()) { if (player == null || !player.isOnline()) {
@@ -253,6 +298,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
Chunk chunk = world.getChunkAt(chunkX, chunkZ); Chunk chunk = world.getChunkAt(chunkX, chunkZ);
if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) { if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) {
if (J.isFolia() && !getMantle().isChunkLoaded(chunkX, chunkZ)) {
warmupMantleChunkAsync(chunkX, chunkZ);
return;
}
getEngine().updateChunk(chunk); getEngine().updateChunk(chunk);
} }
@@ -260,7 +309,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return; return;
} }
getMantle().raiseFlag(chunkX, chunkZ, MantleFlag.INITIAL_SPAWNED_MARKER, () -> { if (!J.isFolia() && !getMantle().isChunkLoaded(chunkX, chunkZ)) {
warmupMantleChunkAsync(chunkX, chunkZ);
return;
}
raiseInitialSpawnMarkerFlag(world, chunkX, chunkZ, () -> {
int delay = RNG.r.i(5, 200); int delay = RNG.r.i(5, 200);
J.runRegion(world, chunkX, chunkZ, () -> { J.runRegion(world, chunkX, chunkZ, () -> {
if (!world.isChunkLoaded(chunkX, chunkZ)) { if (!world.isChunkLoaded(chunkX, chunkZ)) {
@@ -269,23 +323,86 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
spawnIn(world.getChunkAt(chunkX, chunkZ), true); spawnIn(world.getChunkAt(chunkX, chunkZ), true);
}, delay); }, delay);
getSpawnersFromMarkers(chunk).forEach((blockf, spawners) -> { Chunk markerChunk = world.getChunkAt(chunkX, chunkZ);
if (spawners.isEmpty()) { forEachMarkerSpawner(markerChunk, (block, spawners) -> {
IrisSpawner s = new KList<>(spawners).getRandom();
if (s == null) {
return; return;
} }
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
IrisSpawner s = new KList<>(spawners).getRandom();
spawn(block, s, true); spawn(block, s, true);
}); });
}); });
} }
private void raiseInitialSpawnMarkerFlag(World world, int chunkX, int chunkZ, Runnable onFirstRaise) {
if (world == null || onFirstRaise == null) {
return;
}
if (!J.isFolia()) {
getMantle().raiseFlag(chunkX, chunkZ, MantleFlag.INITIAL_SPAWNED_MARKER, onFirstRaise);
return;
}
long key = Cache.key(chunkX, chunkZ);
if (!markerFlagQueue.add(key)) {
return;
}
J.a(() -> {
boolean raised = false;
try {
Mantle mantle = getMantle();
if (!mantle.hasFlag(chunkX, chunkZ, MantleFlag.INITIAL_SPAWNED_MARKER)) {
mantle.flag(chunkX, chunkZ, MantleFlag.INITIAL_SPAWNED_MARKER, true);
raised = true;
}
} catch (Throwable e) {
Iris.reportError(e);
} finally {
markerFlagQueue.remove(key);
}
if (!raised) {
return;
}
J.runRegion(world, chunkX, chunkZ, () -> {
if (!world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ)) {
return;
}
onFirstRaise.run();
});
});
}
private void warmupMantleChunkAsync(int chunkX, int chunkZ) {
long key = Cache.key(chunkX, chunkZ);
if (!mantleWarmupQueue.add(key)) {
return;
}
J.a(() -> {
try {
getMantle().getChunk(chunkX, chunkZ);
} catch (Throwable e) {
Iris.reportError(e);
} finally {
mantleWarmupQueue.remove(key);
}
});
}
private boolean onAsyncTick() { private boolean onAsyncTick() {
if (getEngine().isClosed()) { if (getEngine().isClosed()) {
return false; return false;
} }
if (isPregenActiveForThisWorld()) {
J.sleep(500);
return false;
}
actuallySpawned = 0; actuallySpawned = 0;
if (energy < 100) { if (energy < 100) {
@@ -335,6 +452,24 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return actuallySpawned > 0; return actuallySpawned > 0;
} }
private boolean isPregenActiveForThisWorld() {
World world = getEngine().getWorld().realWorld();
if (world == null) {
return false;
}
if (IrisToolbelt.isWorldMaintenanceActive(world)) {
return true;
}
PregeneratorJob job = PregeneratorJob.getInstance();
if (job == null) {
return false;
}
return job.targetsWorld(world);
}
private Chunk[] getLoadedChunksSnapshot(World world) { private Chunk[] getLoadedChunksSnapshot(World world) {
if (world == null) { if (world == null) {
return new Chunk[0]; return new Chunk[0];
@@ -396,15 +531,14 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
} }
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) { if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
getSpawnersFromMarkers(c).forEach((blockf, spawners) -> { forEachMarkerSpawner(c, (block, spawners) -> {
if (spawners.isEmpty()) { IrisSpawner s = new KList<>(spawners).getRandom();
if (s == null) {
return; return;
} }
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
IrisSpawner s = new KList<>(spawners).getRandom();
spawn(block, s, false); spawn(block, s, false);
J.runRegion(c.getWorld(), c.getX(), c.getZ(), () -> getMantle().raiseFlag(c.getX(), c.getZ(), MantleFlag.INITIAL_SPAWNED_MARKER, J.runRegion(c.getWorld(), c.getX(), c.getZ(), () -> raiseInitialSpawnMarkerFlag(c.getWorld(), c.getX(), c.getZ(),
() -> spawn(block, s, true))); () -> spawn(block, s, true)));
}); });
} }
@@ -530,21 +664,42 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
//INMS.get().injectBiomesFromMantle(e, getMantle()); //INMS.get().injectBiomesFromMantle(e, getMantle());
if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return;
if (isPregenActiveForThisWorld()) return;
World world = e.getWorld();
int chunkX = e.getX();
int chunkZ = e.getZ();
int minY = getTarget().getWorld().minHeight();
int delay = RNG.r.i(20, 60);
Iris.tickets.addTicket(e); Iris.tickets.addTicket(e);
J.s(() -> {
var chunk = getMantle().getChunk(e).use(); Runnable applyCustomBlocks = () -> {
int minY = getTarget().getWorld().minHeight(); if (J.isFolia() && (!world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ))) {
Iris.tickets.removeTicket(e);
return;
}
Chunk chunkRef = world.getChunkAt(chunkX, chunkZ);
var mantleChunk = getMantle().getChunk(chunkRef).use();
try { try {
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> { mantleChunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> {
chunk.iterate(Identifier.class, (x, y, z, v) -> { mantleChunk.iterate(Identifier.class, (x, y, z, v) -> {
Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), e.getBlock(x & 15, y + minY, z & 15), v); Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), chunkRef.getBlock(x & 15, y + minY, z & 15), v);
}); });
}); });
} finally { } finally {
chunk.release(); mantleChunk.release();
Iris.tickets.removeTicket(e); Iris.tickets.removeTicket(e);
} }
}, RNG.r.i(20, 60)); };
if (J.isFolia()) {
if (!J.runRegion(world, chunkX, chunkZ, applyCustomBlocks, delay)) {
Iris.tickets.removeTicket(e);
}
} else {
J.s(applyCustomBlocks, delay);
}
} }
} }
@@ -643,6 +798,14 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
public Map<IrisPosition, KSet<IrisSpawner>> getSpawnersFromMarkers(Chunk c) { public Map<IrisPosition, KSet<IrisSpawner>> getSpawnersFromMarkers(Chunk c) {
Map<IrisPosition, KSet<IrisSpawner>> p = new KMap<>(); Map<IrisPosition, KSet<IrisSpawner>> p = new KMap<>();
Set<IrisPosition> b = new KSet<>(); Set<IrisPosition> b = new KSet<>();
if (J.isFolia()) {
if (!getMantle().isChunkLoaded(c.getX(), c.getZ())) {
warmupMantleChunkAsync(c.getX(), c.getZ());
}
return p;
}
getMantle().iterateChunk(c.getX(), c.getZ(), MatterMarker.class, (x, y, z, t) -> { getMantle().iterateChunk(c.getX(), c.getZ(), MatterMarker.class, (x, y, z, t) -> {
if (t.getTag().equals("cave_floor") || t.getTag().equals("cave_ceiling")) { if (t.getTag().equals("cave_floor") || t.getTag().equals("cave_ceiling")) {
return; return;
@@ -684,6 +847,128 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
return p; return p;
} }
private void forEachMarkerSpawner(Chunk c, BiConsumer<IrisPosition, KSet<IrisSpawner>> consumer) {
if (c == null || consumer == null) {
return;
}
if (!J.isFolia()) {
int minY = getEngine().getWorld().minHeight();
getSpawnersFromMarkers(c).forEach((relative, spawners) -> {
if (spawners.isEmpty()) {
return;
}
consumer.accept(new IrisPosition(relative.getX(), relative.getY() + minY, relative.getZ()), spawners);
});
return;
}
int chunkX = c.getX();
int chunkZ = c.getZ();
World world = c.getWorld();
long key = Cache.key(chunkX, chunkZ);
if (!markerScanQueue.add(key)) {
return;
}
J.a(() -> {
try {
Map<IrisPosition, MarkerSpawnData> markerData = collectMarkerSpawnData(chunkX, chunkZ);
if (markerData.isEmpty()) {
return;
}
J.runRegion(world, chunkX, chunkZ, () -> {
if (!world.isChunkLoaded(chunkX, chunkZ) || !Chunks.isSafe(world, chunkX, chunkZ)) {
return;
}
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
int minY = getEngine().getWorld().minHeight();
markerData.forEach((relative, data) -> {
if (data.spawners.isEmpty()) {
return;
}
if (isMarkerObstructed(chunk, relative, data.requiresEmptyAbove)) {
removeMarkerAsync(relative);
return;
}
consumer.accept(new IrisPosition(relative.getX(), relative.getY() + minY, relative.getZ()), data.spawners);
});
});
} catch (Throwable e) {
Iris.reportError(e);
} finally {
markerScanQueue.remove(key);
}
});
}
private Map<IrisPosition, MarkerSpawnData> collectMarkerSpawnData(int chunkX, int chunkZ) {
Map<IrisPosition, MarkerSpawnData> markerData = new KMap<>();
getMantle().iterateChunk(chunkX, chunkZ, MatterMarker.class, (x, y, z, t) -> {
if (t.getTag().equals("cave_floor") || t.getTag().equals("cave_ceiling")) {
return;
}
IrisMarker mark = getData().getMarkerLoader().load(t.getTag());
if (mark == null) {
return;
}
IrisPosition position = new IrisPosition((chunkX << 4) + x, y, (chunkZ << 4) + z);
MarkerSpawnData data = markerData.computeIfAbsent(position, k -> new MarkerSpawnData());
data.requiresEmptyAbove = data.requiresEmptyAbove || mark.isEmptyAbove();
for (String i : mark.getSpawners()) {
IrisSpawner spawner = getData().getSpawnerLoader().load(i);
if (spawner == null) {
Iris.error("Cannot load spawner: " + i + " for marker on " + getName());
continue;
}
spawner.setReferenceMarker(mark);
data.spawners.add(spawner);
}
});
return markerData;
}
private boolean isMarkerObstructed(Chunk chunk, IrisPosition relative, boolean requiresEmptyAbove) {
if (!requiresEmptyAbove) {
return false;
}
int minY = getEngine().getWorld().minHeight();
int markerY = relative.getY() + minY;
if (markerY + 2 >= chunk.getWorld().getMaxHeight()) {
return true;
}
int localX = relative.getX() & 15;
int localZ = relative.getZ() & 15;
return chunk.getBlock(localX, markerY + 1, localZ).getBlockData().getMaterial().isSolid()
|| chunk.getBlock(localX, markerY + 2, localZ).getBlockData().getMaterial().isSolid();
}
private void removeMarkerAsync(IrisPosition marker) {
J.a(() -> {
try {
getMantle().remove(marker.getX(), marker.getY(), marker.getZ(), MatterMarker.class);
} catch (Throwable e) {
Iris.reportError(e);
}
});
}
private static final class MarkerSpawnData {
private final KSet<IrisSpawner> spawners = new KSet<>();
private boolean requiresEmptyAbove;
}
@Override @Override
public void onBlockBreak(BlockBreakEvent e) { public void onBlockBreak(BlockBreakEvent e) {
if (e.getBlock().getWorld().equals(getTarget().getWorld().realWorld())) { if (e.getBlock().getWorld().equals(getTarget().getWorld().realWorld())) {
@@ -31,6 +31,7 @@ import art.arcane.iris.core.nms.container.Pair;
import art.arcane.iris.core.pregenerator.ChunkUpdater; import art.arcane.iris.core.pregenerator.ChunkUpdater;
import art.arcane.iris.core.scripting.environment.EngineEnvironment; import art.arcane.iris.core.scripting.environment.EngineEnvironment;
import art.arcane.iris.core.service.ExternalDataSVC; import art.arcane.iris.core.service.ExternalDataSVC;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.IrisComplex; import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.data.cache.Cache; import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.data.chunk.TerrainChunk; import art.arcane.iris.engine.data.chunk.TerrainChunk;
@@ -1003,6 +1004,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
} }
default void cleanupMantleChunk(int x, int z) { default void cleanupMantleChunk(int x, int z) {
World world = getWorld().realWorld();
if (world != null && IrisToolbelt.isWorldMaintenanceActive(world)) {
return;
}
if (IrisSettings.get().getPerformance().isTrimMantleInStudio() || !isStudio()) { if (IrisSettings.get().getPerformance().isTrimMantleInStudio() || !isStudio()) {
getMantle().cleanupChunk(x, z); getMantle().cleanupChunk(x, z);
} }
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.framework; package art.arcane.iris.engine.framework;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.IrisComplex; import art.arcane.iris.engine.IrisComplex;
import art.arcane.iris.engine.mantle.EngineMantle; import art.arcane.iris.engine.mantle.EngineMantle;
import art.arcane.iris.util.context.ChunkContext; import art.arcane.iris.util.context.ChunkContext;
@@ -27,6 +28,7 @@ import art.arcane.iris.util.hunk.Hunk;
import art.arcane.volmlib.util.math.RollingSequence; import art.arcane.volmlib.util.math.RollingSequence;
import art.arcane.iris.util.parallel.BurstExecutor; import art.arcane.iris.util.parallel.BurstExecutor;
import art.arcane.iris.util.parallel.MultiBurst; import art.arcane.iris.util.parallel.MultiBurst;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.block.Biome; import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
@@ -69,7 +71,14 @@ public interface EngineMode extends Staged {
@BlockCoordinates @BlockCoordinates
default void generate(int x, int z, Hunk<BlockData> blocks, Hunk<Biome> biomes, boolean multicore) { default void generate(int x, int z, Hunk<BlockData> blocks, Hunk<Biome> biomes, boolean multicore) {
ChunkContext ctx = new ChunkContext(x, z, getComplex()); boolean cacheContext = true;
if (J.isFolia()) {
var world = getEngine().getWorld().realWorld();
if (world != null && IrisToolbelt.isWorldMaintenanceActive(world)) {
cacheContext = false;
}
}
ChunkContext ctx = new ChunkContext(x, z, getComplex(), cacheContext);
IrisContext.getOr(getEngine()).setChunkContext(ctx); IrisContext.getOr(getEngine()).setChunkContext(ctx);
for (EngineStage i : getStages()) { for (EngineStage i : getStages()) {
@@ -42,6 +42,8 @@ import art.arcane.volmlib.util.matter.MatterMarker;
import art.arcane.iris.util.matter.*; import art.arcane.iris.util.matter.*;
import art.arcane.iris.util.matter.slices.UpdateMatter; import art.arcane.iris.util.matter.slices.UpdateMatter;
import art.arcane.iris.util.parallel.MultiBurst; import art.arcane.iris.util.parallel.MultiBurst;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.World;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import org.jetbrains.annotations.UnmodifiableView; import org.jetbrains.annotations.UnmodifiableView;
@@ -80,6 +82,13 @@ public interface EngineMantle extends MatterGenerator {
@ChunkCoordinates @ChunkCoordinates
default KList<IrisPosition> findMarkers(int x, int z, MatterMarker marker) { default KList<IrisPosition> findMarkers(int x, int z, MatterMarker marker) {
KList<IrisPosition> p = new KList<>(); KList<IrisPosition> p = new KList<>();
if (J.isFolia()) {
World world = getEngine().getWorld().realWorld();
if (world != null && J.isOwnedByCurrentRegion(world, x, z)) {
return p;
}
}
getMantle().iterateChunk(x, z, MatterMarker.class, (xx, yy, zz, mm) -> { getMantle().iterateChunk(x, z, MatterMarker.class, (xx, yy, zz, mm) -> {
if (marker.equals(mm)) { if (marker.equals(mm)) {
p.add(new IrisPosition(xx + (x << 4), yy, zz + (z << 4))); p.add(new IrisPosition(xx + (x << 4), yy, zz + (z << 4)));
@@ -20,6 +20,8 @@ package art.arcane.iris.engine.mantle;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.engine.data.cache.Cache; import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -40,6 +42,7 @@ import art.arcane.iris.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern; import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.iris.util.matter.TileWrapper; import art.arcane.iris.util.matter.TileWrapper;
import art.arcane.iris.util.noise.CNG; import art.arcane.iris.util.noise.CNG;
import art.arcane.iris.util.scheduling.J;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import lombok.Data; import lombok.Data;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
@@ -67,7 +70,12 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
this.x = x; this.x = x;
this.z = z; this.z = z;
final int parallelism = multicore ? Runtime.getRuntime().availableProcessors() / 2 : 4; final boolean foliaMaintenance = J.isFolia()
&& IrisToolbelt.isWorldMaintenanceActive(engineMantle.getEngine().getWorld().realWorld());
final int parallelism = foliaMaintenance ? 1 : (multicore ? Runtime.getRuntime().availableProcessors() / 2 : 4);
if (foliaMaintenance && IrisSettings.get().getGeneral().isDebug()) {
Iris.info("MantleWriter using sequential chunk prefetch for maintenance regen at " + x + "," + z + ".");
}
final var map = multicore ? cachedChunks : new KMap<Long, MantleChunk>(d * d, 1f, parallelism); final var map = multicore ? cachedChunks : new KMap<Long, MantleChunk>(d * d, 1f, parallelism);
mantle.getChunks( mantle.getChunks(
x - radius, x - radius,
@@ -19,6 +19,8 @@
package art.arcane.iris.engine.mantle.components; package art.arcane.iris.engine.mantle.components;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.data.cache.Cache; import art.arcane.iris.engine.data.cache.Cache;
import art.arcane.iris.engine.mantle.ComponentFlag; import art.arcane.iris.engine.mantle.ComponentFlag;
import art.arcane.iris.engine.mantle.EngineMantle; import art.arcane.iris.engine.mantle.EngineMantle;
@@ -39,6 +41,7 @@ import art.arcane.iris.util.matter.MatterStructurePOI;
import art.arcane.iris.util.noise.CNG; import art.arcane.iris.util.noise.CNG;
import art.arcane.iris.util.noise.NoiseType; import art.arcane.iris.util.noise.NoiseType;
import art.arcane.iris.util.parallel.BurstExecutor; import art.arcane.iris.util.parallel.BurstExecutor;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.util.BlockVector; import org.bukkit.util.BlockVector;
import java.io.IOException; import java.io.IOException;
@@ -55,12 +58,32 @@ public class MantleObjectComponent extends IrisMantleComponent {
@Override @Override
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) { public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
boolean traceRegen = isRegenTraceThread();
RNG rng = applyNoise(x, z, Cache.key(x, z) + seed()); RNG rng = applyNoise(x, z, Cache.key(x, z) + seed());
int xxx = 8 + (x << 4); int xxx = 8 + (x << 4);
int zzz = 8 + (z << 4); int zzz = 8 + (z << 4);
IrisRegion region = getComplex().getRegionStream().get(xxx, zzz); IrisRegion region = getComplex().getRegionStream().get(xxx, zzz);
IrisBiome biome = getComplex().getTrueBiomeStream().get(xxx, zzz); IrisBiome biome = getComplex().getTrueBiomeStream().get(xxx, zzz);
placeObjects(writer, rng, x, z, biome, region); if (traceRegen) {
Iris.info("Regen object layer start: chunk=" + x + "," + z
+ " biome=" + biome.getLoadKey()
+ " region=" + region.getLoadKey()
+ " biomePlacers=" + biome.getSurfaceObjects().size()
+ " regionPlacers=" + region.getSurfaceObjects().size());
}
ObjectPlacementSummary summary = placeObjects(writer, rng, x, z, biome, region, traceRegen);
if (traceRegen) {
Iris.info("Regen object layer done: chunk=" + x + "," + z
+ " biomePlacersChecked=" + summary.biomePlacersChecked()
+ " biomePlacersTriggered=" + summary.biomePlacersTriggered()
+ " regionPlacersChecked=" + summary.regionPlacersChecked()
+ " regionPlacersTriggered=" + summary.regionPlacersTriggered()
+ " objectAttempts=" + summary.objectAttempts()
+ " objectPlaced=" + summary.objectPlaced()
+ " objectRejected=" + summary.objectRejected()
+ " objectNull=" + summary.objectNull()
+ " objectErrors=" + summary.objectErrors());
}
} }
private RNG applyNoise(int x, int z, long seed) { private RNG applyNoise(int x, int z, long seed) {
@@ -69,12 +92,39 @@ public class MantleObjectComponent extends IrisMantleComponent {
} }
@ChunkCoordinates @ChunkCoordinates
private void placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome biome, IrisRegion region) { private ObjectPlacementSummary placeObjects(MantleWriter writer, RNG rng, int x, int z, IrisBiome biome, IrisRegion region, boolean traceRegen) {
int biomeChecked = 0;
int biomeTriggered = 0;
int regionChecked = 0;
int regionTriggered = 0;
int attempts = 0;
int placed = 0;
int rejected = 0;
int nullObjects = 0;
int errors = 0;
for (IrisObjectPlacement i : biome.getSurfaceObjects()) { for (IrisObjectPlacement i : biome.getSurfaceObjects()) {
if (rng.chance(i.getChance() + rng.d(-0.005, 0.005))) { biomeChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=biome"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
biomeTriggered++;
try { try {
placeObject(writer, rng, x << 4, z << 4, i); ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, traceRegen, x, z, "biome");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) { } catch (Throwable e) {
errors++;
Iris.reportError(e); Iris.reportError(e);
Iris.error("Failed to place objects in the following biome: " + biome.getName()); Iris.error("Failed to place objects in the following biome: " + biome.getName());
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
@@ -85,10 +135,27 @@ public class MantleObjectComponent extends IrisMantleComponent {
} }
for (IrisObjectPlacement i : region.getSurfaceObjects()) { for (IrisObjectPlacement i : region.getSurfaceObjects()) {
if (rng.chance(i.getChance() + rng.d(-0.005, 0.005))) { regionChecked++;
boolean chance = rng.chance(i.getChance() + rng.d(-0.005, 0.005));
if (traceRegen) {
Iris.info("Regen object placer chance: chunk=" + x + "," + z
+ " scope=region"
+ " chanceResult=" + chance
+ " chanceBase=" + i.getChance()
+ " densityMid=" + i.getDensity()
+ " objects=" + i.getPlace().size());
}
if (chance) {
regionTriggered++;
try { try {
placeObject(writer, rng, x << 4, z << 4, i); ObjectPlacementResult result = placeObject(writer, rng, x << 4, z << 4, i, traceRegen, x, z, "region");
attempts += result.attempts();
placed += result.placed();
rejected += result.rejected();
nullObjects += result.nullObjects();
errors += result.errors();
} catch (Throwable e) { } catch (Throwable e) {
errors++;
Iris.reportError(e); Iris.reportError(e);
Iris.error("Failed to place objects in the following region: " + region.getName()); Iris.error("Failed to place objects in the following region: " + region.getName());
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ")."); Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
@@ -97,25 +164,114 @@ public class MantleObjectComponent extends IrisMantleComponent {
} }
} }
} }
return new ObjectPlacementSummary(
biomeChecked,
biomeTriggered,
regionChecked,
regionTriggered,
attempts,
placed,
rejected,
nullObjects,
errors
);
} }
@BlockCoordinates @BlockCoordinates
private void placeObject(MantleWriter writer, RNG rng, int x, int z, IrisObjectPlacement objectPlacement) { private ObjectPlacementResult placeObject(
for (int i = 0; i < objectPlacement.getDensity(rng, x, z, getData()); i++) { MantleWriter writer,
RNG rng,
int x,
int z,
IrisObjectPlacement objectPlacement,
boolean traceRegen,
int chunkX,
int chunkZ,
String scope
) {
int attempts = 0;
int placed = 0;
int rejected = 0;
int nullObjects = 0;
int errors = 0;
int density = objectPlacement.getDensity(rng, x, z, getData());
for (int i = 0; i < density; i++) {
attempts++;
IrisObject v = objectPlacement.getScale().get(rng, objectPlacement.getObject(getComplex(), rng)); IrisObject v = objectPlacement.getScale().get(rng, objectPlacement.getObject(getComplex(), rng));
if (v == null) { if (v == null) {
return; nullObjects++;
if (traceRegen) {
Iris.warn("Regen object placement null object: chunk=" + chunkX + "," + chunkZ
+ " scope=" + scope
+ " densityIndex=" + i
+ " density=" + density
+ " placementKeys=" + objectPlacement.getPlace().toString(","));
}
continue;
} }
int xx = rng.i(x, x + 15); int xx = rng.i(x, x + 15);
int zz = rng.i(z, z + 15); int zz = rng.i(z, z + 15);
int id = rng.i(0, Integer.MAX_VALUE); int id = rng.i(0, Integer.MAX_VALUE);
v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> { try {
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id); int result = v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> {
if (objectPlacement.isDolphinTarget() && objectPlacement.isUnderwater() && B.isStorageChest(data)) { writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE); if (objectPlacement.isDolphinTarget() && objectPlacement.isUnderwater() && B.isStorageChest(data)) {
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
}
}, null, getData());
if (result >= 0) {
placed++;
} else {
rejected++;
} }
}, null, getData());
if (traceRegen) {
Iris.info("Regen object placement result: chunk=" + chunkX + "," + chunkZ
+ " scope=" + scope
+ " object=" + v.getLoadKey()
+ " resultY=" + result
+ " px=" + xx
+ " pz=" + zz
+ " densityIndex=" + i
+ " density=" + density);
}
} catch (Throwable e) {
errors++;
Iris.reportError(e);
Iris.error("Regen object placement exception: chunk=" + chunkX + "," + chunkZ
+ " scope=" + scope
+ " object=" + v.getLoadKey()
+ " densityIndex=" + i
+ " density=" + density
+ " error=" + e.getClass().getSimpleName() + ":" + e.getMessage());
}
} }
return new ObjectPlacementResult(attempts, placed, rejected, nullObjects, errors);
}
private boolean isRegenTraceThread() {
return Thread.currentThread().getName().startsWith("Iris-Regen-")
&& IrisSettings.get().getGeneral().isDebug();
}
private record ObjectPlacementSummary(
int biomePlacersChecked,
int biomePlacersTriggered,
int regionPlacersChecked,
int regionPlacersTriggered,
int objectAttempts,
int objectPlaced,
int objectRejected,
int objectNull,
int objectErrors
) {
}
private record ObjectPlacementResult(int attempts, int placed, int rejected, int nullObjects, int errors) {
} }
@BlockCoordinates @BlockCoordinates
@@ -182,6 +338,15 @@ public class MantleObjectComponent extends IrisMantleComponent {
} }
BurstExecutor e = getEngineMantle().getTarget().getBurster().burst(objects.size()); BurstExecutor e = getEngineMantle().getTarget().getBurster().burst(objects.size());
boolean maintenanceFolia = false;
if (J.isFolia()) {
var world = getEngineMantle().getEngine().getWorld().realWorld();
maintenanceFolia = world != null && IrisToolbelt.isWorldMaintenanceActive(world);
}
if (maintenanceFolia) {
Iris.info("MantleObjectComponent radius scan using single-threaded mode during maintenance regen.");
e.setMulticore(false);
}
KMap<String, BlockVector> sizeCache = new KMap<>(); KMap<String, BlockVector> sizeCache = new KMap<>();
for (String i : objects) { for (String i : objects) {
e.queue(() -> { e.queue(() -> {
@@ -18,6 +18,7 @@
package art.arcane.iris.engine.mode; package art.arcane.iris.engine.mode;
import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.actuator.IrisBiomeActuator; import art.arcane.iris.engine.actuator.IrisBiomeActuator;
import art.arcane.iris.engine.actuator.IrisDecorantActuator; import art.arcane.iris.engine.actuator.IrisDecorantActuator;
import art.arcane.iris.engine.actuator.IrisTerrainNormalActuator; import art.arcane.iris.engine.actuator.IrisTerrainNormalActuator;
@@ -26,9 +27,14 @@ import art.arcane.iris.engine.framework.EngineMode;
import art.arcane.iris.engine.framework.EngineStage; import art.arcane.iris.engine.framework.EngineStage;
import art.arcane.iris.engine.framework.IrisEngineMode; import art.arcane.iris.engine.framework.IrisEngineMode;
import art.arcane.iris.engine.modifier.*; import art.arcane.iris.engine.modifier.*;
import art.arcane.iris.util.scheduling.J;
import org.bukkit.block.data.BlockData; import org.bukkit.block.data.BlockData;
import java.util.concurrent.atomic.AtomicLong;
public class ModeOverworld extends IrisEngineMode implements EngineMode { public class ModeOverworld extends IrisEngineMode implements EngineMode {
private static final AtomicLong lastMaintenanceBypassLog = new AtomicLong(0L);
public ModeOverworld(Engine engine) { public ModeOverworld(Engine engine) {
super(engine); super(engine);
var terrain = new IrisTerrainNormalActuator(getEngine()); var terrain = new IrisTerrainNormalActuator(getEngine());
@@ -40,15 +46,45 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
var perfection = new IrisPerfectionModifier(getEngine()); var perfection = new IrisPerfectionModifier(getEngine());
var custom = new IrisCustomModifier(getEngine()); var custom = new IrisCustomModifier(getEngine());
EngineStage sBiome = (x, z, k, p, m, c) -> biome.actuate(x, z, p, m, c); EngineStage sBiome = (x, z, k, p, m, c) -> biome.actuate(x, z, p, m, c);
EngineStage sGenMatter = (x, z, k, p, m, c) -> generateMatter(x >> 4, z >> 4, m, c); EngineStage sGenMatter = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages(getEngine())) {
return;
}
generateMatter(x >> 4, z >> 4, m, c);
};
EngineStage sTerrain = (x, z, k, p, m, c) -> terrain.actuate(x, z, k, m, c); EngineStage sTerrain = (x, z, k, p, m, c) -> terrain.actuate(x, z, k, m, c);
EngineStage sDecorant = (x, z, k, p, m, c) -> decorant.actuate(x, z, k, m, c); EngineStage sDecorant = (x, z, k, p, m, c) -> decorant.actuate(x, z, k, m, c);
EngineStage sCave = (x, z, k, p, m, c) -> cave.modify(x >> 4, z >> 4, k, m, c); EngineStage sCave = (x, z, k, p, m, c) -> {
EngineStage sDeposit = (x, z, k, p, m, c) -> deposit.modify(x, z, k, m, c); if (shouldBypassMantleStages(getEngine())) {
EngineStage sPost = (x, z, k, p, m, c) -> post.modify(x, z, k, m, c); return;
EngineStage sInsertMatter = (x, z, K, p, m, c) -> getMantle().insertMatter(x >> 4, z >> 4, BlockData.class, K, m); }
cave.modify(x >> 4, z >> 4, k, m, c);
};
EngineStage sDeposit = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages(getEngine())) {
return;
}
deposit.modify(x, z, k, m, c);
};
EngineStage sPost = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages(getEngine())) {
return;
}
post.modify(x, z, k, m, c);
};
EngineStage sInsertMatter = (x, z, K, p, m, c) -> {
if (shouldBypassMantleStages(getEngine())) {
return;
}
getMantle().insertMatter(x >> 4, z >> 4, BlockData.class, K, m);
};
EngineStage sPerfection = (x, z, k, p, m, c) -> perfection.modify(x, z, k, m, c); EngineStage sPerfection = (x, z, k, p, m, c) -> perfection.modify(x, z, k, m, c);
EngineStage sCustom = (x, z, k, p, m, c) -> custom.modify(x, z, k, m, c); EngineStage sCustom = (x, z, k, p, m, c) -> {
if (shouldBypassMantleStages(getEngine())) {
return;
}
custom.modify(x, z, k, m, c);
};
registerStage(burst( registerStage(burst(
sGenMatter, sGenMatter,
@@ -66,4 +102,21 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
registerStage(sPerfection); registerStage(sPerfection);
registerStage(sCustom); registerStage(sCustom);
} }
private static boolean shouldBypassMantleStages(Engine engine) {
if (!J.isFolia()) {
return false;
}
var world = engine.getWorld().realWorld();
boolean active = world != null && IrisToolbelt.isWorldMaintenanceBypassingMantleStages(world);
if (active) {
long now = System.currentTimeMillis();
long last = lastMaintenanceBypassLog.get();
if (now - last >= 5000L && lastMaintenanceBypassLog.compareAndSet(last, now)) {
art.arcane.iris.Iris.info("Maintenance regen bypass: skipping mantle-backed overworld stages for Folia safety.");
}
}
return active;
}
} }
@@ -28,6 +28,7 @@ import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.math.Vector3d; import art.arcane.volmlib.util.math.Vector3d;
import art.arcane.volmlib.util.matter.MatterMarker; import art.arcane.volmlib.util.matter.MatterMarker;
import art.arcane.iris.util.matter.slices.MarkerMatter; import art.arcane.iris.util.matter.slices.MarkerMatter;
import art.arcane.iris.util.scheduling.J;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -77,8 +78,14 @@ public class IrisEntitySpawn implements IRare {
int hf = gen.getHeight(x, z, false) + (gen.getWorld().tryGetRealWorld() ? gen.getWorld().realWorld().getMinHeight() : -64); int hf = gen.getHeight(x, z, false) + (gen.getWorld().tryGetRealWorld() ? gen.getWorld().realWorld().getMinHeight() : -64);
Location l = switch (getReferenceSpawner().getGroup()) { Location l = switch (getReferenceSpawner().getGroup()) {
case NORMAL -> new Location(c.getWorld(), x, hf + 1, z); case NORMAL -> new Location(c.getWorld(), x, hf + 1, z);
case CAVE -> gen.getMantle().findMarkers(c.getX(), c.getZ(), MarkerMatter.CAVE_FLOOR) case CAVE -> {
.convert((i) -> i.toLocation(c.getWorld()).add(0, 1, 0)).getRandom(rng); if (J.isFolia()) {
// Avoid mantle region IO lookups on Folia tick threads.
yield new Location(c.getWorld(), x, h + 1, z);
}
yield gen.getMantle().findMarkers(c.getX(), c.getZ(), MarkerMatter.CAVE_FLOOR)
.convert((i) -> i.toLocation(c.getWorld()).add(0, 1, 0)).getRandom(rng);
}
case UNDERWATER, BEACH -> new Location(c.getWorld(), x, rng.i(h + 1, hf), z); case UNDERWATER, BEACH -> new Location(c.getWorld(), x, rng.i(h + 1, hf), z);
}; };
@@ -113,7 +120,11 @@ public class IrisEntitySpawn implements IRare {
if (spawns > 0) { if (spawns > 0) {
if (referenceMarker != null && referenceMarker.shouldExhaust()) { if (referenceMarker != null && referenceMarker.shouldExhaust()) {
gen.getMantle().getMantle().remove(c.getX(), c.getY() - gen.getWorld().minHeight(), c.getZ(), MatterMarker.class); if (J.isFolia()) {
J.a(() -> gen.getMantle().getMantle().remove(c.getX(), c.getY() - gen.getWorld().minHeight(), c.getZ(), MatterMarker.class));
} else {
gen.getMantle().getMantle().remove(c.getX(), c.getY() - gen.getWorld().minHeight(), c.getZ(), MatterMarker.class);
}
} }
for (int id = 0; id < spawns; id++) { for (int id = 0; id < spawns; id++) {
@@ -65,6 +65,7 @@ import java.util.Random;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@@ -198,74 +199,186 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
@Override @Override
public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) { public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) {
boolean acquired = false;
String phase = "start";
try { try {
loadLock.acquire(); phase = "acquire-load-lock";
long acquireStart = System.currentTimeMillis();
while (!loadLock.tryAcquire(5, TimeUnit.SECONDS)) {
Iris.warn("Chunk replacement waiting for load lock at " + x + "," + z
+ " for " + (System.currentTimeMillis() - acquireStart) + "ms.");
}
acquired = true;
long acquireWait = System.currentTimeMillis() - acquireStart;
if (acquireWait >= 5000L) {
Iris.warn("Chunk replacement waited " + acquireWait + "ms for load lock at " + x + "," + z + ".");
}
IrisBiomeStorage st = new IrisBiomeStorage(); IrisBiomeStorage st = new IrisBiomeStorage();
TerrainChunk tc = TerrainChunk.createUnsafe(world, st); TerrainChunk tc = TerrainChunk.createUnsafe(world, st);
this.world.bind(world); this.world.bind(world);
getEngine().generate(x << 4, z << 4, tc, IrisSettings.get().getGenerator().useMulticore);
Chunk c = PaperLib.getChunkAtAsync(world, x, z) phase = "engine-generate";
.thenApply(d -> { long generateStart = System.currentTimeMillis();
Iris.tickets.addTicket(d); boolean useMulticore = IrisSettings.get().getGenerator().useMulticore && !J.isFolia();
AtomicBoolean generateDone = new AtomicBoolean(false);
AtomicLong generationWatchdogStart = new AtomicLong(System.currentTimeMillis());
Thread generateThread = Thread.currentThread();
J.a(() -> {
while (!generateDone.get()) {
if (!J.sleep(5000)) {
return;
}
if (generateDone.get()) {
return;
}
for (Entity ee : d.getEntities()) { Iris.warn("Chunk replacement still generating at " + x + "," + z
if (ee instanceof Player) { + " for " + (System.currentTimeMillis() - generationWatchdogStart.get()) + "ms"
continue; + " thread=" + generateThread.getName()
} + " state=" + generateThread.getState());
}
});
try {
getEngine().generate(x << 4, z << 4, tc, useMulticore);
} finally {
generateDone.set(true);
}
long generateTook = System.currentTimeMillis() - generateStart;
if (generateTook >= 5000L) {
Iris.warn("Chunk replacement terrain generation took " + generateTook + "ms at " + x + "," + z + ".");
}
ee.remove(); if (J.isFolia()) {
} phase = "folia-run-region";
CountDownLatch latch = new CountDownLatch(1);
return d; Throwable[] failure = new Throwable[1];
}).get(); long regionScheduleStart = System.currentTimeMillis();
if (!J.runRegion(world, x, z, () -> {
try {
KList<CompletableFuture<?>> futures = new KList<>(1 + getEngine().getHeight() >> 4); phaseUnsafeSet("folia-region-run", x, z);
for (int i = getEngine().getHeight() >> 4; i >= 0; i--) { Chunk c = world.getChunkAt(x, z);
int finalI = i << 4; Iris.tickets.addTicket(c);
futures.add(CompletableFuture.runAsync(() -> { try {
for (int xx = 0; xx < 16; xx++) { for (Entity ee : c.getEntities()) {
for (int yy = 0; yy < 16; yy++) { if (ee instanceof Player) {
for (int zz = 0; zz < 16; zz++) {
if (yy + finalI >= engine.getHeight() || yy + finalI < 0) {
continue; continue;
} }
int y = yy + finalI + world.getMinHeight();
c.getBlock(xx, y, zz).setBlockData(tc.getBlockData(xx, y, zz), false); ee.remove();
}
for (int i = getEngine().getHeight() >> 4; i >= 0; i--) {
int finalI = i << 4;
for (int xx = 0; xx < 16; xx++) {
for (int yy = 0; yy < 16; yy++) {
for (int zz = 0; zz < 16; zz++) {
if (yy + finalI >= engine.getHeight() || yy + finalI < 0) {
continue;
}
int y = yy + finalI + world.getMinHeight();
c.getBlock(xx, y, zz).setBlockData(tc.getBlockData(xx, y, zz), false);
}
}
}
}
INMS.get().placeStructures(c);
engine.getWorldManager().onChunkLoad(c, true);
} finally {
Iris.tickets.removeTicket(c);
}
} catch (Throwable e) {
failure[0] = e;
} finally {
latch.countDown();
}
})) {
throw new IllegalStateException("Failed to schedule region task for chunk replacement at " + x + "," + z);
}
long regionScheduleTook = System.currentTimeMillis() - regionScheduleStart;
if (regionScheduleTook >= 1000L) {
Iris.verbose("Chunk replacement region task scheduling took " + regionScheduleTook + "ms at " + x + "," + z + ".");
}
long regionWaitStart = System.currentTimeMillis();
while (!latch.await(5, TimeUnit.SECONDS)) {
Iris.warn("Chunk replacement waiting on region task at " + x + "," + z
+ " for " + (System.currentTimeMillis() - regionWaitStart) + "ms.");
}
long regionWaitTook = System.currentTimeMillis() - regionWaitStart;
if (regionWaitTook >= 5000L) {
Iris.warn("Chunk replacement region task completed after " + regionWaitTook + "ms at " + x + "," + z + ".");
}
if (failure[0] != null) {
throw failure[0];
}
} else {
phase = "paperlib-async-load";
long loadChunkStart = System.currentTimeMillis();
Chunk c = PaperLib.getChunkAtAsync(world, x, z).get();
long loadChunkTook = System.currentTimeMillis() - loadChunkStart;
if (loadChunkTook >= 5000L) {
Iris.warn("Chunk replacement chunk load took " + loadChunkTook + "ms at " + x + "," + z + ".");
}
phase = "non-folia-apply";
Iris.tickets.addTicket(c);
CompletableFuture.runAsync(() -> {
for (Entity ee : c.getEntities()) {
if (ee instanceof Player) {
continue;
}
ee.remove();
}
}, syncExecutor).get();
KList<CompletableFuture<?>> futures = new KList<>(1 + getEngine().getHeight() >> 4);
for (int i = getEngine().getHeight() >> 4; i >= 0; i--) {
int finalI = i << 4;
futures.add(CompletableFuture.runAsync(() -> {
for (int xx = 0; xx < 16; xx++) {
for (int yy = 0; yy < 16; yy++) {
for (int zz = 0; zz < 16; zz++) {
if (yy + finalI >= engine.getHeight() || yy + finalI < 0) {
continue;
}
int y = yy + finalI + world.getMinHeight();
c.getBlock(xx, y, zz).setBlockData(tc.getBlockData(xx, y, zz), false);
}
} }
} }
} }, syncExecutor));
}, syncExecutor)); }
futures.add(CompletableFuture.runAsync(() -> INMS.get().placeStructures(c), syncExecutor));
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRunAsync(() -> {
Iris.tickets.removeTicket(c);
engine.getWorldManager().onChunkLoad(c, true);
}, syncExecutor)
.get();
} }
futures.add(CompletableFuture.runAsync(() -> INMS.get().placeStructures(c), syncExecutor));
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRunAsync(() -> {
Iris.tickets.removeTicket(c);
engine.getWorldManager().onChunkLoad(c, true);
}, syncExecutor)
.get();
Iris.debug("Regenerated " + x + " " + z); Iris.debug("Regenerated " + x + " " + z);
loadLock.release();
} catch (Throwable e) { } catch (Throwable e) {
loadLock.release();
Iris.error("======================================"); Iris.error("======================================");
Iris.error("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z);
e.printStackTrace(); e.printStackTrace();
Iris.reportErrorChunk(x, z, e, "CHUNK"); Iris.reportErrorChunk(x, z, e, "CHUNK");
Iris.error("======================================"); Iris.error("======================================");
throw new IllegalStateException("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z, e);
ChunkData d = Bukkit.createChunkData(world); } finally {
if (acquired) {
for (int i = 0; i < 16; i++) { loadLock.release();
for (int j = 0; j < 16; j++) {
d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData());
}
} }
} }
} }
private static void phaseUnsafeSet(String phase, int x, int z) {
Iris.verbose("Chunk replacement phase=" + phase + " chunk=" + x + "," + z);
}
private Engine getEngine(WorldInfo world) { private Engine getEngine(WorldInfo world) {
if (setup.get()) { if (setup.get()) {
return getEngine(); return getEngine();
@@ -1,10 +1,10 @@
package art.arcane.iris.util.decree; package art.arcane.iris.util.decree;
import art.arcane.volmlib.util.decree.context.DecreeContextBase; import art.arcane.volmlib.util.director.context.DirectorContextBase;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
public class DecreeContext { public class DecreeContext {
private static final DecreeContextBase<VolmitSender> context = new DecreeContextBase<>(); private static final DirectorContextBase<VolmitSender> context = new DirectorContextBase<>();
public static VolmitSender get() { public static VolmitSender get() {
return context.get(); return context.get();
@@ -1,17 +1,17 @@
package art.arcane.iris.util.decree; package art.arcane.iris.util.decree;
import art.arcane.volmlib.util.decree.context.DecreeContextHandlers; import art.arcane.volmlib.util.director.context.DirectorContextHandlers;
import art.arcane.volmlib.util.decree.context.DecreeContextHandlerType; import art.arcane.volmlib.util.director.context.DirectorContextHandlerType;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import java.util.Map; import java.util.Map;
public interface DecreeContextHandler<T> extends DecreeContextHandlerType<T, VolmitSender> { public interface DecreeContextHandler<T> extends DirectorContextHandlerType<T, VolmitSender> {
Map<Class<?>, DecreeContextHandler<?>> contextHandlers = buildContextHandlers(); Map<Class<?>, DecreeContextHandler<?>> contextHandlers = buildContextHandlers();
static Map<Class<?>, DecreeContextHandler<?>> buildContextHandlers() { static Map<Class<?>, DecreeContextHandler<?>> buildContextHandlers() {
return DecreeContextHandlers.buildOrEmpty( return DirectorContextHandlers.buildOrEmpty(
Iris.initialize("art.arcane.iris.util.decree.context"), Iris.initialize("art.arcane.iris.util.decree.context"),
DecreeContextHandler.class, DecreeContextHandler.class,
h -> ((DecreeContextHandler<?>) h).getType(), h -> ((DecreeContextHandler<?>) h).getType(),
@@ -18,7 +18,7 @@
package art.arcane.iris.util.decree; package art.arcane.iris.util.decree;
import art.arcane.volmlib.util.decree.DecreeExecutorBase; import art.arcane.volmlib.util.director.DirectorExecutorBase;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.Engine;
@@ -26,7 +26,7 @@ import art.arcane.iris.engine.platform.PlatformChunkGenerator;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public interface DecreeExecutor extends DecreeExecutorBase { public interface DecreeExecutor extends DirectorExecutorBase {
default VolmitSender sender() { default VolmitSender sender() {
return DecreeContext.get(); return DecreeContext.get();
} }
@@ -1,5 +1,5 @@
package art.arcane.iris.util.decree; package art.arcane.iris.util.decree;
public interface DecreeParameterHandler<T> public interface DirectorParameterHandler<T>
extends DecreeExecutor, art.arcane.volmlib.util.decree.DecreeParameterHandler<T> { extends DecreeExecutor, art.arcane.volmlib.util.director.DirectorParameterHandler<T> {
} }
@@ -18,11 +18,11 @@
package art.arcane.iris.util.decree; package art.arcane.iris.util.decree;
import art.arcane.volmlib.util.decree.DecreeSystemSupport; import art.arcane.volmlib.util.director.DirectorSystemSupport;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
public final class DecreeSystem { public final class DecreeSystem {
public static final KList<DecreeParameterHandler<?>> handlers = Iris.initialize("art.arcane.iris.util.decree.handlers", null).convert((i) -> (DecreeParameterHandler<?>) i); public static final KList<DirectorParameterHandler<?>> handlers = Iris.initialize("art.arcane.iris.util.decree.handlers", null).convert((i) -> (DirectorParameterHandler<?>) i);
private DecreeSystem() { private DecreeSystem() {
} }
@@ -31,15 +31,15 @@ public final class DecreeSystem {
* Get the handler for the specified type * Get the handler for the specified type
* *
* @param type The type to handle * @param type The type to handle
* @return The corresponding {@link DecreeParameterHandler}, or null * @return The corresponding {@link DirectorParameterHandler}, or null
*/ */
public static DecreeParameterHandler<?> getHandler(Class<?> type) { public static DirectorParameterHandler<?> getHandler(Class<?> type) {
DecreeParameterHandler<?> handler = DecreeSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t)); DirectorParameterHandler<?> handler = DirectorSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t));
if (handler != null) { if (handler != null) {
return handler; return handler;
} }
Iris.error("Unhandled type in Decree Parameter: " + type.getName() + ". This is bad!"); Iris.error("Unhandled type in Director Parameter: " + type.getName() + ". This is bad!");
return null; return null;
} }
} }
@@ -1,6 +1,6 @@
package art.arcane.iris.util.decree.context; package art.arcane.iris.util.decree.context;
import art.arcane.volmlib.util.decree.context.WorldContextHandlerBase; import art.arcane.volmlib.util.director.context.WorldContextHandlerBase;
import art.arcane.iris.util.decree.DecreeContextHandler; import art.arcane.iris.util.decree.DecreeContextHandler;
import art.arcane.iris.util.plugin.VolmitSender; import art.arcane.iris.util.plugin.VolmitSender;
import org.bukkit.World; import org.bukkit.World;
@@ -1,9 +1,9 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.iris.util.decree.DecreeContext; import art.arcane.iris.util.decree.DecreeContext;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.iris.util.decree.DecreeSystem; import art.arcane.iris.util.decree.DecreeSystem;
import art.arcane.volmlib.util.decree.handlers.base.BlockVectorHandlerBase; import art.arcane.volmlib.util.director.handlers.base.BlockVectorHandlerBase;
import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.Form;
import org.bukkit.FluidCollisionMode; import org.bukkit.FluidCollisionMode;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -11,7 +11,7 @@ import org.bukkit.util.BlockVector;
import java.util.List; import java.util.List;
public class BlockVectorHandler extends BlockVectorHandlerBase implements DecreeParameterHandler<BlockVector> { public class BlockVectorHandler extends BlockVectorHandlerBase implements DirectorParameterHandler<BlockVector> {
@Override @Override
protected boolean isSenderPlayer() { protected boolean isSenderPlayer() {
return DecreeContext.get().isPlayer(); return DecreeContext.get().isPlayer();
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.BooleanHandlerBase; import art.arcane.volmlib.util.director.handlers.base.BooleanHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class BooleanHandler extends BooleanHandlerBase implements DecreeParameterHandler<Boolean> { public class BooleanHandler extends BooleanHandlerBase implements DirectorParameterHandler<Boolean> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.ByteHandlerBase; import art.arcane.volmlib.util.director.handlers.base.ByteHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class ByteHandler extends ByteHandlerBase implements DecreeParameterHandler<Byte> { public class ByteHandler extends ByteHandlerBase implements DirectorParameterHandler<Byte> {
} }
@@ -2,10 +2,10 @@ package art.arcane.iris.util.decree.handlers;
import art.arcane.iris.core.nms.datapack.DataVersion; import art.arcane.iris.core.nms.datapack.DataVersion;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
public class DataVersionHandler implements DecreeParameterHandler<DataVersion> { public class DataVersionHandler implements DirectorParameterHandler<DataVersion> {
@Override @Override
public KList<DataVersion> getPossibilities() { public KList<DataVersion> getPossibilities() {
return new KList<>(DataVersion.values()).qdel(DataVersion.UNSUPPORTED); return new KList<>(DataVersion.values()).qdel(DataVersion.UNSUPPORTED);
@@ -17,7 +17,7 @@ public class DataVersionHandler implements DecreeParameterHandler<DataVersion> {
} }
@Override @Override
public DataVersion parse(String in, boolean force) throws DecreeParsingException { public DataVersion parse(String in, boolean force) throws DirectorParsingException {
if (in.equalsIgnoreCase("latest")) { if (in.equalsIgnoreCase("latest")) {
return DataVersion.getLatest(); return DataVersion.getLatest();
} }
@@ -26,7 +26,7 @@ public class DataVersionHandler implements DecreeParameterHandler<DataVersion> {
return v; return v;
} }
} }
throw new DecreeParsingException("Unable to parse data version \"" + in + "\""); throw new DirectorParsingException("Unable to parse data version \"" + in + "\"");
} }
@Override @Override
@@ -22,7 +22,7 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import art.arcane.iris.util.decree.specialhandlers.RegistrantHandler; import art.arcane.iris.util.decree.specialhandlers.RegistrantHandler;
import java.util.Locale; import java.util.Locale;
@@ -33,7 +33,7 @@ public class DimensionHandler extends RegistrantHandler<IrisDimension> {
} }
@Override @Override
public IrisDimension parse(String in, boolean force) throws DecreeParsingException { public IrisDimension parse(String in, boolean force) throws DirectorParsingException {
String key = in.trim(); String key = in.trim();
if (key.equalsIgnoreCase("default")) { if (key.equalsIgnoreCase("default")) {
key = IrisSettings.get().getGenerator().getDefaultWorldType(); key = IrisSettings.get().getGenerator().getDefaultWorldType();
@@ -41,7 +41,7 @@ public class DimensionHandler extends RegistrantHandler<IrisDimension> {
try { try {
return super.parse(key, force); return super.parse(key, force);
} catch (DecreeParsingException ignored) { } catch (DirectorParsingException ignored) {
String normalized = key.toLowerCase(Locale.ROOT); String normalized = key.toLowerCase(Locale.ROOT);
IrisDimension resolved = IrisToolbelt.getDimension(normalized); IrisDimension resolved = IrisToolbelt.getDimension(normalized);
if (resolved != null) { if (resolved != null) {
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.DoubleHandlerBase; import art.arcane.volmlib.util.director.handlers.base.DoubleHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class DoubleHandler extends DoubleHandlerBase implements DecreeParameterHandler<Double> { public class DoubleHandler extends DoubleHandlerBase implements DirectorParameterHandler<Double> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.FloatHandlerBase; import art.arcane.volmlib.util.director.handlers.base.FloatHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class FloatHandler extends FloatHandlerBase implements DecreeParameterHandler<Float> { public class FloatHandler extends FloatHandlerBase implements DirectorParameterHandler<Float> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.IntegerHandlerBase; import art.arcane.volmlib.util.director.handlers.base.IntegerHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class IntegerHandler extends IntegerHandlerBase implements DecreeParameterHandler<Integer> { public class IntegerHandler extends IntegerHandlerBase implements DirectorParameterHandler<Integer> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.LongHandlerBase; import art.arcane.volmlib.util.director.handlers.base.LongHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class LongHandler extends LongHandlerBase implements DecreeParameterHandler<Long> { public class LongHandler extends LongHandlerBase implements DirectorParameterHandler<Long> {
} }
@@ -18,9 +18,9 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.BukkitPlayerHandlerBase; import art.arcane.volmlib.util.director.handlers.base.BukkitPlayerHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class PlayerHandler extends BukkitPlayerHandlerBase implements DecreeParameterHandler<Player> { public class PlayerHandler extends BukkitPlayerHandlerBase implements DirectorParameterHandler<Player> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.ShortHandlerBase; import art.arcane.volmlib.util.director.handlers.base.ShortHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class ShortHandler extends ShortHandlerBase implements DecreeParameterHandler<Short> { public class ShortHandler extends ShortHandlerBase implements DirectorParameterHandler<Short> {
} }
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.StringHandlerBase; import art.arcane.volmlib.util.director.handlers.StringHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class StringHandler extends StringHandlerBase implements DecreeParameterHandler<String> { public class StringHandler extends StringHandlerBase implements DirectorParameterHandler<String> {
} }
@@ -1,9 +1,9 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.iris.util.decree.DecreeContext; import art.arcane.iris.util.decree.DecreeContext;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.iris.util.decree.DecreeSystem; import art.arcane.iris.util.decree.DecreeSystem;
import art.arcane.volmlib.util.decree.handlers.base.VectorHandlerBase; import art.arcane.volmlib.util.director.handlers.base.VectorHandlerBase;
import art.arcane.volmlib.util.format.Form; import art.arcane.volmlib.util.format.Form;
import org.bukkit.FluidCollisionMode; import org.bukkit.FluidCollisionMode;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@@ -11,7 +11,7 @@ import org.bukkit.util.Vector;
import java.util.List; import java.util.List;
public class VectorHandler extends VectorHandlerBase implements DecreeParameterHandler<Vector> { public class VectorHandler extends VectorHandlerBase implements DirectorParameterHandler<Vector> {
@Override @Override
protected boolean isSenderPlayer() { protected boolean isSenderPlayer() {
return DecreeContext.get().isPlayer(); return DecreeContext.get().isPlayer();
@@ -1,10 +1,10 @@
package art.arcane.iris.util.decree.handlers; package art.arcane.iris.util.decree.handlers;
import art.arcane.volmlib.util.decree.handlers.base.WorldHandlerBase; import art.arcane.volmlib.util.director.handlers.base.WorldHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import org.bukkit.World; import org.bukkit.World;
public class WorldHandler extends WorldHandlerBase implements DecreeParameterHandler<World> { public class WorldHandler extends WorldHandlerBase implements DirectorParameterHandler<World> {
@Override @Override
protected String excludedPrefix() { protected String excludedPrefix() {
return "iris/"; return "iris/";
@@ -1,7 +1,7 @@
package art.arcane.iris.util.decree.specialhandlers; package art.arcane.iris.util.decree.specialhandlers;
import art.arcane.volmlib.util.decree.handlers.base.DummyHandlerBase; import art.arcane.volmlib.util.director.handlers.base.DummyHandlerBase;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
public class DummyHandler extends DummyHandlerBase implements DecreeParameterHandler<Object> { public class DummyHandler extends DummyHandlerBase implements DirectorParameterHandler<Object> {
} }
@@ -22,7 +22,7 @@ import art.arcane.iris.core.IrisSettings;
import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.core.tools.IrisToolbelt;
import art.arcane.iris.engine.object.IrisDimension; import art.arcane.iris.engine.object.IrisDimension;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import java.util.Locale; import java.util.Locale;
@@ -32,7 +32,7 @@ public class NullableDimensionHandler extends RegistrantHandler<IrisDimension> {
} }
@Override @Override
public IrisDimension parse(String in, boolean force) throws DecreeParsingException { public IrisDimension parse(String in, boolean force) throws DirectorParsingException {
String key = in.trim(); String key = in.trim();
if (key.equalsIgnoreCase("default")) { if (key.equalsIgnoreCase("default")) {
key = IrisSettings.get().getGenerator().getDefaultWorldType(); key = IrisSettings.get().getGenerator().getDefaultWorldType();
@@ -40,7 +40,7 @@ public class NullableDimensionHandler extends RegistrantHandler<IrisDimension> {
try { try {
return super.parse(key, force); return super.parse(key, force);
} catch (DecreeParsingException ignored) { } catch (DirectorParsingException ignored) {
String normalized = key.toLowerCase(Locale.ROOT); String normalized = key.toLowerCase(Locale.ROOT);
IrisDimension resolved = IrisToolbelt.getDimension(normalized); IrisDimension resolved = IrisToolbelt.getDimension(normalized);
if (resolved != null) { if (resolved != null) {
@@ -1,14 +1,14 @@
package art.arcane.iris.util.decree.specialhandlers; package art.arcane.iris.util.decree.specialhandlers;
import art.arcane.volmlib.util.decree.handlers.base.NullablePlayerHandlerBase; import art.arcane.volmlib.util.director.handlers.base.NullablePlayerHandlerBase;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.iris.util.decree.handlers.PlayerHandler; import art.arcane.iris.util.decree.handlers.PlayerHandler;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
public class NullablePlayerHandler extends PlayerHandler implements DecreeParameterHandler<Player> { public class NullablePlayerHandler extends PlayerHandler implements DirectorParameterHandler<Player> {
@Override @Override
public Player parse(String in, boolean force) throws DecreeParsingException { public Player parse(String in, boolean force) throws DirectorParsingException {
return NullablePlayerHandlerBase.parseNullable(this, in); return NullablePlayerHandlerBase.parseNullable(this, in);
} }
} }
@@ -21,13 +21,13 @@ package art.arcane.iris.util.decree.specialhandlers;
import art.arcane.iris.Iris; import art.arcane.iris.Iris;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import java.io.File; import java.io.File;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class ObjectHandler implements DecreeParameterHandler<String> { public class ObjectHandler implements DirectorParameterHandler<String> {
@Override @Override
public KList<String> getPossibilities() { public KList<String> getPossibilities() {
KList<String> p = new KList<>(); KList<String> p = new KList<>();
@@ -53,16 +53,16 @@ public class ObjectHandler implements DecreeParameterHandler<String> {
} }
@Override @Override
public String parse(String in, boolean force) throws DecreeParsingException { public String parse(String in, boolean force) throws DirectorParsingException {
KList<String> options = getPossibilities(in); KList<String> options = getPossibilities(in);
if (options.isEmpty()) { if (options.isEmpty()) {
throw new DecreeParsingException("Unable to find Object \"" + in + "\""); throw new DirectorParsingException("Unable to find Object \"" + in + "\"");
} }
try { try {
return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0); return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0);
} catch (Throwable e) { } catch (Throwable e) {
throw new DecreeParsingException("Unable to filter which Object \"" + in + "\""); throw new DirectorParsingException("Unable to filter which Object \"" + in + "\"");
} }
} }
@@ -4,14 +4,14 @@ import art.arcane.iris.Iris;
import art.arcane.iris.core.loader.IrisData; import art.arcane.iris.core.loader.IrisData;
import art.arcane.iris.core.loader.IrisRegistrant; import art.arcane.iris.core.loader.IrisRegistrant;
import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.decree.DecreeParameterHandler; import art.arcane.iris.util.decree.DirectorParameterHandler;
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException; import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
import java.io.File; import java.io.File;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
public abstract class RegistrantHandler<T extends IrisRegistrant> implements DecreeParameterHandler<T> { public abstract class RegistrantHandler<T extends IrisRegistrant> implements DirectorParameterHandler<T> {
private final Class<T> type; private final Class<T> type;
private final String name; private final String name;
private final boolean nullable; private final boolean nullable;
@@ -54,19 +54,19 @@ public abstract class RegistrantHandler<T extends IrisRegistrant> implements Dec
} }
@Override @Override
public T parse(String in, boolean force) throws DecreeParsingException { public T parse(String in, boolean force) throws DirectorParsingException {
if (in.equals("null") && nullable) { if (in.equals("null") && nullable) {
return null; return null;
} }
KList<T> options = getPossibilities(in); KList<T> options = getPossibilities(in);
if (options.isEmpty()) { if (options.isEmpty()) {
throw new DecreeParsingException("Unable to find " + name + " \"" + in + "\""); throw new DirectorParsingException("Unable to find " + name + " \"" + in + "\"");
} }
return options.stream() return options.stream()
.filter((i) -> toString(i).equalsIgnoreCase(in)) .filter((i) -> toString(i).equalsIgnoreCase(in))
.findFirst() .findFirst()
.orElseThrow(() -> new DecreeParsingException("Unable to filter which " + name + " \"" + in + "\"")); .orElseThrow(() -> new DirectorParsingException("Unable to filter which " + name + " \"" + in + "\""));
} }
@Override @Override
@@ -54,9 +54,16 @@ public class IOWorker {
public IOWorker(File root, int worldHeight) { public IOWorker(File root, int worldHeight) {
this.worldHeight = worldHeight; this.worldHeight = worldHeight;
this.support = new IOWorkerSupport(root, 128, (name, millis) -> this.support = new IOWorkerSupport(root, 128, (name, millis) -> {
Iris.debug("Acquired Channel for " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(millis, 2)) String threadName = Thread.currentThread().getName();
); String msg = "Acquired Channel for " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(millis, 2)
+ C.GRAY + " thread=" + threadName;
if (millis >= 1000L) {
Iris.warn(msg);
} else {
Iris.debug(msg);
}
});
this.runtime = new IOWorkerRuntimeSupport(support, LZ4_CODEC); this.runtime = new IOWorkerRuntimeSupport(support, LZ4_CODEC);
} }
@@ -0,0 +1,20 @@
package art.arcane.iris.util.misc;
public final class RegenRuntime {
private static final ThreadLocal<String> RUN_ID = new ThreadLocal<>();
private RegenRuntime() {
}
public static void setRunId(String runId) {
RUN_ID.set(runId);
}
public static String getRunId() {
return RUN_ID.get();
}
public static void clear() {
RUN_ID.remove();
}
}
@@ -11,7 +11,6 @@ import kotlin.script.experimental.dependencies.Repository
import kotlin.script.experimental.dependencies.addRepository import kotlin.script.experimental.dependencies.addRepository
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
import kotlin.script.experimental.jvm.JvmDependency import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader
import kotlin.script.experimental.jvm.updateClasspath import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.classpathFromClassloader import kotlin.script.experimental.jvm.util.classpathFromClassloader
import kotlin.script.experimental.util.PropertiesCollection import kotlin.script.experimental.util.PropertiesCollection
@@ -193,7 +192,7 @@ private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<Scri
} }
internal class SharedClassLoader(parent: ClassLoader = SharedClassLoader::class.java.classLoader) : URLClassLoader(arrayOf(), parent) { internal class SharedClassLoader(parent: ClassLoader = SharedClassLoader::class.java.classLoader) : URLClassLoader(arrayOf(), parent) {
val dependency = JvmDependencyFromClassLoader { this } val dependency get() = JvmDependency(classpath)
fun addFiles(files: List<File>) { fun addFiles(files: List<File>) {
files.forEach { addURL(it.toURI().toURL()) } files.forEach { addURL(it.toURI().toURL()) }
@@ -205,7 +204,10 @@ internal fun ScriptCompilationConfiguration.Builder.configure() {
beforeParsing { context -> try { beforeParsing { context -> try {
context.compilationConfiguration.with { context.compilationConfiguration.with {
if (context.compilationConfiguration[ScriptCompilationConfiguration.server] ?: false) { if (context.compilationConfiguration[ScriptCompilationConfiguration.server] ?: false) {
ScriptCompilationConfiguration.dependencies.append(this[ScriptCompilationConfiguration.sharedClassloader]!!.dependency) val sharedClasspath = this[ScriptCompilationConfiguration.sharedClassloader]!!.classpath
if (sharedClasspath.isNotEmpty()) {
ScriptCompilationConfiguration.dependencies.append(JvmDependency(sharedClasspath))
}
} }
}.asSuccess() }.asSuccess()
} catch (e: Throwable) { } catch (e: Throwable) {
@@ -214,4 +216,4 @@ internal fun ScriptCompilationConfiguration.Builder.configure() {
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations) onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
} }
} }
@@ -1,9 +1,12 @@
package art.arcane.iris.engine.mantle package art.arcane.iris.engine.mantle
import art.arcane.iris.Iris
import art.arcane.iris.core.IrisSettings import art.arcane.iris.core.IrisSettings
import art.arcane.iris.core.nms.container.Pair import art.arcane.iris.core.nms.container.Pair
import art.arcane.iris.engine.framework.Engine import art.arcane.iris.engine.framework.Engine
import art.arcane.iris.util.context.ChunkContext import art.arcane.iris.util.context.ChunkContext
import art.arcane.iris.util.misc.RegenRuntime
import art.arcane.iris.util.matter.TileWrapper
import art.arcane.volmlib.util.documentation.ChunkCoordinates import art.arcane.volmlib.util.documentation.ChunkCoordinates
import art.arcane.iris.util.mantle.Mantle import art.arcane.iris.util.mantle.Mantle
import art.arcane.volmlib.util.mantle.flag.MantleFlag import art.arcane.volmlib.util.mantle.flag.MantleFlag
@@ -11,7 +14,10 @@ import art.arcane.iris.util.parallel.MultiBurst
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import org.bukkit.block.data.BlockData
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
import kotlin.math.min
interface MatterGenerator { interface MatterGenerator {
val engine: Engine val engine: Engine
@@ -24,34 +30,110 @@ interface MatterGenerator {
fun generateMatter(x: Int, z: Int, multicore: Boolean, context: ChunkContext) { fun generateMatter(x: Int, z: Int, multicore: Boolean, context: ChunkContext) {
if (!engine.dimension.isUseMantle) return if (!engine.dimension.isUseMantle) return
val multicore = multicore || IrisSettings.get().generator.isUseMulticoreMantle val multicore = multicore || IrisSettings.get().generator.isUseMulticoreMantle
val threadName = Thread.currentThread().name
val regenThread = threadName.startsWith("Iris-Regen-")
val traceRegen = regenThread && IrisSettings.get().general.isDebug
val forceRegen = regenThread
val regenPassKey = if (forceRegen) resolveRegenPassKey(threadName) else null
val optimizedRegen = forceRegen && !IrisSettings.get().general.isDebug && regenPassKey != null
val writeRadius = if (optimizedRegen) min(radius, realRadius) else radius
val clearedChunks = if (optimizedRegen) getRegenPassSet(regenClearedChunksByPass, regenPassKey!!) else HashSet<Long>()
val plannedChunks = if (optimizedRegen) getRegenPassSet(regenPlannedChunksByPass, regenPassKey!!) else null
mantle.write(engine.mantle, x, z, radius, multicore).use { writer -> if (optimizedRegen) {
touchRegenPass(regenPassKey!!)
}
if (traceRegen) {
Iris.info("Regen matter start: center=$x,$z radius=$radius realRadius=$realRadius writeRadius=$writeRadius multicore=$multicore components=${components.size} optimized=$optimizedRegen passKey=${regenPassKey ?: "none"} thread=$threadName")
}
mantle.write(engine.mantle, x, z, writeRadius, multicore).use { writer ->
for (pair in components) { for (pair in components) {
val rawPassRadius = pair.b
val passRadius = if (optimizedRegen) min(rawPassRadius, writeRadius) else rawPassRadius
val passFlags = pair.a.joinToString(",") { it.flag.toString() }
val passFlagKey = if (optimizedRegen) "$regenPassKey|$passFlags" else null
val generatedChunks = if (passFlagKey != null) getRegenPassSet(regenGeneratedChunksByPass, passFlagKey) else null
var visitedChunks = 0
var clearedCount = 0
var plannedSkipped = 0
var componentSkipped = 0
var componentForcedReset = 0
var launchedLayers = 0
var dedupSkipped = 0
if (passFlagKey != null) {
touchRegenPass(passFlagKey)
}
if (traceRegen) {
Iris.info("Regen matter pass start: center=$x,$z passRadius=$passRadius rawPassRadius=$rawPassRadius flags=[$passFlags]")
}
runBlocking { runBlocking {
radius(x, z, pair.b) { x, z -> radius(x, z, passRadius) { passX, passZ ->
val mc = writer.acquireChunk(x, z) visitedChunks++
if (mc.isFlagged(MantleFlag.PLANNED)) val passKey = chunkKey(passX, passZ)
if (generatedChunks != null && !generatedChunks.add(passKey)) {
dedupSkipped++
return@radius return@radius
}
val mc = writer.acquireChunk(passX, passZ)
if (forceRegen) {
if (clearedChunks.add(passKey)) {
mc.deleteSlices(BlockData::class.java)
mc.deleteSlices(String::class.java)
mc.deleteSlices(TileWrapper::class.java)
mc.flag(MantleFlag.PLANNED, false)
clearedCount++
}
}
if (!forceRegen && mc.isFlagged(MantleFlag.PLANNED)) {
plannedSkipped++
return@radius
}
for (c in pair.a) { for (c in pair.a) {
if (mc.isFlagged(c.flag)) if (!forceRegen && mc.isFlagged(c.flag)) {
componentSkipped++
continue continue
}
if (forceRegen && mc.isFlagged(c.flag)) {
mc.flag(c.flag, false)
componentForcedReset++
}
launchedLayers++
launch(multicore) { launch(multicore) {
mc.raiseFlagSuspend(c.flag) { mc.raiseFlagSuspend(c.flag) {
c.generateLayer(writer, x, z, context) c.generateLayer(writer, passX, passZ, context)
} }
} }
} }
} }
} }
if (traceRegen) {
Iris.info("Regen matter pass done: center=$x,$z passRadius=$passRadius rawPassRadius=$rawPassRadius visited=$visitedChunks cleared=$clearedCount dedupSkipped=$dedupSkipped plannedSkipped=$plannedSkipped componentSkipped=$componentSkipped componentForcedReset=$componentForcedReset launchedLayers=$launchedLayers flags=[$passFlags]")
}
} }
radius(x, z, realRadius) { x, z -> radius(x, z, realRadius) { realX, realZ ->
writer.acquireChunk(x, z) val realKey = chunkKey(realX, realZ)
if (plannedChunks != null && !plannedChunks.add(realKey)) {
return@radius
}
writer.acquireChunk(realX, realZ)
.flag(MantleFlag.PLANNED, true) .flag(MantleFlag.PLANNED, true)
} }
} }
if (traceRegen) {
Iris.info("Regen matter done: center=$x,$z markedRealRadius=$realRadius forceRegen=$forceRegen")
}
} }
private inline fun radius(x: Int, z: Int, radius: Int, crossinline task: (Int, Int) -> Unit) { private inline fun radius(x: Int, z: Int, radius: Int, crossinline task: (Int, Int) -> Unit) {
@@ -64,7 +146,59 @@ interface MatterGenerator {
companion object { companion object {
private val dispatcher = MultiBurst.burst.dispatcher//.limitedParallelism(128, "Mantle") private val dispatcher = MultiBurst.burst.dispatcher//.limitedParallelism(128, "Mantle")
private const val regenPassCacheTtlMs = 600000L
private val regenGeneratedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenClearedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenPlannedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenPassTouchedMs = ConcurrentHashMap<String, Long>()
private fun CoroutineScope.launch(multicore: Boolean, block: suspend CoroutineScope.() -> Unit) = private fun CoroutineScope.launch(multicore: Boolean, block: suspend CoroutineScope.() -> Unit) =
launch(if (multicore) dispatcher else EmptyCoroutineContext, block = block) launch(if (multicore) dispatcher else EmptyCoroutineContext, block = block)
private fun chunkKey(x: Int, z: Int): Long {
return (x.toLong() shl 32) xor (z.toLong() and 0xffffffffL)
}
private fun getRegenPassSet(store: ConcurrentHashMap<String, MutableSet<Long>>, passKey: String): MutableSet<Long> {
return store.computeIfAbsent(passKey) { ConcurrentHashMap.newKeySet<Long>() }
}
private fun resolveRegenPassKey(threadName: String): String? {
val runtimeKey = RegenRuntime.getRunId()
if (!runtimeKey.isNullOrBlank()) {
return runtimeKey
}
if (!threadName.startsWith("Iris-Regen-")) {
return null
}
val suffix = threadName.substring("Iris-Regen-".length)
val lastDash = suffix.lastIndexOf('-')
if (lastDash <= 0) {
return suffix
}
return suffix.substring(0, lastDash)
}
private fun touchRegenPass(passKey: String) {
val now = System.currentTimeMillis()
regenPassTouchedMs[passKey] = now
if (regenPassTouchedMs.size <= 64) {
return
}
val iterator = regenPassTouchedMs.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
if (now - entry.value <= regenPassCacheTtlMs) {
continue
}
val key = entry.key
iterator.remove()
regenGeneratedChunksByPass.remove(key)
regenClearedChunksByPass.remove(key)
regenPlannedChunksByPass.remove(key)
}
}
} }
} }
+19 -1
View File
@@ -15,12 +15,30 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import java.io.File
plugins { plugins {
id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
} }
rootProject.name = "Iris" rootProject.name = "Iris"
val useLocalVolmLib: Boolean = providers.gradleProperty("useLocalVolmLib")
.orElse("true")
.map { value: String -> value.equals("true", ignoreCase = true) }
.get()
val localVolmLibDirectory: File = file("../VolmLib")
if (useLocalVolmLib && localVolmLibDirectory.resolve("settings.gradle.kts").exists()) {
includeBuild(localVolmLibDirectory) {
dependencySubstitution {
substitute(module("com.github.VolmitSoftware:VolmLib")).using(project(":shared"))
substitute(module("com.github.VolmitSoftware.VolmLib:shared")).using(project(":shared"))
substitute(module("com.github.VolmitSoftware.VolmLib:volmlib-shared")).using(project(":shared"))
}
}
}
include(":core", ":core:agent") include(":core", ":core:agent")
include( include(
":nms:v1_21_R7", ":nms:v1_21_R7",
@@ -34,4 +52,4 @@ include(
":nms:v1_20_R3", ":nms:v1_20_R3",
":nms:v1_20_R2", ":nms:v1_20_R2",
":nms:v1_20_R1", ":nms:v1_20_R1",
) )