mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-04-09 01:06:05 +00:00
WIP - Dont compile
This commit is contained in:
@@ -35,6 +35,9 @@ plugins {
|
||||
|
||||
group = "art.arcane"
|
||||
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>()
|
||||
|
||||
@@ -89,6 +92,10 @@ nmsBindings.forEach { (key, value) ->
|
||||
|
||||
dependencies {
|
||||
compileOnly(project(":core"))
|
||||
compileOnly(volmLibCoordinate) {
|
||||
isChanging = true
|
||||
isTransitive = false
|
||||
}
|
||||
compileOnly(rootProject.libs.annotations)
|
||||
compileOnly(rootProject.libs.byteBuddy.core)
|
||||
}
|
||||
@@ -181,8 +188,8 @@ fun exec(vararg command: Any) {
|
||||
}
|
||||
|
||||
configurations.configureEach {
|
||||
resolutionStrategy.cacheChangingModulesFor(60, "minutes")
|
||||
resolutionStrategy.cacheDynamicVersionsFor(60, "minutes")
|
||||
resolutionStrategy.cacheChangingModulesFor(0, "seconds")
|
||||
resolutionStrategy.cacheDynamicVersionsFor(0, "seconds")
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
||||
@@ -38,6 +38,9 @@ plugins {
|
||||
val apiVersion = "1.19"
|
||||
val main = "art.arcane.iris.Iris"
|
||||
val lib = "art.arcane.iris.util"
|
||||
val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate")
|
||||
.orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT")
|
||||
.get()
|
||||
|
||||
/**
|
||||
* Dependencies.
|
||||
@@ -73,6 +76,10 @@ dependencies {
|
||||
|
||||
// Shaded
|
||||
implementation(slimjarHelper("spigot"))
|
||||
implementation(volmLibCoordinate) {
|
||||
isChanging = true
|
||||
isTransitive = false
|
||||
}
|
||||
|
||||
// Dynamically Loaded
|
||||
slim(libs.paralithic)
|
||||
@@ -234,10 +241,5 @@ rootProject.tasks.named("prepareKotlinBuildScriptModel") {
|
||||
}
|
||||
|
||||
sourceSets.main {
|
||||
java.srcDir("../../VolmLib/shared/src/main/java")
|
||||
java.srcDir(generateTemplates.map { it.outputs })
|
||||
}
|
||||
|
||||
kotlin.sourceSets.named("main") {
|
||||
kotlin.srcDir("../../VolmLib/shared/src/main/kotlin")
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ import art.arcane.iris.util.plugin.VolmitPlugin;
|
||||
import art.arcane.iris.util.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.plugin.chunk.ChunkTickets;
|
||||
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.ShurikenQueue;
|
||||
import lombok.NonNull;
|
||||
@@ -94,6 +95,7 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
private static VolmitSender sender;
|
||||
private static Thread shutdownHook;
|
||||
private static File settingsFile;
|
||||
private static final String PENDING_WORLD_DELETE_FILE = "pending-world-deletes.txt";
|
||||
|
||||
static {
|
||||
try {
|
||||
@@ -470,6 +472,12 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
services.values().forEach(IrisService::onEnable);
|
||||
services.values().forEach(this::registerListener);
|
||||
addShutdownHook();
|
||||
processPendingStartupWorldDeletes();
|
||||
|
||||
if (J.isFolia()) {
|
||||
checkForBukkitWorlds(s -> true);
|
||||
}
|
||||
|
||||
J.s(() -> {
|
||||
J.a(() -> IO.delete(getTemp()));
|
||||
J.a(LazyPregenerator::loadLazyGenerators, 100);
|
||||
@@ -480,7 +488,9 @@ public class Iris extends VolmitPlugin implements Listener {
|
||||
J.a(ServerConfigurator::configure, 20);
|
||||
|
||||
autoStartStudio();
|
||||
checkForBukkitWorlds(s -> true);
|
||||
if (!J.isFolia()) {
|
||||
checkForBukkitWorlds(s -> true);
|
||||
}
|
||||
IrisToolbelt.retainMantleDataForSlice(String.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 + "!");
|
||||
} catch (Throwable e) {
|
||||
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();
|
||||
}
|
||||
});
|
||||
@@ -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() {
|
||||
if (IrisSettings.get().getStudio().isAutoStartDefaultStudio()) {
|
||||
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.object.IrisDimension;
|
||||
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.volmlib.util.collection.KMap;
|
||||
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.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.io.CountingDataInputStream;
|
||||
import art.arcane.volmlib.util.io.IO;
|
||||
import art.arcane.iris.util.mantle.TectonicPlate;
|
||||
import art.arcane.iris.util.math.Position2;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.iris.util.matter.Matter;
|
||||
import art.arcane.iris.util.nbt.mca.MCAFile;
|
||||
import art.arcane.iris.util.nbt.mca.MCAUtil;
|
||||
import art.arcane.iris.util.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.plugin.VolmitSender;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import art.arcane.iris.util.scheduling.jobs.Job;
|
||||
import lombok.SneakyThrows;
|
||||
import net.jpountz.lz4.LZ4BlockInputStream;
|
||||
@@ -72,38 +75,41 @@ import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.*;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
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 {
|
||||
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 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() {
|
||||
Iris.service(IrisEngineSVC.class)
|
||||
.engineStatus(sender());
|
||||
}
|
||||
|
||||
@Decree(description = "Send a test exception to sentry")
|
||||
@Director(description = "Send a test exception to sentry")
|
||||
public void Sentry() {
|
||||
Engine engine = engine();
|
||||
if (engine != null) IrisContext.getOr(engine);
|
||||
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() {
|
||||
sender().sendMessage(C.GREEN + "Opening studio for the \"Overworld\" pack (seed: 1337)");
|
||||
Iris.service(StudioSVC.class).open(sender(), 1337, "overworld");
|
||||
}
|
||||
|
||||
@Decree(description = "Set aura spins")
|
||||
@Director(description = "Set aura spins")
|
||||
public void aura(
|
||||
@Param(description = "The h color value", defaultValue = "-20")
|
||||
int h,
|
||||
@@ -119,7 +125,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
sender().sendMessage("<rainbow>Aura Spins updated to " + h + " " + s + " " + b);
|
||||
}
|
||||
|
||||
@Decree(description = "Bitwise calculations")
|
||||
@Director(description = "Bitwise calculations")
|
||||
public void bitwise(
|
||||
@Param(description = "The first value to run calculations on")
|
||||
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);
|
||||
}
|
||||
|
||||
@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(
|
||||
@Param(description = "The world to update", contextual = true)
|
||||
World world,
|
||||
@@ -180,7 +186,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
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(
|
||||
@Param(aliases = "dimension", description = "The dimension type to create the world with")
|
||||
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 {
|
||||
var base = Iris.instance.getDataFile("dump", "pv." + name + ".ttp.lz4b.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);
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
@Director(description = "Test")
|
||||
public void dumpThreads() {
|
||||
try {
|
||||
File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt");
|
||||
@@ -362,7 +368,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
@Decree(description = "Generate Iris structures for all loaded datapack structures")
|
||||
@Director(description = "Generate Iris structures for all loaded datapack structures")
|
||||
public void generateStructures(
|
||||
@Param(description = "The pack to add the generated structures to", aliases = "pack", defaultValue = "null", customHandler = NullableDimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@@ -485,7 +491,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
data.hotloaded();
|
||||
}
|
||||
|
||||
@Decree(description = "Test")
|
||||
@Director(description = "Test")
|
||||
public void packBenchmark(
|
||||
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
|
||||
IrisDimension dimension,
|
||||
@@ -497,7 +503,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
new IrisPackBenchmarking(dimension, radius, gui);
|
||||
}
|
||||
|
||||
@Decree(description = "Upgrade to another Minecraft version")
|
||||
@Director(description = "Upgrade to another Minecraft version")
|
||||
public void upgrade(
|
||||
@Param(description = "The version to upgrade to", defaultValue = "latest") DataVersion version) {
|
||||
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());
|
||||
}
|
||||
|
||||
@Decree(description = "test")
|
||||
@Director(description = "test")
|
||||
public void mca (
|
||||
@Param(description = "String") String world) {
|
||||
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() {
|
||||
List<World> IrisWorlds = new ArrayList<>();
|
||||
int chunksUnloaded = 0;
|
||||
@@ -545,7 +1094,7 @@ public class CommandDeveloper implements DecreeExecutor {
|
||||
|
||||
}
|
||||
|
||||
@Decree
|
||||
@Director
|
||||
public void objects(@Param(defaultValue = "overworld") IrisDimension dimension) {
|
||||
var loader = dimension.getLoader().getObjectLoader();
|
||||
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");
|
||||
}
|
||||
|
||||
@Decree(description = "Test", aliases = {"ip"})
|
||||
@Director(description = "Test", aliases = {"ip"})
|
||||
public void network() {
|
||||
try {
|
||||
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(
|
||||
@Param(description = "base IrisWorld") World world,
|
||||
@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.engine.object.*;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
|
||||
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 {
|
||||
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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) {
|
||||
if (noStudio()) {
|
||||
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.IrisRegion;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
|
||||
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 {
|
||||
@Decree(description = "Find a biome")
|
||||
@Director(description = "Find a biome")
|
||||
public void biome(
|
||||
@Param(description = "The biome to look for")
|
||||
IrisBiome biome,
|
||||
@@ -48,7 +48,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoBiome(biome, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a region")
|
||||
@Director(description = "Find a region")
|
||||
public void region(
|
||||
@Param(description = "The region to look for")
|
||||
IrisRegion region,
|
||||
@@ -65,7 +65,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoRegion(region, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a structure")
|
||||
@Director(description = "Find a structure")
|
||||
public void structure(
|
||||
@Param(description = "The structure to look for")
|
||||
IrisJigsawStructure structure,
|
||||
@@ -82,7 +82,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoJigsaw(structure, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find a point of interest.")
|
||||
@Director(description = "Find a point of interest.")
|
||||
public void poi(
|
||||
@Param(description = "The type of PoI to look for.")
|
||||
String type,
|
||||
@@ -98,7 +98,7 @@ public class CommandFind implements DecreeExecutor {
|
||||
e.gotoPOI(type, player(), teleport);
|
||||
}
|
||||
|
||||
@Decree(description = "Find an object")
|
||||
@Director(description = "Find an object")
|
||||
public void object(
|
||||
@Param(description = "The object to look for", customHandler = ObjectHandler.class)
|
||||
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.IrisPosition;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
@@ -40,9 +40,9 @@ import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
|
||||
|
||||
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 {
|
||||
@Decree(description = "Edit a jigsaw piece")
|
||||
@Director(description = "Edit a jigsaw piece")
|
||||
public void edit(
|
||||
@Param(description = "The jigsaw piece to edit")
|
||||
IrisJigsawPiece piece
|
||||
@@ -51,7 +51,7 @@ public class CommandJigsaw implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The jigsaw structure to place")
|
||||
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(
|
||||
@Param(description = "The name of the jigsaw piece")
|
||||
String piece,
|
||||
@@ -93,7 +93,7 @@ public class CommandJigsaw implements DecreeExecutor {
|
||||
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() {
|
||||
JigsawEditor editor = JigsawEditor.editors.get(player());
|
||||
|
||||
@@ -106,7 +106,7 @@ public class CommandJigsaw implements DecreeExecutor {
|
||||
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() {
|
||||
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.tools.IrisToolbelt;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.iris.util.math.Position2;
|
||||
import org.bukkit.Bukkit;
|
||||
@@ -36,10 +36,10 @@ import org.bukkit.util.Vector;
|
||||
import java.io.File;
|
||||
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 String worldName;
|
||||
@Decree(description = "Pregenerate a world")
|
||||
@Director(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
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(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
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(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
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.registry.Materials;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.decree.specialhandlers.ObjectHandler;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.iris.util.math.Direction;
|
||||
@@ -49,7 +49,7 @@ import java.io.IOException;
|
||||
import java.text.NumberFormat;
|
||||
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 {
|
||||
|
||||
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(
|
||||
@Param(description = "The object to analyze", customHandler = ObjectHandler.class)
|
||||
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) {
|
||||
IrisObject o = IrisData.loadAnyObject(object, data());
|
||||
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 () {
|
||||
try {
|
||||
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() {
|
||||
player().getInventory().addItem(WandSVC.createDust());
|
||||
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(
|
||||
@Param(description = "The amount to inset by", defaultValue = "1")
|
||||
int amount
|
||||
@@ -267,7 +267,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
|
||||
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(
|
||||
@Param(description = "Whether to use your current position, or where you look", defaultValue = "true")
|
||||
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(
|
||||
@Param(description = "The object to paste", customHandler = ObjectHandler.class)
|
||||
String object,
|
||||
@@ -381,7 +381,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
}
|
||||
}
|
||||
|
||||
@Decree(description = "Save an object")
|
||||
@Director(description = "Save an object")
|
||||
public void save(
|
||||
@Param(description = "The dimension to store the object in", contextual = true)
|
||||
IrisDimension dimension,
|
||||
@@ -416,7 +416,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The amount to shift by", defaultValue = "1")
|
||||
int amount
|
||||
@@ -447,7 +447,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The amount of pastes to undo", defaultValue = "1")
|
||||
int amount
|
||||
@@ -458,7 +458,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
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() {
|
||||
if (!Bukkit.getPluginManager().isPluginEnabled("WorldEdit")) {
|
||||
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!");
|
||||
}
|
||||
|
||||
@Decree(description = "Get an object wand", sync = true)
|
||||
@Director(description = "Get an object wand", sync = true)
|
||||
public void wand() {
|
||||
player().getInventory().addItem(WandSVC.createWand());
|
||||
sender().playSound(Sound.ITEM_ARMOR_EQUIP_NETHERITE, 1f, 1.5f);
|
||||
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() {
|
||||
if (!WandSVC.isHoldingWand(player())) {
|
||||
sender().sendMessage(C.YELLOW + "Hold your wand!");
|
||||
@@ -534,7 +534,7 @@ public class CommandObject implements DecreeExecutor {
|
||||
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() {
|
||||
if (!WandSVC.isHoldingWand(player())) {
|
||||
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.tools.IrisToolbelt;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.iris.util.math.Position2;
|
||||
import org.bukkit.World;
|
||||
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 {
|
||||
@Decree(description = "Pregenerate a world")
|
||||
@Director(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
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() {
|
||||
if (PregeneratorJob.shutdownInstance()) {
|
||||
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() {
|
||||
if (PregeneratorJob.pauseResume()) {
|
||||
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.handlers.DimensionHandler;
|
||||
import art.arcane.iris.util.decree.specialhandlers.NullableDimensionHandler;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import art.arcane.volmlib.util.function.Function2;
|
||||
@@ -79,12 +79,11 @@ import java.time.temporal.ChronoUnit;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
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 {
|
||||
private CommandFind find;
|
||||
private CommandEdit edit;
|
||||
@@ -95,7 +94,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
}
|
||||
|
||||
//TODO fix pack trimming
|
||||
@Decree(description = "Download a project.", aliases = "dl")
|
||||
@Director(description = "Download a project.", aliases = "dl")
|
||||
public void download(
|
||||
@Param(name = "pack", description = "The pack to download", defaultValue = "overworld", aliases = "project")
|
||||
String pack,
|
||||
@@ -109,7 +108,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(defaultValue = "default", description = "The dimension to open a studio for", aliases = "dim", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@@ -126,7 +125,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(defaultValue = "default", description = "The dimension to open VSCode for", aliases = "dim", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
@@ -135,7 +134,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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() {
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No open studio projects.");
|
||||
@@ -146,7 +145,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The name of this new Iris Project.")
|
||||
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(
|
||||
@Param(defaultValue = "default", description = "The dimension get the version of", aliases = "dim", contextual = true, customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
@@ -171,7 +170,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(name = "radius", description = "The radius of nearby cunks", defaultValue = "5")
|
||||
int radius
|
||||
@@ -183,46 +182,60 @@ public class CommandStudio implements DecreeExecutor {
|
||||
|
||||
VolmitSender sender = sender();
|
||||
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);
|
||||
Engine engine = plat.getEngine();
|
||||
DecreeContext.touch(sender);
|
||||
IrisToolbelt.beginWorldMaintenance(world, "studio-regen");
|
||||
try (SyncExecutor executor = new SyncExecutor(20);
|
||||
var service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors())
|
||||
var service = Executors.newFixedThreadPool(threadCount)
|
||||
) {
|
||||
int x = loc.getBlockX() >> 4;
|
||||
int z = loc.getBlockZ() >> 4;
|
||||
|
||||
int rad = engine.getMantle().getRadius();
|
||||
var mantle = engine.getMantle().getMantle();
|
||||
int rad = 0;
|
||||
var chunkMap = new KMap<Position2, MantleChunk>();
|
||||
ParallelRadiusJob prep = new ParallelRadiusJob(Integer.MAX_VALUE, 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;
|
||||
boolean foliaFastRegen = J.isFolia();
|
||||
if (foliaFastRegen) {
|
||||
sender.sendMessage(C.YELLOW + "Folia safe default: using 1 regen worker in studio.");
|
||||
}
|
||||
if (!foliaFastRegen) {
|
||||
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
|
||||
public String getName() {
|
||||
return "Preparing Mantle";
|
||||
}
|
||||
}.retarget(radius + rad, 0, 0);
|
||||
CountDownLatch pLatch = new CountDownLatch(1);
|
||||
prep.execute(sender(), pLatch::countDown);
|
||||
pLatch.await();
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Preparing Mantle";
|
||||
}
|
||||
}.retarget(radius + rad, 0, 0);
|
||||
sender.sendMessage(C.YELLOW + "Preparing mantle data for studio regen...");
|
||||
prep.execute();
|
||||
} 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
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -231,28 +244,38 @@ public class CommandStudio implements DecreeExecutor {
|
||||
return "Regenerating";
|
||||
}
|
||||
}.retarget(radius, x, z);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
job.execute(sender(), latch::countDown);
|
||||
latch.await();
|
||||
job.execute();
|
||||
|
||||
chunkMap.forEach((pos, chunk) ->
|
||||
mantle.getChunk(pos.getX(), pos.getZ()).copyFrom(chunk));
|
||||
if (!foliaFastRegen) {
|
||||
var mantle = engine.getMantle().getMantle();
|
||||
chunkMap.forEach((pos, chunk) ->
|
||||
mantle.getChunk(pos.getX(), pos.getZ()).copyFrom(chunk));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
sender().sendMessage("Error while regenerating chunks");
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
IrisToolbelt.endWorldMaintenance(world, "studio-regen");
|
||||
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() {
|
||||
Iris.service(ConversionSVC.class).check(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(
|
||||
@Param(description = "The script to run")
|
||||
IrisScript script
|
||||
@@ -260,14 +283,14 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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() {
|
||||
if (noGUI()) return;
|
||||
sender().sendMessage(C.GREEN + "Opening Noise Explorer!");
|
||||
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() {
|
||||
if (!IrisToolbelt.isIrisWorld(world())) {
|
||||
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();
|
||||
}
|
||||
|
||||
@Decree(description = "Preview noise gens (External GUI)", aliases = {"generator", "gen"})
|
||||
@Director(description = "Preview noise gens (External GUI)", aliases = {"generator", "gen"})
|
||||
public void explore(
|
||||
@Param(description = "The generator to explore", contextual = true)
|
||||
IrisGenerator generator,
|
||||
@@ -298,7 +321,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
NoiseExplorerGUI.launch(l, "Custom Generator");
|
||||
}
|
||||
|
||||
@Decree(description = "Hotload a studio", aliases = {"reload", "h"})
|
||||
@Director(description = "Hotload a studio", aliases = {"reload", "h"})
|
||||
public void hotload() {
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No studio world open!");
|
||||
@@ -308,7 +331,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "Fast insertion of items in virtual inventory (may cause performance drop)", defaultValue = "false")
|
||||
boolean fast,
|
||||
@@ -355,7 +378,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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) {
|
||||
var engine = engine();
|
||||
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) {
|
||||
var engine = engine();
|
||||
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(
|
||||
@Param(name = "world", description = "The world to open the generator for", contextual = true)
|
||||
World world
|
||||
@@ -474,7 +497,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(name = "dimension", description = "The dimension pack to compress", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension,
|
||||
@@ -486,7 +509,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The dimension to profile", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
IrisDimension dimension
|
||||
@@ -675,7 +698,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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(
|
||||
@Param(description = "The entity to spawn")
|
||||
IrisEntity entity,
|
||||
@@ -688,7 +711,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
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() {
|
||||
if (!Iris.service(StudioSVC.class).isProjectOpen()) {
|
||||
sender().sendMessage(C.RED + "No studio world is open!");
|
||||
@@ -711,7 +734,7 @@ public class CommandStudio implements DecreeExecutor {
|
||||
).thenRun(() -> player.setGameMode(GameMode.SPECTATOR));
|
||||
}
|
||||
|
||||
@Decree(description = "Update your dimension projects VSCode workspace")
|
||||
@Director(description = "Update your dimension projects VSCode workspace")
|
||||
public void update(
|
||||
@Param(description = "The dimension to update the workspace of", contextual = true, defaultValue = "default", customHandler = DimensionHandler.class)
|
||||
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() {
|
||||
if (!IrisToolbelt.isIrisWorld(player().getWorld())) {
|
||||
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.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
@@ -33,10 +33,10 @@ import org.bukkit.util.Vector;
|
||||
import java.io.File;
|
||||
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 String worldName;
|
||||
@Decree(description = "Pregenerate a world")
|
||||
@Director(description = "Pregenerate a world")
|
||||
public void start(
|
||||
@Param(description = "The radius of the pregen in blocks", aliases = "size")
|
||||
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 {
|
||||
TurboPregenerator turboPregenInstance = TurboPregenerator.getInstance();
|
||||
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(
|
||||
@Param(aliases = "world", description = "The world to pause")
|
||||
World world
|
||||
|
||||
@@ -25,18 +25,18 @@ import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.core.pregenerator.ChunkUpdater;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
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 {
|
||||
private final Object lock = new Object();
|
||||
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(
|
||||
@Param(description = "World to update chunks at", contextual = true)
|
||||
World world
|
||||
@@ -61,7 +61,7 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Synchronized("lock")
|
||||
@Decree(description = "Pause the updater")
|
||||
@Director(description = "Pause the updater")
|
||||
public void pause( ) {
|
||||
if (chunkUpdater == null) {
|
||||
sender().sendMessage(C.GOLD + "You cant pause something that doesnt exist?");
|
||||
@@ -84,7 +84,7 @@ public class CommandUpdater implements DecreeExecutor {
|
||||
}
|
||||
|
||||
@Synchronized("lock")
|
||||
@Decree(description = "Stops the updater")
|
||||
@Director(description = "Stops the updater")
|
||||
public void stop() {
|
||||
if (chunkUpdater == null) {
|
||||
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.util.data.B;
|
||||
import art.arcane.iris.util.decree.DecreeExecutor;
|
||||
import art.arcane.volmlib.util.decree.DecreeOrigin;
|
||||
import art.arcane.volmlib.util.decree.annotations.Decree;
|
||||
import art.arcane.volmlib.util.decree.annotations.Param;
|
||||
import art.arcane.volmlib.util.director.DirectorOrigin;
|
||||
import art.arcane.volmlib.util.director.annotations.Director;
|
||||
import art.arcane.volmlib.util.director.annotations.Param;
|
||||
import art.arcane.iris.util.format.C;
|
||||
import art.arcane.volmlib.util.matter.MatterMarker;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
@@ -42,9 +42,9 @@ import org.bukkit.block.data.BlockData;
|
||||
import java.util.Objects;
|
||||
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 {
|
||||
@Decree(description = "What is in my hand?", origin = DecreeOrigin.PLAYER)
|
||||
@Director(description = "What is in my hand?", origin = DirectorOrigin.PLAYER)
|
||||
public void hand() {
|
||||
try {
|
||||
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() {
|
||||
try {
|
||||
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() {
|
||||
try {
|
||||
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() {
|
||||
BlockData bd;
|
||||
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) {
|
||||
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.volmlib.util.scheduling.ChronoLatch;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import org.bukkit.World;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
@@ -158,6 +159,15 @@ public class PregeneratorJob implements PregenListener {
|
||||
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) {
|
||||
String v = (c.startsWith("#") ? c : "#" + c).trim();
|
||||
try {
|
||||
|
||||
@@ -261,6 +261,10 @@ public class ChunkUpdater {
|
||||
}
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
if (J.isFolia()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
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.PregeneratorMethod;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.mantle.Mantle;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.iris.util.parallel.MultiBurst;
|
||||
@@ -34,19 +33,30 @@ import org.bukkit.World;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
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 Executor executor;
|
||||
private final Semaphore semaphore;
|
||||
private final int threads;
|
||||
private final boolean urgent;
|
||||
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) {
|
||||
if (!PaperLib.isPaper()) {
|
||||
@@ -54,14 +64,29 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
|
||||
this.world = world;
|
||||
this.executor = IrisSettings.get().getPregen().isUseTicketQueue() ? new TicketExecutor() : new ServiceExecutor();
|
||||
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
|
||||
if (J.isFolia()) {
|
||||
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.urgent = IrisSettings.get().getPregen().useHighPriority;
|
||||
this.lastUse = new KMap<>();
|
||||
this.lastUse = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
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 {
|
||||
J.sfut(() -> {
|
||||
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
|
||||
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();
|
||||
increaseWorkerThreads();
|
||||
}
|
||||
@@ -126,10 +214,16 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
public void generateChunk(int x, int z, PregenListener listener) {
|
||||
listener.onChunkGenerating(x, z);
|
||||
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) {
|
||||
Thread.currentThread().interrupt();
|
||||
return;
|
||||
}
|
||||
|
||||
markSubmitted();
|
||||
executor.generate(x, z, listener);
|
||||
}
|
||||
|
||||
@@ -189,6 +283,40 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
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 final ExecutorService service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
|
||||
Executors.newVirtualThreadPerTaskExecutor() :
|
||||
@@ -196,18 +324,27 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
service.submit(() -> {
|
||||
boolean success = false;
|
||||
try {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent).thenAccept((i) -> {
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
if (i == null) return;
|
||||
lastUse.put(i, M.ms());
|
||||
}).get();
|
||||
Chunk i = PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
.orTimeout(CHUNK_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.exceptionally(e -> onChunkFutureFailure(x, z, e))
|
||||
.get();
|
||||
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
if (i == null) {
|
||||
return;
|
||||
}
|
||||
lastUse.put(i, M.ms());
|
||||
success = true;
|
||||
} catch (InterruptedException ignored) {
|
||||
Thread.currentThread().interrupt();
|
||||
} catch (Throwable e) {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
markFinished(success);
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
@@ -223,17 +360,21 @@ public class AsyncPregenMethod implements PregeneratorMethod {
|
||||
@Override
|
||||
public void generate(int x, int z, PregenListener listener) {
|
||||
PaperLib.getChunkAtAsync(world, x, z, true, urgent)
|
||||
.exceptionally(e -> {
|
||||
Iris.reportError(e);
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
})
|
||||
.orTimeout(CHUNK_LOAD_TIMEOUT_SECONDS, TimeUnit.SECONDS)
|
||||
.exceptionally(e -> onChunkFutureFailure(x, z, e))
|
||||
.thenAccept(i -> {
|
||||
semaphore.release();
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
if (i == null) return;
|
||||
lastUse.put(i, M.ms());
|
||||
boolean success = false;
|
||||
try {
|
||||
listener.onChunkGenerated(x, z);
|
||||
listener.onChunkCleaned(x, z);
|
||||
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.tools.IrisToolbelt;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.volmlib.util.collection.KMap;
|
||||
import art.arcane.iris.util.mantle.Mantle;
|
||||
import art.arcane.volmlib.util.math.M;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
@@ -35,6 +34,7 @@ import org.bukkit.World;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
private final World world;
|
||||
@@ -44,7 +44,7 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
public MedievalPregenMethod(World world) {
|
||||
this.world = world;
|
||||
futures = new KList<>();
|
||||
this.lastUse = new KMap<>();
|
||||
this.lastUse = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
private void waitForChunks() {
|
||||
@@ -60,6 +60,11 @@ public class MedievalPregenMethod implements PregeneratorMethod {
|
||||
}
|
||||
|
||||
private void unloadAndSaveAllChunks() {
|
||||
if (J.isFolia()) {
|
||||
lastUse.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
J.sfut(() -> {
|
||||
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.VolmitSender;
|
||||
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.runtime.DirectorExecutionMode;
|
||||
import art.arcane.volmlib.util.director.runtime.DirectorExecutionResult;
|
||||
@@ -94,7 +94,7 @@ public class CommandSVC implements IrisService, CommandExecutor, TabCompleter, D
|
||||
}
|
||||
|
||||
public DirectorRuntimeEngine getDirector() {
|
||||
return directorCache.aquireNastyPrint(() -> DirectorDecreeEngineFactory.create(
|
||||
return directorCache.aquireNastyPrint(() -> DirectorEngineFactory.create(
|
||||
new CommandIris(),
|
||||
null,
|
||||
buildDirectorContexts(),
|
||||
|
||||
@@ -281,6 +281,10 @@ public class IrisEngineSVC implements IrisService {
|
||||
|| engine.getMantle().getMantle().isClosed()
|
||||
|| !engine.getMantle().getMantle().shouldReduce(engine))
|
||||
return;
|
||||
World engineWorld = engine.getWorld().realWorld();
|
||||
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
engine.getMantle().trim(tectonicLimit());
|
||||
@@ -304,6 +308,10 @@ public class IrisEngineSVC implements IrisService {
|
||||
|| engine.getMantle().getMantle().isClosed()
|
||||
|| !engine.getMantle().getMantle().shouldReduce(engine))
|
||||
return;
|
||||
World engineWorld = engine.getWorld().realWorld();
|
||||
if (engineWorld != null && IrisToolbelt.isWorldMaintenanceActive(engineWorld)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
long unloadStart = System.currentTimeMillis();
|
||||
|
||||
@@ -57,7 +57,7 @@ public class StudioSVC implements IrisService {
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
J.s(() -> {
|
||||
J.a(() -> {
|
||||
String pack = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
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.object.IrisDimension;
|
||||
import art.arcane.iris.engine.platform.PlatformChunkGenerator;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import art.arcane.iris.util.plugin.VolmitSender;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.World;
|
||||
@@ -40,6 +41,8 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
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.
|
||||
@@ -48,6 +51,8 @@ import java.util.Map;
|
||||
public class IrisToolbelt {
|
||||
@ApiStatus.Internal
|
||||
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
|
||||
@@ -215,7 +220,8 @@ public class IrisToolbelt {
|
||||
* @return the pregenerator job (already started)
|
||||
*/
|
||||
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();
|
||||
}
|
||||
|
||||
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) {
|
||||
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.scripting.environment.EngineEnvironment;
|
||||
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.framework.*;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
@@ -55,6 +56,7 @@ import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.ToString;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -499,7 +501,11 @@ public class IrisEngine implements Engine {
|
||||
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());
|
||||
generated.incrementAndGet();
|
||||
|
||||
|
||||
@@ -20,9 +20,11 @@ package art.arcane.iris.engine;
|
||||
|
||||
import art.arcane.iris.Iris;
|
||||
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.loader.IrisData;
|
||||
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.framework.Engine;
|
||||
import art.arcane.iris.engine.framework.EngineAssignedWorldManager;
|
||||
@@ -62,6 +64,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -80,6 +83,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
private final ChronoLatch chunkDiscovery;
|
||||
private final KMap<Long, Future<?>> cleanup = new KMap<>();
|
||||
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 int entityCount = 0;
|
||||
private long charge = 0;
|
||||
@@ -190,12 +197,15 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
|
||||
private void discoverChunks() {
|
||||
var mantle = getEngine().getMantle().getMantle();
|
||||
World world = getEngine().getWorld().realWorld();
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPregenActiveForThisWorld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
J.s(() -> {
|
||||
for (Player player : world.getPlayers()) {
|
||||
if (player == null || !player.isOnline()) {
|
||||
@@ -208,7 +218,9 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
int radius = 1;
|
||||
for (int x = -radius; x <= radius; x++) {
|
||||
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() {
|
||||
World world = getEngine().getWorld().realWorld();
|
||||
if (world == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPregenActiveForThisWorld()) {
|
||||
return;
|
||||
}
|
||||
|
||||
J.s(() -> {
|
||||
for (Player player : world.getPlayers()) {
|
||||
if (player == null || !player.isOnline()) {
|
||||
@@ -253,6 +298,10 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
Chunk chunk = world.getChunkAt(chunkX, chunkZ);
|
||||
|
||||
if (IrisSettings.get().getWorld().isPostLoadBlockUpdates()) {
|
||||
if (J.isFolia() && !getMantle().isChunkLoaded(chunkX, chunkZ)) {
|
||||
warmupMantleChunkAsync(chunkX, chunkZ);
|
||||
return;
|
||||
}
|
||||
getEngine().updateChunk(chunk);
|
||||
}
|
||||
|
||||
@@ -260,7 +309,12 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
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);
|
||||
J.runRegion(world, chunkX, chunkZ, () -> {
|
||||
if (!world.isChunkLoaded(chunkX, chunkZ)) {
|
||||
@@ -269,23 +323,86 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
spawnIn(world.getChunkAt(chunkX, chunkZ), true);
|
||||
}, delay);
|
||||
|
||||
getSpawnersFromMarkers(chunk).forEach((blockf, spawners) -> {
|
||||
if (spawners.isEmpty()) {
|
||||
Chunk markerChunk = world.getChunkAt(chunkX, chunkZ);
|
||||
forEachMarkerSpawner(markerChunk, (block, spawners) -> {
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
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() {
|
||||
if (getEngine().isClosed()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isPregenActiveForThisWorld()) {
|
||||
J.sleep(500);
|
||||
return false;
|
||||
}
|
||||
|
||||
actuallySpawned = 0;
|
||||
|
||||
if (energy < 100) {
|
||||
@@ -335,6 +452,24 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
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) {
|
||||
if (world == null) {
|
||||
return new Chunk[0];
|
||||
@@ -396,15 +531,14 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
}
|
||||
|
||||
if (IrisSettings.get().getWorld().isMarkerEntitySpawningSystem()) {
|
||||
getSpawnersFromMarkers(c).forEach((blockf, spawners) -> {
|
||||
if (spawners.isEmpty()) {
|
||||
forEachMarkerSpawner(c, (block, spawners) -> {
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
if (s == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
IrisPosition block = new IrisPosition(blockf.getX(), blockf.getY() + getEngine().getWorld().minHeight(), blockf.getZ());
|
||||
IrisSpawner s = new KList<>(spawners).getRandom();
|
||||
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)));
|
||||
});
|
||||
}
|
||||
@@ -530,21 +664,42 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
//INMS.get().injectBiomesFromMantle(e, getMantle());
|
||||
|
||||
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);
|
||||
J.s(() -> {
|
||||
var chunk = getMantle().getChunk(e).use();
|
||||
int minY = getTarget().getWorld().minHeight();
|
||||
|
||||
Runnable applyCustomBlocks = () -> {
|
||||
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 {
|
||||
chunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> {
|
||||
chunk.iterate(Identifier.class, (x, y, z, v) -> {
|
||||
Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), e.getBlock(x & 15, y + minY, z & 15), v);
|
||||
mantleChunk.raiseFlagUnchecked(MantleFlag.CUSTOM, () -> {
|
||||
mantleChunk.iterate(Identifier.class, (x, y, z, v) -> {
|
||||
Iris.service(ExternalDataSVC.class).processUpdate(getEngine(), chunkRef.getBlock(x & 15, y + minY, z & 15), v);
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
chunk.release();
|
||||
mantleChunk.release();
|
||||
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) {
|
||||
Map<IrisPosition, KSet<IrisSpawner>> p = new KMap<>();
|
||||
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) -> {
|
||||
if (t.getTag().equals("cave_floor") || t.getTag().equals("cave_ceiling")) {
|
||||
return;
|
||||
@@ -684,6 +847,128 @@ public class IrisWorldManager extends EngineAssignedWorldManager {
|
||||
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
|
||||
public void onBlockBreak(BlockBreakEvent e) {
|
||||
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.scripting.environment.EngineEnvironment;
|
||||
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.data.cache.Cache;
|
||||
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) {
|
||||
World world = getWorld().realWorld();
|
||||
if (world != null && IrisToolbelt.isWorldMaintenanceActive(world)) {
|
||||
return;
|
||||
}
|
||||
if (IrisSettings.get().getPerformance().isTrimMantleInStudio() || !isStudio()) {
|
||||
getMantle().cleanupChunk(x, z);
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
package art.arcane.iris.engine.framework;
|
||||
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.IrisComplex;
|
||||
import art.arcane.iris.engine.mantle.EngineMantle;
|
||||
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.iris.util.parallel.BurstExecutor;
|
||||
import art.arcane.iris.util.parallel.MultiBurst;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import org.bukkit.block.Biome;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
@@ -69,7 +71,14 @@ public interface EngineMode extends Staged {
|
||||
|
||||
@BlockCoordinates
|
||||
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);
|
||||
|
||||
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.slices.UpdateMatter;
|
||||
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.jetbrains.annotations.UnmodifiableView;
|
||||
|
||||
@@ -80,6 +82,13 @@ public interface EngineMantle extends MatterGenerator {
|
||||
@ChunkCoordinates
|
||||
default KList<IrisPosition> findMarkers(int x, int z, MatterMarker marker) {
|
||||
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) -> {
|
||||
if (marker.equals(mm)) {
|
||||
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 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.engine.data.cache.Cache;
|
||||
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.iris.util.matter.TileWrapper;
|
||||
import art.arcane.iris.util.noise.CNG;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||
import lombok.Data;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
@@ -67,7 +70,12 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable {
|
||||
this.x = x;
|
||||
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);
|
||||
mantle.getChunks(
|
||||
x - radius,
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
package art.arcane.iris.engine.mantle.components;
|
||||
|
||||
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.mantle.ComponentFlag;
|
||||
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.NoiseType;
|
||||
import art.arcane.iris.util.parallel.BurstExecutor;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import org.bukkit.util.BlockVector;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -55,12 +58,32 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
|
||||
@Override
|
||||
public void generateLayer(MantleWriter writer, int x, int z, ChunkContext context) {
|
||||
boolean traceRegen = isRegenTraceThread();
|
||||
RNG rng = applyNoise(x, z, Cache.key(x, z) + seed());
|
||||
int xxx = 8 + (x << 4);
|
||||
int zzz = 8 + (z << 4);
|
||||
IrisRegion region = getComplex().getRegionStream().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) {
|
||||
@@ -69,12 +92,39 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
@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()) {
|
||||
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 {
|
||||
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) {
|
||||
errors++;
|
||||
Iris.reportError(e);
|
||||
Iris.error("Failed to place objects in the following biome: " + biome.getName());
|
||||
Iris.error("Object(s) " + i.getPlace().toString(", ") + " (" + e.getClass().getSimpleName() + ").");
|
||||
@@ -85,10 +135,27 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
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 {
|
||||
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) {
|
||||
errors++;
|
||||
Iris.reportError(e);
|
||||
Iris.error("Failed to place objects in the following region: " + region.getName());
|
||||
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
|
||||
private void placeObject(MantleWriter writer, RNG rng, int x, int z, IrisObjectPlacement objectPlacement) {
|
||||
for (int i = 0; i < objectPlacement.getDensity(rng, x, z, getData()); i++) {
|
||||
private ObjectPlacementResult placeObject(
|
||||
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));
|
||||
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 zz = rng.i(z, z + 15);
|
||||
int id = rng.i(0, Integer.MAX_VALUE);
|
||||
v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||
if (objectPlacement.isDolphinTarget() && objectPlacement.isUnderwater() && B.isStorageChest(data)) {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), MatterStructurePOI.BURIED_TREASURE);
|
||||
try {
|
||||
int result = v.place(xx, -1, zz, writer, objectPlacement, rng, (b, data) -> {
|
||||
writer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id);
|
||||
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
|
||||
@@ -182,6 +338,15 @@ public class MantleObjectComponent extends IrisMantleComponent {
|
||||
}
|
||||
|
||||
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<>();
|
||||
for (String i : objects) {
|
||||
e.queue(() -> {
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
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.IrisDecorantActuator;
|
||||
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.IrisEngineMode;
|
||||
import art.arcane.iris.engine.modifier.*;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import org.bukkit.block.data.BlockData;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
||||
private static final AtomicLong lastMaintenanceBypassLog = new AtomicLong(0L);
|
||||
|
||||
public ModeOverworld(Engine engine) {
|
||||
super(engine);
|
||||
var terrain = new IrisTerrainNormalActuator(getEngine());
|
||||
@@ -40,15 +46,45 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
||||
var perfection = new IrisPerfectionModifier(getEngine());
|
||||
var custom = new IrisCustomModifier(getEngine());
|
||||
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 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 sDeposit = (x, z, k, p, m, c) -> deposit.modify(x, z, k, m, c);
|
||||
EngineStage sPost = (x, z, k, p, m, c) -> post.modify(x, z, k, m, c);
|
||||
EngineStage sInsertMatter = (x, z, K, p, m, c) -> getMantle().insertMatter(x >> 4, z >> 4, BlockData.class, K, m);
|
||||
EngineStage sCave = (x, z, k, p, m, c) -> {
|
||||
if (shouldBypassMantleStages(getEngine())) {
|
||||
return;
|
||||
}
|
||||
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 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(
|
||||
sGenMatter,
|
||||
@@ -66,4 +102,21 @@ public class ModeOverworld extends IrisEngineMode implements EngineMode {
|
||||
registerStage(sPerfection);
|
||||
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.matter.MatterMarker;
|
||||
import art.arcane.iris.util.matter.slices.MarkerMatter;
|
||||
import art.arcane.iris.util.scheduling.J;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
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);
|
||||
Location l = switch (getReferenceSpawner().getGroup()) {
|
||||
case NORMAL -> new Location(c.getWorld(), x, hf + 1, z);
|
||||
case CAVE -> gen.getMantle().findMarkers(c.getX(), c.getZ(), MarkerMatter.CAVE_FLOOR)
|
||||
.convert((i) -> i.toLocation(c.getWorld()).add(0, 1, 0)).getRandom(rng);
|
||||
case CAVE -> {
|
||||
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);
|
||||
};
|
||||
|
||||
@@ -113,7 +120,11 @@ public class IrisEntitySpawn implements IRare {
|
||||
if (spawns > 0) {
|
||||
|
||||
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++) {
|
||||
|
||||
@@ -65,6 +65,7 @@ import java.util.Random;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@@ -198,74 +199,186 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
|
||||
|
||||
@Override
|
||||
public void injectChunkReplacement(World world, int x, int z, Executor syncExecutor) {
|
||||
boolean acquired = false;
|
||||
String phase = "start";
|
||||
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();
|
||||
TerrainChunk tc = TerrainChunk.createUnsafe(world, st);
|
||||
this.world.bind(world);
|
||||
getEngine().generate(x << 4, z << 4, tc, IrisSettings.get().getGenerator().useMulticore);
|
||||
|
||||
Chunk c = PaperLib.getChunkAtAsync(world, x, z)
|
||||
.thenApply(d -> {
|
||||
Iris.tickets.addTicket(d);
|
||||
phase = "engine-generate";
|
||||
long generateStart = System.currentTimeMillis();
|
||||
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()) {
|
||||
if (ee instanceof Player) {
|
||||
continue;
|
||||
}
|
||||
Iris.warn("Chunk replacement still generating at " + x + "," + z
|
||||
+ " for " + (System.currentTimeMillis() - generationWatchdogStart.get()) + "ms"
|
||||
+ " 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();
|
||||
}
|
||||
|
||||
return d;
|
||||
}).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) {
|
||||
if (J.isFolia()) {
|
||||
phase = "folia-run-region";
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Throwable[] failure = new Throwable[1];
|
||||
long regionScheduleStart = System.currentTimeMillis();
|
||||
if (!J.runRegion(world, x, z, () -> {
|
||||
try {
|
||||
phaseUnsafeSet("folia-region-run", x, z);
|
||||
Chunk c = world.getChunkAt(x, z);
|
||||
Iris.tickets.addTicket(c);
|
||||
try {
|
||||
for (Entity ee : c.getEntities()) {
|
||||
if (ee instanceof Player) {
|
||||
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);
|
||||
|
||||
loadLock.release();
|
||||
} catch (Throwable e) {
|
||||
loadLock.release();
|
||||
Iris.error("======================================");
|
||||
Iris.error("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z);
|
||||
e.printStackTrace();
|
||||
Iris.reportErrorChunk(x, z, e, "CHUNK");
|
||||
Iris.error("======================================");
|
||||
|
||||
ChunkData d = Bukkit.createChunkData(world);
|
||||
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int j = 0; j < 16; j++) {
|
||||
d.setBlock(i, 0, j, Material.RED_GLAZED_TERRACOTTA.createBlockData());
|
||||
}
|
||||
throw new IllegalStateException("Chunk replacement failed at phase=" + phase + " chunk=" + x + "," + z, e);
|
||||
} finally {
|
||||
if (acquired) {
|
||||
loadLock.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void phaseUnsafeSet(String phase, int x, int z) {
|
||||
Iris.verbose("Chunk replacement phase=" + phase + " chunk=" + x + "," + z);
|
||||
}
|
||||
|
||||
private Engine getEngine(WorldInfo world) {
|
||||
if (setup.get()) {
|
||||
return getEngine();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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;
|
||||
|
||||
public class DecreeContext {
|
||||
private static final DecreeContextBase<VolmitSender> context = new DecreeContextBase<>();
|
||||
private static final DirectorContextBase<VolmitSender> context = new DirectorContextBase<>();
|
||||
|
||||
public static VolmitSender get() {
|
||||
return context.get();
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
package art.arcane.iris.util.decree;
|
||||
|
||||
import art.arcane.volmlib.util.decree.context.DecreeContextHandlers;
|
||||
import art.arcane.volmlib.util.decree.context.DecreeContextHandlerType;
|
||||
import art.arcane.volmlib.util.director.context.DirectorContextHandlers;
|
||||
import art.arcane.volmlib.util.director.context.DirectorContextHandlerType;
|
||||
import art.arcane.iris.Iris;
|
||||
import art.arcane.iris.util.plugin.VolmitSender;
|
||||
|
||||
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();
|
||||
|
||||
static Map<Class<?>, DecreeContextHandler<?>> buildContextHandlers() {
|
||||
return DecreeContextHandlers.buildOrEmpty(
|
||||
return DirectorContextHandlers.buildOrEmpty(
|
||||
Iris.initialize("art.arcane.iris.util.decree.context"),
|
||||
DecreeContextHandler.class,
|
||||
h -> ((DecreeContextHandler<?>) h).getType(),
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
|
||||
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.tools.IrisToolbelt;
|
||||
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 org.bukkit.entity.Player;
|
||||
|
||||
public interface DecreeExecutor extends DecreeExecutorBase {
|
||||
public interface DecreeExecutor extends DirectorExecutorBase {
|
||||
default VolmitSender sender() {
|
||||
return DecreeContext.get();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
package art.arcane.iris.util.decree;
|
||||
|
||||
public interface DecreeParameterHandler<T>
|
||||
extends DecreeExecutor, art.arcane.volmlib.util.decree.DecreeParameterHandler<T> {
|
||||
public interface DirectorParameterHandler<T>
|
||||
extends DecreeExecutor, art.arcane.volmlib.util.director.DirectorParameterHandler<T> {
|
||||
}
|
||||
|
||||
@@ -18,11 +18,11 @@
|
||||
|
||||
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.volmlib.util.collection.KList;
|
||||
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() {
|
||||
}
|
||||
@@ -31,15 +31,15 @@ public final class DecreeSystem {
|
||||
* Get the handler for the specified type
|
||||
*
|
||||
* @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) {
|
||||
DecreeParameterHandler<?> handler = DecreeSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t));
|
||||
public static DirectorParameterHandler<?> getHandler(Class<?> type) {
|
||||
DirectorParameterHandler<?> handler = DirectorSystemSupport.getHandler(handlers, type, (h, t) -> h.supports(t));
|
||||
if (handler != null) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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.plugin.VolmitSender;
|
||||
import org.bukkit.World;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package art.arcane.iris.util.decree.handlers;
|
||||
|
||||
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.volmlib.util.decree.handlers.base.BlockVectorHandlerBase;
|
||||
import art.arcane.volmlib.util.director.handlers.base.BlockVectorHandlerBase;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -11,7 +11,7 @@ import org.bukkit.util.BlockVector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BlockVectorHandler extends BlockVectorHandlerBase implements DecreeParameterHandler<BlockVector> {
|
||||
public class BlockVectorHandler extends BlockVectorHandlerBase implements DirectorParameterHandler<BlockVector> {
|
||||
@Override
|
||||
protected boolean isSenderPlayer() {
|
||||
return DecreeContext.get().isPlayer();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package art.arcane.iris.util.decree.handlers;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.BooleanHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.BooleanHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.ByteHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.ByteHandlerBase;
|
||||
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.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
|
||||
public class DataVersionHandler implements DecreeParameterHandler<DataVersion> {
|
||||
public class DataVersionHandler implements DirectorParameterHandler<DataVersion> {
|
||||
@Override
|
||||
public KList<DataVersion> getPossibilities() {
|
||||
return new KList<>(DataVersion.values()).qdel(DataVersion.UNSUPPORTED);
|
||||
@@ -17,7 +17,7 @@ public class DataVersionHandler implements DecreeParameterHandler<DataVersion> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public DataVersion parse(String in, boolean force) throws DecreeParsingException {
|
||||
public DataVersion parse(String in, boolean force) throws DirectorParsingException {
|
||||
if (in.equalsIgnoreCase("latest")) {
|
||||
return DataVersion.getLatest();
|
||||
}
|
||||
@@ -26,7 +26,7 @@ public class DataVersionHandler implements DecreeParameterHandler<DataVersion> {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
throw new DecreeParsingException("Unable to parse data version \"" + in + "\"");
|
||||
throw new DirectorParsingException("Unable to parse data version \"" + in + "\"");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,7 +22,7 @@ import art.arcane.iris.core.IrisSettings;
|
||||
import art.arcane.iris.core.tools.IrisToolbelt;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
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 java.util.Locale;
|
||||
@@ -33,7 +33,7 @@ public class DimensionHandler extends RegistrantHandler<IrisDimension> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisDimension parse(String in, boolean force) throws DecreeParsingException {
|
||||
public IrisDimension parse(String in, boolean force) throws DirectorParsingException {
|
||||
String key = in.trim();
|
||||
if (key.equalsIgnoreCase("default")) {
|
||||
key = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
@@ -41,7 +41,7 @@ public class DimensionHandler extends RegistrantHandler<IrisDimension> {
|
||||
|
||||
try {
|
||||
return super.parse(key, force);
|
||||
} catch (DecreeParsingException ignored) {
|
||||
} catch (DirectorParsingException ignored) {
|
||||
String normalized = key.toLowerCase(Locale.ROOT);
|
||||
IrisDimension resolved = IrisToolbelt.getDimension(normalized);
|
||||
if (resolved != null) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package art.arcane.iris.util.decree.handlers;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.DoubleHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.DoubleHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.FloatHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.FloatHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.IntegerHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.IntegerHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.LongHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.LongHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.BukkitPlayerHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.BukkitPlayerHandlerBase;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.ShortHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.ShortHandlerBase;
|
||||
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;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.StringHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.StringHandlerBase;
|
||||
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;
|
||||
|
||||
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.volmlib.util.decree.handlers.base.VectorHandlerBase;
|
||||
import art.arcane.volmlib.util.director.handlers.base.VectorHandlerBase;
|
||||
import art.arcane.volmlib.util.format.Form;
|
||||
import org.bukkit.FluidCollisionMode;
|
||||
import org.bukkit.entity.Player;
|
||||
@@ -11,7 +11,7 @@ import org.bukkit.util.Vector;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class VectorHandler extends VectorHandlerBase implements DecreeParameterHandler<Vector> {
|
||||
public class VectorHandler extends VectorHandlerBase implements DirectorParameterHandler<Vector> {
|
||||
@Override
|
||||
protected boolean isSenderPlayer() {
|
||||
return DecreeContext.get().isPlayer();
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package art.arcane.iris.util.decree.handlers;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.WorldHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.WorldHandlerBase;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
import org.bukkit.World;
|
||||
|
||||
public class WorldHandler extends WorldHandlerBase implements DecreeParameterHandler<World> {
|
||||
public class WorldHandler extends WorldHandlerBase implements DirectorParameterHandler<World> {
|
||||
@Override
|
||||
protected String excludedPrefix() {
|
||||
return "iris/";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package art.arcane.iris.util.decree.specialhandlers;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.DummyHandlerBase;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.DummyHandlerBase;
|
||||
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.engine.object.IrisDimension;
|
||||
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;
|
||||
|
||||
@@ -32,7 +32,7 @@ public class NullableDimensionHandler extends RegistrantHandler<IrisDimension> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public IrisDimension parse(String in, boolean force) throws DecreeParsingException {
|
||||
public IrisDimension parse(String in, boolean force) throws DirectorParsingException {
|
||||
String key = in.trim();
|
||||
if (key.equalsIgnoreCase("default")) {
|
||||
key = IrisSettings.get().getGenerator().getDefaultWorldType();
|
||||
@@ -40,7 +40,7 @@ public class NullableDimensionHandler extends RegistrantHandler<IrisDimension> {
|
||||
|
||||
try {
|
||||
return super.parse(key, force);
|
||||
} catch (DecreeParsingException ignored) {
|
||||
} catch (DirectorParsingException ignored) {
|
||||
String normalized = key.toLowerCase(Locale.ROOT);
|
||||
IrisDimension resolved = IrisToolbelt.getDimension(normalized);
|
||||
if (resolved != null) {
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
package art.arcane.iris.util.decree.specialhandlers;
|
||||
|
||||
import art.arcane.volmlib.util.decree.handlers.base.NullablePlayerHandlerBase;
|
||||
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.director.handlers.base.NullablePlayerHandlerBase;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
import art.arcane.iris.util.decree.handlers.PlayerHandler;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NullablePlayerHandler extends PlayerHandler implements DecreeParameterHandler<Player> {
|
||||
public class NullablePlayerHandler extends PlayerHandler implements DirectorParameterHandler<Player> {
|
||||
@Override
|
||||
public Player parse(String in, boolean force) throws DecreeParsingException {
|
||||
public Player parse(String in, boolean force) throws DirectorParsingException {
|
||||
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.core.loader.IrisData;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ObjectHandler implements DecreeParameterHandler<String> {
|
||||
public class ObjectHandler implements DirectorParameterHandler<String> {
|
||||
@Override
|
||||
public KList<String> getPossibilities() {
|
||||
KList<String> p = new KList<>();
|
||||
@@ -53,16 +53,16 @@ public class ObjectHandler implements DecreeParameterHandler<String> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String parse(String in, boolean force) throws DecreeParsingException {
|
||||
public String parse(String in, boolean force) throws DirectorParsingException {
|
||||
KList<String> options = getPossibilities(in);
|
||||
|
||||
if (options.isEmpty()) {
|
||||
throw new DecreeParsingException("Unable to find Object \"" + in + "\"");
|
||||
throw new DirectorParsingException("Unable to find Object \"" + in + "\"");
|
||||
}
|
||||
try {
|
||||
return options.stream().filter((i) -> toString(i).equalsIgnoreCase(in)).collect(Collectors.toList()).get(0);
|
||||
} 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.IrisRegistrant;
|
||||
import art.arcane.volmlib.util.collection.KList;
|
||||
import art.arcane.iris.util.decree.DecreeParameterHandler;
|
||||
import art.arcane.volmlib.util.decree.exceptions.DecreeParsingException;
|
||||
import art.arcane.iris.util.decree.DirectorParameterHandler;
|
||||
import art.arcane.volmlib.util.director.exceptions.DirectorParsingException;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.HashSet;
|
||||
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 String name;
|
||||
private final boolean nullable;
|
||||
@@ -54,19 +54,19 @@ public abstract class RegistrantHandler<T extends IrisRegistrant> implements Dec
|
||||
}
|
||||
|
||||
@Override
|
||||
public T parse(String in, boolean force) throws DecreeParsingException {
|
||||
public T parse(String in, boolean force) throws DirectorParsingException {
|
||||
if (in.equals("null") && nullable) {
|
||||
return null;
|
||||
}
|
||||
KList<T> options = getPossibilities(in);
|
||||
if (options.isEmpty()) {
|
||||
throw new DecreeParsingException("Unable to find " + name + " \"" + in + "\"");
|
||||
throw new DirectorParsingException("Unable to find " + name + " \"" + in + "\"");
|
||||
}
|
||||
|
||||
return options.stream()
|
||||
.filter((i) -> toString(i).equalsIgnoreCase(in))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new DecreeParsingException("Unable to filter which " + name + " \"" + in + "\""));
|
||||
.orElseThrow(() -> new DirectorParsingException("Unable to filter which " + name + " \"" + in + "\""));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -54,9 +54,16 @@ public class IOWorker {
|
||||
|
||||
public IOWorker(File root, int worldHeight) {
|
||||
this.worldHeight = worldHeight;
|
||||
this.support = new IOWorkerSupport(root, 128, (name, millis) ->
|
||||
Iris.debug("Acquired Channel for " + C.DARK_GREEN + name + C.RED + " in " + Form.duration(millis, 2))
|
||||
);
|
||||
this.support = new IOWorkerSupport(root, 128, (name, millis) -> {
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.impl.SimpleExternalDependenciesResolverOptionsParser
|
||||
import kotlin.script.experimental.jvm.JvmDependency
|
||||
import kotlin.script.experimental.jvm.JvmDependencyFromClassLoader
|
||||
import kotlin.script.experimental.jvm.updateClasspath
|
||||
import kotlin.script.experimental.jvm.util.classpathFromClassloader
|
||||
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) {
|
||||
val dependency = JvmDependencyFromClassLoader { this }
|
||||
val dependency get() = JvmDependency(classpath)
|
||||
|
||||
fun addFiles(files: List<File>) {
|
||||
files.forEach { addURL(it.toURI().toURL()) }
|
||||
@@ -205,7 +204,10 @@ internal fun ScriptCompilationConfiguration.Builder.configure() {
|
||||
beforeParsing { context -> try {
|
||||
context.compilationConfiguration.with {
|
||||
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()
|
||||
} catch (e: Throwable) {
|
||||
@@ -214,4 +216,4 @@ internal fun ScriptCompilationConfiguration.Builder.configure() {
|
||||
|
||||
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
package art.arcane.iris.engine.mantle
|
||||
|
||||
import art.arcane.iris.Iris
|
||||
import art.arcane.iris.core.IrisSettings
|
||||
import art.arcane.iris.core.nms.container.Pair
|
||||
import art.arcane.iris.engine.framework.Engine
|
||||
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.iris.util.mantle.Mantle
|
||||
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.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.bukkit.block.data.BlockData
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.coroutines.EmptyCoroutineContext
|
||||
import kotlin.math.min
|
||||
|
||||
interface MatterGenerator {
|
||||
val engine: Engine
|
||||
@@ -24,34 +30,110 @@ interface MatterGenerator {
|
||||
fun generateMatter(x: Int, z: Int, multicore: Boolean, context: ChunkContext) {
|
||||
if (!engine.dimension.isUseMantle) return
|
||||
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) {
|
||||
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 {
|
||||
radius(x, z, pair.b) { x, z ->
|
||||
val mc = writer.acquireChunk(x, z)
|
||||
if (mc.isFlagged(MantleFlag.PLANNED))
|
||||
radius(x, z, passRadius) { passX, passZ ->
|
||||
visitedChunks++
|
||||
val passKey = chunkKey(passX, passZ)
|
||||
if (generatedChunks != null && !generatedChunks.add(passKey)) {
|
||||
dedupSkipped++
|
||||
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) {
|
||||
if (mc.isFlagged(c.flag))
|
||||
if (!forceRegen && mc.isFlagged(c.flag)) {
|
||||
componentSkipped++
|
||||
continue
|
||||
}
|
||||
if (forceRegen && mc.isFlagged(c.flag)) {
|
||||
mc.flag(c.flag, false)
|
||||
componentForcedReset++
|
||||
}
|
||||
|
||||
launchedLayers++
|
||||
|
||||
launch(multicore) {
|
||||
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 ->
|
||||
writer.acquireChunk(x, z)
|
||||
radius(x, z, realRadius) { realX, realZ ->
|
||||
val realKey = chunkKey(realX, realZ)
|
||||
if (plannedChunks != null && !plannedChunks.add(realKey)) {
|
||||
return@radius
|
||||
}
|
||||
writer.acquireChunk(realX, realZ)
|
||||
.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) {
|
||||
@@ -64,7 +146,59 @@ interface MatterGenerator {
|
||||
|
||||
companion object {
|
||||
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) =
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,30 @@
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import java.io.File
|
||||
|
||||
plugins {
|
||||
id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
|
||||
}
|
||||
|
||||
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(
|
||||
":nms:v1_21_R7",
|
||||
@@ -34,4 +52,4 @@ include(
|
||||
":nms:v1_20_R3",
|
||||
":nms:v1_20_R2",
|
||||
":nms:v1_20_R1",
|
||||
)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user