Compare commits

...

24 Commits

Author SHA1 Message Date
Aidan Aeternum f93995e152 v+ 2025-04-21 20:27:43 -04:00
Aidan Aeternum 7a5a2e5909 Merge pull request #1191 from VolmitSoftware/dev
3.6.7
2025-04-21 20:27:18 -04:00
Pixel f1c72974fd Cleanup (#1186)
* Remove server benchmark command

* Moved lazy pregen to dev
2025-04-15 15:53:50 +02:00
Julian Krings 26e2e20840 Feat/pregen cache (#1190)
Add cache to pregen to skip already generated chunks faster
2025-04-14 21:26:39 +02:00
Julian Krings c00dcf205b fix deadlock when closing pregen method while using modified concurrency 2025-04-14 21:25:31 +02:00
Julian Krings a10a784c3b add max concurrency setting for pregen 2025-04-14 21:25:31 +02:00
Julian Krings f99cc61042 add setting to use a virtual thread executor instead of MultiBurst for async pregen 2025-04-14 21:25:31 +02:00
Julian Krings d2a1e5cc1e fix duplicate & redundant purpur recommendations 2025-04-14 21:23:52 +02:00
Julian Krings 3e2c0fa025 Decrease MSPT impact of throwing ender eyes in Iris worlds 2025-04-14 21:23:37 +02:00
Aidan Aeternum ab4770400e v+ 2025-04-10 04:49:32 -04:00
Aidan Aeternum 1a810d5d62 Merge pull request #1184 from VolmitSoftware/dev
3.6.6
2025-04-10 04:49:02 -04:00
Julian Krings 536d20bca7 remove context injection on world init 2025-04-09 12:16:03 +02:00
Julian Krings 9a691ac5b4 setup runServer for all supported versions 2025-04-09 10:57:35 +02:00
Julian Krings 71078a20a9 Don't inject world load context for main worlds to prevent incompatibilities 2025-04-08 21:15:19 +02:00
Julian Krings 74e2576ca2 Prevent saving the iris level stems 2025-04-06 16:31:35 +02:00
Julian Krings 407e51378c fix applying x offset to z coords in Spiraler 2025-03-27 15:02:47 +01:00
Julian Krings c468eb1ab1 make pregen use block radius as input 2025-03-27 15:02:47 +01:00
Aidan Aeternum bdb7cc61e5 v+ 2025-03-26 15:41:35 -04:00
Aidan Aeternum e8f9e841c4 Merge pull request #1181 from VolmitSoftware/dev
3.6.5
2025-03-26 15:41:06 -04:00
Julian Krings 1b1b9d97b7 update overworld pack to 31020 2025-03-25 19:15:47 +01:00
Julian Krings 24355064ff add safeguard for missing dimension types to prevent world corruption 2025-03-25 19:14:20 +01:00
Julian Krings 06a45056d9 use flat level source instead of trying to get the levelstems 2025-03-22 12:38:25 +01:00
Aidan Aeternum dfe4894be7 v+ 2025-03-18 16:40:33 -04:00
Aidan Aeternum 8eb2287ec0 Merge pull request #1178 from VolmitSoftware/dev
3.6.4
2025-03-18 16:34:22 -04:00
51 changed files with 1555 additions and 1382 deletions
+27 -6
View File
@@ -1,3 +1,5 @@
import xyz.jpenilla.runpaper.task.RunServer
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2021 Arcane Arts (Volmit Software)
@@ -30,10 +32,11 @@ plugins {
id 'java-library'
id "io.github.goooler.shadow" version "8.1.7"
id "de.undercouch.download" version "5.0.1"
id "xyz.jpenilla.run-paper" version "2.3.1"
}
version '3.6.3-1.20.1-1.21.4'
version '3.6.7-1.20.1-1.21.4'
// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED
// ======================== WINDOWS =============================
@@ -53,6 +56,11 @@ registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins')
registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins')
// ==============================================================
def MIN_HEAP_SIZE = "2G"
def MAX_HEAP_SIZE = "8G"
//Valid values are: none, truecolor, indexed256, indexed16, indexed8
def COLOR = "truecolor"
def NMS_BINDINGS = Map.of(
"v1_21_R3", "1.21.4-R0.1-SNAPSHOT",
"v1_21_R2", "1.21.3-R0.1-SNAPSHOT",
@@ -62,21 +70,34 @@ def NMS_BINDINGS = Map.of(
"v1_20_R2", "1.20.2-R0.1-SNAPSHOT",
"v1_20_R1", "1.20.1-R0.1-SNAPSHOT",
)
def JVM_VERSION = Map.of()
NMS_BINDINGS.each { nms ->
project(":nms:${nms.key}") {
def JVM_VERSION = Map.<String, Integer>of()
NMS_BINDINGS.forEach { key, value ->
project(":nms:$key") {
apply plugin: 'java'
apply plugin: 'com.volmit.nmstools'
nmsTools {
it.jvm = JVM_VERSION.getOrDefault(nms.key, 21)
it.version = nms.value
it.jvm = JVM_VERSION.getOrDefault(key, 21)
it.version = value
}
dependencies {
implementation project(":core")
}
}
tasks.register("runServer-$key", RunServer) {
group("servers")
minecraftVersion(value.split("-")[0])
minHeapSize(MIN_HEAP_SIZE)
maxHeapSize(MAX_HEAP_SIZE)
pluginJars(tasks.shadowJar.archiveFile)
javaLauncher = javaToolchains.launcherFor { it.languageVersion = JavaLanguageVersion.of(JVM_VERSION.getOrDefault(key, 21))}
runDirectory.convention(layout.buildDirectory.dir("run/$key"))
systemProperty("disable.watchdog", "")
systemProperty("net.kyori.ansi.colorLevel", COLOR)
systemProperty("com.mojang.eula.agree", true)
}
}
shadowJar {
+16 -11
View File
@@ -459,15 +459,17 @@ public class Iris extends VolmitPlugin implements Listener {
initialize("com.volmit.iris.core.service").forEach((i) -> services.put((Class<? extends IrisService>) i.getClass(), (IrisService) i));
INMS.get();
IO.delete(new File("iris"));
compat = IrisCompat.configured(getDataFile("compat.json"));
ServerConfigurator.configure();
new IrisContextInjector();
IrisSafeguard.IrisSafeguardSystem();
getSender().setTag(getTag());
compat = IrisCompat.configured(getDataFile("compat.json"));
IrisSafeguard.earlySplash();
linkMultiverseCore = new MultiverseCoreLink();
linkMythicMobs = new MythicMobsLink();
configWatcher = new FileWatcher(getDataFile("settings.json"));
services.values().forEach(IrisService::onEnable);
services.values().forEach(this::registerListener);
registerListener(new IrisContextInjector());
J.s(() -> {
J.a(() -> PaperLib.suggestPaper(this));
J.a(() -> IO.delete(getTemp()));
@@ -622,12 +624,22 @@ public class Iris extends VolmitPlugin implements Listener {
Iris.warn("=");
Iris.warn("============================================");
}
if (!instance.getServer().getVersion().contains("Purpur")) {
passed = false;
try {
Class.forName("io.papermc.paper.configuration.PaperConfigurations");
} catch (ClassNotFoundException e) {
Iris.info(C.RED + "Iris requires paper or above to function properly..");
return false;
}
try {
Class.forName("org.purpurmc.purpur.PurpurConfig");
} catch (ClassNotFoundException e) {
Iris.info("We recommend using Purpur for the best experience with Iris.");
Iris.info("Purpur is a fork of Paper that is optimized for performance and stability.");
Iris.info("Plugins that work on Spigot / Paper work on Purpur.");
Iris.info("You can download it here: https://purpurmc.org");
return false;
}
return passed;
}
@@ -852,13 +864,6 @@ public class Iris extends VolmitPlugin implements Listener {
Iris.info("Server type & version: " + C.RED + Bukkit.getVersion());
} else { Iris.info("Server type & version: " + Bukkit.getVersion()); }
Iris.info("Java: " + getJava());
if (!instance.getServer().getVersion().contains("Purpur")) {
if (instance.getServer().getVersion().contains("Spigot") && instance.getServer().getVersion().contains("Bukkit")) {
Iris.info(C.RED + " Iris requires paper or above to function properly..");
} else {
Iris.info(C.YELLOW + "Purpur is recommended to use with iris.");
}
}
if (getHardware.getProcessMemory() < 5999) {
Iris.warn("6GB+ Ram is recommended");
Iris.warn("Process Memory: " + getHardware.getProcessMemory() + " MB");
@@ -24,7 +24,6 @@ import com.volmit.iris.util.io.IO;
import com.volmit.iris.util.json.JSONException;
import com.volmit.iris.util.json.JSONObject;
import com.volmit.iris.util.plugin.VolmitSender;
import com.volmit.iris.util.scheduling.ChronoLatch;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@@ -45,6 +44,7 @@ public class IrisSettings {
private IrisSettingsStudio studio = new IrisSettingsStudio();
private IrisSettingsPerformance performance = new IrisSettingsPerformance();
private IrisSettingsUpdater updater = new IrisSettingsUpdater();
private IrisSettingsPregen pregen = new IrisSettingsPregen();
public static int getThreadCount(int c) {
return switch (c) {
@@ -130,6 +130,7 @@ public class IrisSettings {
public boolean markerEntitySpawningSystem = true;
public boolean effectSystem = true;
public boolean worldEditWandCUI = true;
public boolean globalPregenCache = true;
}
@Data
@@ -137,6 +138,12 @@ public class IrisSettings {
public int parallelism = -1;
}
@Data
public static class IrisSettingsPregen {
public boolean useVirtualThreads = false;
public int maxConcurrency = 256;
}
@Data
public static class IrisSettingsPerformance {
public boolean trimMantleInStudio = false;
@@ -31,6 +31,7 @@ import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.collection.KSet;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.misc.ServerProperties;
import com.volmit.iris.util.plugin.VolmitSender;
import com.volmit.iris.util.scheduling.J;
import lombok.Data;
@@ -97,6 +98,7 @@ public class ServerConfigurator {
}
KList<File> worlds = new KList<>();
Bukkit.getServer().getWorlds().forEach(w -> worlds.add(new File(w.getWorldFolder(), "datapacks")));
if (worlds.isEmpty()) worlds.add(new File(Bukkit.getWorldContainer(), ServerProperties.LEVEL_NAME + "/datapacks"));
return worlds;
}
@@ -1,134 +0,0 @@
/*
* Iris is a World Generator for Minecraft Bukkit Servers
* Copyright (c) 2022 Arcane Arts (Volmit Software)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.volmit.iris.core.commands;
import com.volmit.iris.Iris;
import com.volmit.iris.core.pregenerator.DeepSearchPregenerator;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.pregenerator.TurboPregenerator;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.util.data.Dimension;
import com.volmit.iris.util.decree.DecreeExecutor;
import com.volmit.iris.util.decree.annotations.Decree;
import com.volmit.iris.util.decree.annotations.Param;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.math.Position2;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.util.Vector;
import java.io.File;
import java.io.IOException;
@Decree(name = "DeepSearch", aliases = "search", description = "Pregenerate your Iris worlds!")
public class CommandDeepSearch implements DecreeExecutor {
public String worldName;
@Decree(description = "DeepSearch a world")
public void start(
@Param(description = "The radius of the pregen in blocks", aliases = "size")
int radius,
@Param(description = "The world to pregen", contextual = true)
World world,
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
Vector center
) {
worldName = world.getName();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File TurboFile = new File(worldDirectory, "DeepSearch.json");
if (TurboFile.exists()) {
if (DeepSearchPregenerator.getInstance() != null) {
sender().sendMessage(C.BLUE + "DeepSearch is already in progress");
Iris.info(C.YELLOW + "DeepSearch is already in progress");
return;
} else {
try {
TurboFile.delete();
} catch (Exception e){
Iris.error("Failed to delete the old instance file of DeepSearch!");
return;
}
}
}
try {
if (sender().isPlayer() && access() == null) {
sender().sendMessage(C.RED + "The engine access for this world is null!");
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
}
DeepSearchPregenerator.DeepSearchJob DeepSearchJob = DeepSearchPregenerator.DeepSearchJob.builder()
.world(world)
.radiusBlocks(radius)
.position(0)
.build();
File SearchGenFile = new File(worldDirectory, "DeepSearch.json");
DeepSearchPregenerator pregenerator = new DeepSearchPregenerator(DeepSearchJob, SearchGenFile);
pregenerator.start();
String msg = C.GREEN + "DeepSearch started in " + C.GOLD + worldName + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
sender().sendMessage(msg);
Iris.info(msg);
} catch (Throwable e) {
sender().sendMessage(C.RED + "Epic fail. See console.");
Iris.reportError(e);
e.printStackTrace();
}
}
@Decree(description = "Stop the active DeepSearch task", aliases = "x")
public void stop(@Param(aliases = "world", description = "The world to pause") World world) throws IOException {
DeepSearchPregenerator DeepSearchInstance = DeepSearchPregenerator.getInstance();
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File turboFile = new File(worldDirectory, "DeepSearch.json");
if (DeepSearchInstance != null) {
DeepSearchInstance.shutdownInstance(world);
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
} else if (turboFile.exists() && turboFile.delete()) {
sender().sendMessage(C.LIGHT_PURPLE + "Closed Turbogen instance for " + world.getName());
} else if (turboFile.exists()) {
Iris.error("Failed to delete the old instance file of Turbo Pregen!");
} else {
sender().sendMessage(C.YELLOW + "No active pregeneration tasks to stop");
}
}
@Decree(description = "Pause / continue the active pregeneration task", aliases = {"t", "resume", "unpause"})
public void pause(
@Param(aliases = "world", description = "The world to pause")
World world
) {
if (TurboPregenerator.getInstance() != null) {
TurboPregenerator.setPausedTurbo(world);
sender().sendMessage(C.GREEN + "Paused/unpaused Turbo Pregen, now: " + (TurboPregenerator.isPausedTurbo(world) ? "Paused" : "Running") + ".");
} else {
File worldDirectory = new File(Bukkit.getWorldContainer(), world.getName());
File TurboFile = new File(worldDirectory, "DeepSearch.json");
if (TurboFile.exists()){
TurboPregenerator.loadTurboGenerator(world.getName());
sender().sendMessage(C.YELLOW + "Started DeepSearch back up!");
} else {
sender().sendMessage(C.YELLOW + "No active DeepSearch tasks to pause/unpause.");
}
}
}
}
@@ -36,6 +36,7 @@ import com.volmit.iris.util.format.Form;
import com.volmit.iris.util.io.CountingDataInputStream;
import com.volmit.iris.util.io.IO;
import com.volmit.iris.util.mantle.TectonicPlate;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.nbt.mca.MCAFile;
import com.volmit.iris.util.nbt.mca.MCAUtil;
import com.volmit.iris.util.parallel.MultiBurst;
@@ -62,6 +63,7 @@ import java.util.zip.GZIPOutputStream;
@Decree(name = "Developer", origin = DecreeOrigin.BOTH, description = "Iris World Manager", aliases = {"dev"})
public class CommandDeveloper implements DecreeExecutor {
private CommandTurboPregen turboPregen;
private CommandLazyPregen lazyPregen;
private CommandUpdater updater;
@Decree(description = "Get Loaded TectonicPlates Count", origin = DecreeOrigin.BOTH, sync = true)
@@ -115,6 +117,42 @@ public class CommandDeveloper implements DecreeExecutor {
Iris.info("-------------------------");
}
@Decree(description = "Test")
public void dumpThreads() {
try {
File fi = Iris.instance.getDataFile("dump", "td-" + new java.sql.Date(M.ms()) + ".txt");
FileOutputStream fos = new FileOutputStream(fi);
Map<Thread, StackTraceElement[]> f = Thread.getAllStackTraces();
PrintWriter pw = new PrintWriter(fos);
pw.println(Thread.activeCount() + "/" + f.size());
var run = Runtime.getRuntime();
pw.println("Memory:");
pw.println("\tMax: " + run.maxMemory());
pw.println("\tTotal: " + run.totalMemory());
pw.println("\tFree: " + run.freeMemory());
pw.println("\tUsed: " + (run.totalMemory() - run.freeMemory()));
for (Thread i : f.keySet()) {
pw.println("========================================");
pw.println("Thread: '" + i.getName() + "' ID: " + i.getId() + " STATUS: " + i.getState().name());
for (StackTraceElement j : f.get(i)) {
pw.println(" @ " + j.toString());
}
pw.println("========================================");
pw.println();
pw.println();
}
pw.close();
Iris.info("DUMPED! See " + fi.getAbsolutePath());
} catch (Throwable e) {
e.printStackTrace();
}
}
@Decree(description = "Test")
public void benchmarkMantle(
@Param(description = "The world to bench", aliases = {"world"})
@@ -140,7 +178,7 @@ public class CommandDeveloper implements DecreeExecutor {
public void packBenchmark(
@Param(description = "The pack to bench", aliases = {"pack"}, defaultValue = "overworld")
IrisDimension dimension,
@Param(description = "Radius in regions", defaultValue = "5")
@Param(description = "Radius in regions", defaultValue = "2048")
int radius,
@Param(description = "Open GUI while benchmarking", defaultValue = "false")
boolean gui
@@ -24,7 +24,6 @@ import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.pregenerator.ChunkUpdater;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.core.tools.IrisBenchmarking;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.engine.object.IrisDimension;
@@ -59,7 +58,6 @@ import java.util.List;
import static com.volmit.iris.Iris.service;
import static com.volmit.iris.core.service.EditSVC.deletingWorld;
import static com.volmit.iris.core.tools.IrisBenchmarking.inProgress;
import static com.volmit.iris.core.safeguard.IrisSafeguard.unstablemode;
import static com.volmit.iris.core.safeguard.ServerBootSFG.incompatibilities;
import static org.bukkit.Bukkit.getServer;
@@ -68,7 +66,6 @@ import static org.bukkit.Bukkit.getServer;
public class CommandIris implements DecreeExecutor {
private CommandStudio studio;
private CommandPregen pregen;
private CommandLazyPregen lazyPregen;
private CommandSettings settings;
private CommandObject object;
private CommandJigsaw jigsaw;
@@ -175,16 +172,6 @@ public class CommandIris implements DecreeExecutor {
sender().sendMessage(C.GREEN + "Iris v" + Iris.instance.getDescription().getVersion() + " by Volmit Software");
}
//todo Move to React
@Decree(description = "Benchmark your server", origin = DecreeOrigin.CONSOLE)
public void serverbenchmark() throws InterruptedException {
if(!inProgress) {
IrisBenchmarking.runBenchmark();
} else {
Iris.info(C.RED + "Benchmark already is in progress.");
}
}
/*
/todo
@Decree(description = "Benchmark a pack", origin = DecreeOrigin.CONSOLE)
@@ -19,9 +19,7 @@
package com.volmit.iris.core.commands;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.gui.PregeneratorJob;
import com.volmit.iris.core.pregenerator.LazyPregenerator;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.tools.IrisToolbelt;
import com.volmit.iris.util.decree.DecreeExecutor;
@@ -29,12 +27,9 @@ import com.volmit.iris.util.decree.annotations.Decree;
import com.volmit.iris.util.decree.annotations.Param;
import com.volmit.iris.util.format.C;
import com.volmit.iris.util.math.Position2;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.util.Vector;
import java.io.File;
@Decree(name = "pregen", aliases = "pregenerate", description = "Pregenerate your Iris worlds!")
public class CommandPregen implements DecreeExecutor {
@Decree(description = "Pregenerate a world")
@@ -44,7 +39,9 @@ public class CommandPregen implements DecreeExecutor {
@Param(description = "The world to pregen", contextual = true)
World world,
@Param(aliases = "middle", description = "The center location of the pregen. Use \"me\" for your current location", defaultValue = "0,0")
Vector center
Vector center,
@Param(description = "Open the Iris pregen gui", defaultValue = "true")
boolean gui
) {
try {
if (sender().isPlayer() && access() == null) {
@@ -52,13 +49,12 @@ public class CommandPregen implements DecreeExecutor {
sender().sendMessage(C.RED + "Please make sure the world is loaded & the engine is initialized. Generate a new chunk, for example.");
}
radius = Math.max(radius, 1024);
int w = (radius >> 9 + 1) * 2;
IrisToolbelt.pregenerate(PregenTask
.builder()
.center(new Position2(center.getBlockX() >> 9, center.getBlockZ() >> 9))
.gui(true)
.width(w)
.height(w)
.center(new Position2(center.getBlockX(), center.getBlockZ()))
.gui(gui)
.radiusX(radius)
.radiusZ(radius)
.build(), world);
String msg = C.GREEN + "Pregen started in " + C.GOLD + world.getName() + C.GREEN + " of " + C.GOLD + (radius * 2) + C.GREEN + " by " + C.GOLD + (radius * 2) + C.GREEN + " blocks from " + C.GOLD + center.getX() + "," + center.getZ();
sender().sendMessage(msg);
@@ -24,7 +24,6 @@ import com.volmit.iris.core.pregenerator.IrisPregenerator;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.core.tools.IrisPackBenchmarking;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.format.Form;
@@ -41,12 +40,12 @@ import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.BufferedImage;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import static com.volmit.iris.core.tools.IrisPackBenchmarking.benchmarkInProgress;
public class PregeneratorJob implements PregenListener {
private static final Color COLOR_EXISTS = parseColor("#4d7d5b");
private static final Color COLOR_BLACK = parseColor("#4d7d5b");
@@ -67,6 +66,7 @@ public class PregeneratorJob implements PregenListener {
private final Position2 max;
private final ChronoLatch cl = new ChronoLatch(TimeUnit.MINUTES.toMillis(1));
private final Engine engine;
private final ExecutorService service;
private JFrame frame;
private PregenRenderer renderer;
private int rgc = 0;
@@ -81,12 +81,12 @@ public class PregeneratorJob implements PregenListener {
this.task = task;
this.pregenerator = new IrisPregenerator(task, method, this);
max = new Position2(0, 0);
min = new Position2(0, 0);
task.iterateRegions((xx, zz) -> {
min.setX(Math.min(xx << 5, min.getX()));
min.setZ(Math.min(zz << 5, min.getZ()));
max.setX(Math.max((xx << 5) + 31, max.getX()));
max.setZ(Math.max((zz << 5) + 31, max.getZ()));
min = new Position2(Integer.MAX_VALUE, Integer.MAX_VALUE);
task.iterateAllChunks((xx, zz) -> {
min.setX(Math.min(xx, min.getX()));
min.setZ(Math.min(zz, min.getZ()));
max.setX(Math.max(xx, max.getX()));
max.setZ(Math.max(zz, max.getZ()));
});
if (IrisSettings.get().getGui().isUseServerLaunchedGuis() && task.isGui()) {
@@ -99,6 +99,7 @@ public class PregeneratorJob implements PregenListener {
}, "Iris Pregenerator");
t.setPriority(Thread.MIN_PRIORITY);
t.start();
service = Executors.newVirtualThreadPerTaskExecutor();
}
public static boolean shutdownInstance() {
@@ -162,7 +163,7 @@ public class PregeneratorJob implements PregenListener {
}
public void drawRegion(int x, int z, Color color) {
J.a(() -> PregenTask.iterateRegion(x, z, (xx, zz) -> {
J.a(() -> task.iterateChunks(x, z, (xx, zz) -> {
draw(xx, zz, color);
J.sleep(3);
}));
@@ -222,10 +223,10 @@ public class PregeneratorJob implements PregenListener {
}
@Override
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
info = new String[]{
(paused() ? "PAUSED" : (saving ? "Saving... " : "Generating")) + " " + Form.f(generated) + " of " + Form.f(totalChunks) + " (" + Form.pc(percent, 0) + " Complete)",
"Speed: " + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
"Speed: " + (cached ? "Cached " : "") + Form.f(chunksPerSecond, 0) + " Chunks/s, " + Form.f(regionsPerMinute, 1) + " Regions/m, " + Form.f(chunksPerMinute, 0) + " Chunks/m",
Form.duration(eta, 2) + " Remaining " + " (" + Form.duration(elapsed, 2) + " Elapsed)",
"Generation Method: " + method,
"Memory: " + Form.memSize(monitor.getUsedBytes(), 2) + " (" + Form.pc(monitor.getUsagePercent(), 0) + ") Pressure: " + Form.memSize(monitor.getPressure(), 0) + "/s",
@@ -243,13 +244,16 @@ public class PregeneratorJob implements PregenListener {
}
@Override
public void onChunkGenerated(int x, int z) {
public void onChunkGenerated(int x, int z, boolean cached) {
if (renderer == null || frame == null || !frame.isVisible()) return;
service.submit(() -> {
if (engine != null) {
draw(x, z, engine.draw((x << 4) + 8, (z << 4) + 8));
return;
}
draw(x, z, COLOR_GENERATED);
});
}
@Override
@@ -309,6 +313,7 @@ public class PregeneratorJob implements PregenListener {
close();
instance = null;
whenDone.forEach(Runnable::run);
service.shutdownNow();
}
@Override
@@ -37,7 +37,7 @@ public class INMS {
"1.21.4", "v1_21_R3"
);
private static final List<Version> PACKS = List.of(
new Version(21, 4, "31010"),
new Version(21, 4, "31020"),
new Version(21, 2, "31000"),
new Version(20, 1, "3910")
);
@@ -20,7 +20,6 @@ package com.volmit.iris.core.nms;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.core.nms.datapack.DataVersion;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KList;
@@ -32,15 +31,12 @@ import com.volmit.iris.util.nbt.mca.palette.MCAPaletteAccess;
import com.volmit.iris.util.nbt.tag.CompoundTag;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.entity.Dolphin;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.event.entity.CreatureSpawnEvent;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.generator.structure.Structure;
import org.bukkit.inventory.ItemStack;
import java.awt.*;
import java.awt.Color;
public interface INMSBinding {
@@ -93,7 +89,11 @@ public interface INMSBinding {
MCABiomeContainer newBiomeContainer(int min, int max);
default World createWorld(WorldCreator c) {
if (missingDimensionTypes(true, true, true))
throw new IllegalStateException("Missing dimenstion types to create world");
try (var ignored = injectLevelStems()) {
ignored.storeContext();
return c.createWorld();
}
}
@@ -130,11 +130,13 @@ public interface INMSBinding {
KList<String> getStructureKeys();
default AutoClosing injectLevelStems() {
return new AutoClosing(() -> {});
AutoClosing injectLevelStems();
default AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
return null;
}
default Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
return new Pair<>(0, injectLevelStems());
}
boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end);
void removeCustomDimensions(World world);
}
@@ -1,5 +1,6 @@
package com.volmit.iris.core.nms.container;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.function.NastyRunnable;
import lombok.AllArgsConstructor;
@@ -7,6 +8,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
@AllArgsConstructor
public class AutoClosing implements AutoCloseable {
private static final KMap<Thread, AutoClosing> CONTEXTS = new KMap<>();
private final AtomicBoolean closed = new AtomicBoolean();
private final NastyRunnable action;
@@ -14,9 +16,24 @@ public class AutoClosing implements AutoCloseable {
public void close() {
if (closed.getAndSet(true)) return;
try {
removeContext();
action.run();
} catch (Throwable e) {
throw new RuntimeException(e);
}
}
public void storeContext() {
CONTEXTS.put(Thread.currentThread(), this);
}
public void removeContext() {
CONTEXTS.values().removeIf(c -> c == this);
}
public static void closeContext() {
AutoClosing closing = CONTEXTS.remove(Thread.currentThread());
if (closing == null) return;
closing.close();
}
}
@@ -20,7 +20,9 @@ package com.volmit.iris.core.nms.v1X;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMSBinding;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.nms.container.BiomeColor;
import com.volmit.iris.core.nms.container.Pair;
import com.volmit.iris.engine.framework.Engine;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KMap;
@@ -118,6 +120,26 @@ public class NMSBinding1X implements INMSBinding {
return new KList<>(list);
}
@Override
public AutoClosing injectLevelStems() {
return new AutoClosing(() -> {});
}
@Override
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
return injectLevelStems();
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
return false;
}
@Override
public void removeCustomDimensions(World world) {
}
@Override
public CompoundTag serializeEntity(Entity location) {
return null;
@@ -165,8 +165,11 @@ public class ChunkUpdater {
if (rX < dimensions.min.getX() || rX > dimensions.max.getX() || rZ < dimensions.min.getZ() || rZ > dimensions.max.getZ()) {
return;
}
if (!new File(world.getWorldFolder(), "region" + File.separator + rX + "." + rZ + ".mca").exists()) {
return;
}
PregenTask.iterateRegion(rX, rZ, (x, z) -> {
task.iterateChunks(rX, rZ, (x, z) -> {
while (paused.get() && !cancelled.get()) {
J.sleep(50);
}
@@ -348,8 +351,8 @@ public class ChunkUpdater {
int width = maxZ - minZ + 1;
return new Dimensions(new Position2(minX, minZ), new Position2(maxX, maxZ), height * width, PregenTask.builder()
.width((int) Math.ceil(width / 2d))
.height((int) Math.ceil(height / 2d))
.radiusZ((int) Math.ceil(width / 2d * 512))
.radiusX((int) Math.ceil(height / 2d * 512))
.center(new Position2(oX, oZ))
.build());
}
@@ -19,7 +19,6 @@
package com.volmit.iris.core.pregenerator;
import com.volmit.iris.Iris;
import com.volmit.iris.core.pack.IrisPack;
import com.volmit.iris.core.tools.IrisPackBenchmarking;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.collection.KSet;
@@ -32,28 +31,33 @@ import com.volmit.iris.util.math.RollingSequence;
import com.volmit.iris.util.scheduling.ChronoLatch;
import com.volmit.iris.util.scheduling.J;
import com.volmit.iris.util.scheduling.Looper;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
public class IrisPregenerator {
private static final double INVALID = 9223372036854775807d;
private final PregenTask task;
private final PregeneratorMethod generator;
private final PregenListener listener;
private final Looper ticker;
private final AtomicBoolean paused;
private final AtomicBoolean shutdown;
private final RollingSequence cachedPerSecond;
private final RollingSequence chunksPerSecond;
private final RollingSequence chunksPerMinute;
private final RollingSequence regionsPerMinute;
private final KList<Integer> chunksPerSecondHistory;
private static AtomicInteger generated;
private final AtomicInteger generatedLast;
private final AtomicInteger generatedLastMinute;
private static AtomicInteger totalChunks;
private final AtomicLong generated;
private final AtomicLong generatedLast;
private final AtomicLong generatedLastMinute;
private final AtomicLong cached;
private final AtomicLong cachedLast;
private final AtomicLong cachedLastMinute;
private final AtomicLong totalChunks;
private final AtomicLong startTime;
private final ChronoLatch minuteLatch;
private final AtomicReference<String> currentGeneratorMethod;
@@ -75,46 +79,71 @@ public class IrisPregenerator {
net = new KSet<>();
currentGeneratorMethod = new AtomicReference<>("Void");
minuteLatch = new ChronoLatch(60000, false);
cachedPerSecond = new RollingSequence(5);
chunksPerSecond = new RollingSequence(10);
chunksPerMinute = new RollingSequence(10);
regionsPerMinute = new RollingSequence(10);
chunksPerSecondHistory = new KList<>();
generated = new AtomicInteger(0);
generatedLast = new AtomicInteger(0);
generatedLastMinute = new AtomicInteger(0);
totalChunks = new AtomicInteger(0);
task.iterateRegions((_a, _b) -> totalChunks.addAndGet(1024));
generated = new AtomicLong(0);
generatedLast = new AtomicLong(0);
generatedLastMinute = new AtomicLong(0);
cached = new AtomicLong();
cachedLast = new AtomicLong(0);
cachedLastMinute = new AtomicLong(0);
totalChunks = new AtomicLong(0);
task.iterateAllChunks((_a, _b) -> totalChunks.incrementAndGet());
startTime = new AtomicLong(M.ms());
ticker = new Looper() {
@Override
protected long loop() {
long eta = computeETA();
int secondGenerated = generated.get() - generatedLast.get();
long secondCached = cached.get() - cachedLast.get();
cachedLast.set(cached.get());
cachedPerSecond.put(secondCached);
long secondGenerated = generated.get() - generatedLast.get() - secondCached;
generatedLast.set(generated.get());
if (secondCached == 0 || secondGenerated != 0) {
chunksPerSecond.put(secondGenerated);
chunksPerSecondHistory.add(secondGenerated);
chunksPerSecondHistory.add((int) secondGenerated);
}
if (minuteLatch.flip()) {
int minuteGenerated = generated.get() - generatedLastMinute.get();
long minuteCached = cached.get() - cachedLastMinute.get();
cachedLastMinute.set(cached.get());
long minuteGenerated = generated.get() - generatedLastMinute.get() - minuteCached;
generatedLastMinute.set(generated.get());
if (minuteCached == 0 || minuteGenerated != 0) {
chunksPerMinute.put(minuteGenerated);
regionsPerMinute.put((double) minuteGenerated / 1024D);
}
}
boolean cached = cachedPerSecond.getAverage() != 0;
listener.onTick(chunksPerSecond.getAverage(), chunksPerMinute.getAverage(),
listener.onTick(
cached ? cachedPerSecond.getAverage() : chunksPerSecond.getAverage(),
chunksPerMinute.getAverage(),
regionsPerMinute.getAverage(),
(double) generated.get() / (double) totalChunks.get(),
generated.get(), totalChunks.get(),
totalChunks.get() - generated.get(),
eta, M.ms() - startTime.get(), currentGeneratorMethod.get());
(double) generated.get() / (double) totalChunks.get(), generated.get(),
totalChunks.get(),
totalChunks.get() - generated.get(), eta, M.ms() - startTime.get(), currentGeneratorMethod.get(),
cached);
if (cl.flip()) {
double percentage = ((double) generated.get() / (double) totalChunks.get()) * 100;
if (!IrisPackBenchmarking.benchmarkInProgress) {
Iris.info("Pregen: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage);
} else {
Iris.info("Benchmarking: " + Form.f(generated.get()) + " of " + Form.f(totalChunks.get()) + " (%.0f%%) " + Form.f((int) chunksPerSecond.getAverage()) + "/s ETA: " + Form.duration(eta, 2), percentage);
}
Iris.info("%s: %s of %s (%.0f%%), %s/s ETA: %s",
IrisPackBenchmarking.benchmarkInProgress ? "Benchmarking" : "Pregen",
Form.f(generated.get()),
Form.f(totalChunks.get()),
percentage,
cached ?
"Cached " + Form.f((int) cachedPerSecond.getAverage()) :
Form.f((int) chunksPerSecond.getAverage()),
Form.duration(eta, 2)
);
}
return 1000;
}
@@ -122,12 +151,13 @@ public class IrisPregenerator {
}
private long computeETA() {
return (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
double d = (long) (totalChunks.get() > 1024 ? // Generated chunks exceed 1/8th of total?
// If yes, use smooth function (which gets more accurate over time since its less sensitive to outliers)
((totalChunks.get() - generated.get()) * ((double) (M.ms() - startTime.get()) / (double) generated.get())) :
((totalChunks.get() - generated.get() - cached.get()) * ((double) (M.ms() - startTime.get()) / ((double) generated.get() - cached.get()))) :
// If no, use quick function (which is less accurate over time but responds better to the initial delay)
((totalChunks.get() - generated.get()) / chunksPerSecond.getAverage()) * 1000
((totalChunks.get() - generated.get() - cached.get()) / chunksPerSecond.getAverage()) * 1000
);
return Double.isFinite(d) && d != INVALID ? (long) d : 0;
}
@@ -139,8 +169,10 @@ public class IrisPregenerator {
init();
ticker.start();
checkRegions();
var p = PrecisionStopwatch.start();
task.iterateRegions((x, z) -> visitRegion(x, z, true));
task.iterateRegions((x, z) -> visitRegion(x, z, false));
Iris.info("Pregen took " + Form.duration((long) p.getMilliseconds()));
shutdown();
if (!IrisPackBenchmarking.benchmarkInProgress) {
Iris.info(C.IRIS + "Pregen stopped.");
@@ -194,7 +226,7 @@ public class IrisPregenerator {
} else if (!regions) {
hit = true;
listener.onRegionGenerating(x, z);
PregenTask.iterateRegion(x, z, (xx, zz) -> {
task.iterateChunks(x, z, (xx, zz) -> {
while (paused.get() && !shutdown.get()) {
J.sleep(50);
}
@@ -235,8 +267,8 @@ public class IrisPregenerator {
private PregenListener listenify(PregenListener listener) {
return new PregenListener() {
@Override
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method) {
listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method);
public void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached) {
listener.onTick(chunksPerSecond, chunksPerMinute, regionsPerMinute, percent, generated, totalChunks, chunksRemaining, eta, elapsed, method, cached);
}
@Override
@@ -245,9 +277,10 @@ public class IrisPregenerator {
}
@Override
public void onChunkGenerated(int x, int z) {
listener.onChunkGenerated(x, z);
public void onChunkGenerated(int x, int z, boolean c) {
listener.onChunkGenerated(x, z, c);
generated.addAndGet(1);
if (c) cached.addAndGet(1);
}
@Override
@@ -19,11 +19,15 @@
package com.volmit.iris.core.pregenerator;
public interface PregenListener {
void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, int generated, int totalChunks, int chunksRemaining, long eta, long elapsed, String method);
void onTick(double chunksPerSecond, double chunksPerMinute, double regionsPerMinute, double percent, long generated, long totalChunks, long chunksRemaining, long eta, long elapsed, String method, boolean cached);
void onChunkGenerating(int x, int z);
void onChunkGenerated(int x, int z);
default void onChunkGenerated(int x, int z) {
onChunkGenerated(x, z, false);
}
void onChunkGenerated(int x, int z, boolean cached);
void onRegionGenerated(int x, int z);
@@ -32,17 +32,26 @@ import java.util.Comparator;
@Data
public class PregenTask {
private static final Position2 ZERO = new Position2(0, 0);
private static final KList<Position2> ORDER_CENTER = computeChunkOrder();
private static final KMap<Position2, KList<Position2>> ORDERS = new KMap<>();
@Builder.Default
private boolean gui = false;
private final boolean gui = false;
@Builder.Default
private Position2 center = new Position2(0, 0);
private final Position2 center = new Position2(0, 0);
@Builder.Default
private int width = 1;
private final int radiusX = 1;
@Builder.Default
private int height = 1;
private final int radiusZ = 1;
private final Bounds bounds = new Bounds();
protected PregenTask(boolean gui, Position2 center, int radiusX, int radiusZ) {
this.gui = gui;
this.center = new ProxiedPos(center);
this.radiusX = radiusX;
this.radiusZ = radiusZ;
bounds.update();
}
public static void iterateRegion(int xr, int zr, Spiraled s, Position2 pull) {
for (Position2 i : ORDERS.computeIfAbsent(pull, PregenTask::computeOrder)) {
@@ -70,29 +79,72 @@ public class PregenTask {
return p;
}
private static KList<Position2> computeChunkOrder() {
Position2 center = new Position2(15, 15);
KList<Position2> p = new KList<>();
new Spiraler(33, 33, (x, z) -> {
int xx = x + 15;
int zz = z + 15;
if (xx < 0 || xx > 31 || zz < 0 || zz > 31) {
return;
}
p.add(new Position2(xx, zz));
}).drain();
p.sort(Comparator.comparing((i) -> i.distance(center)));
return p;
}
public void iterateRegions(Spiraled s) {
new Spiraler(getWidth() * 2, getHeight() * 2, s)
.setOffset(center.getX(), center.getZ()).drain();
var bound = bounds.region();
new Spiraler(bound.sizeX, bound.sizeZ, ((x, z) -> {
if (bound.check(x, z)) s.on(x, z);
})).setOffset(center.getX() >> 9, center.getZ() >> 9).drain();
}
public void iterateChunks(int rX, int rZ, Spiraled s) {
var bound = bounds.chunk();
iterateRegion(rX, rZ, ((x, z) -> {
if (bound.check(x, z)) s.on(x, z);
}));
}
public void iterateAllChunks(Spiraled s) {
new Spiraler(getWidth() * 2, getHeight() * 2, (x, z) -> iterateRegion(x, z, s))
.setOffset(center.getX(), center.getZ()).drain();
iterateRegions(((rX, rZ) -> iterateChunks(rX, rZ, s)));
}
private class Bounds {
private Bound chunk = null;
private Bound region = null;
public void update() {
int maxX = center.getX() + radiusX;
int maxZ = center.getZ() + radiusZ;
int minX = center.getX() - radiusX;
int minZ = center.getZ() - radiusZ;
chunk = new Bound(minX >> 4, minZ >> 4, Math.ceilDiv(maxX, 16), Math.ceilDiv(maxZ, 16));
region = new Bound(minX >> 9, minZ >> 9, Math.ceilDiv(maxX, 512), Math.ceilDiv(maxZ, 512));
}
public Bound chunk() {
if (chunk == null) update();
return chunk;
}
public Bound region() {
if (region == null) update();
return region;
}
}
private record Bound(int minX, int maxX, int minZ, int maxZ, int sizeX, int sizeZ) {
private Bound(int minX, int minZ, int maxX, int maxZ) {
this(minX, maxX, minZ, maxZ, maxZ - minZ + 1, maxZ - minZ + 1);
}
boolean check(int x, int z) {
return x >= minX && x <= maxX && z >= minZ && z <= maxZ;
}
}
private static class ProxiedPos extends Position2 {
public ProxiedPos(Position2 p) {
super(p.getX(), p.getZ());
}
@Override
public void setX(int x) {
throw new IllegalStateException("This Position2 may not be modified");
}
@Override
public void setZ(int z) {
throw new IllegalStateException("This Position2 may not be modified");
}
}
}
@@ -0,0 +1,70 @@
package com.volmit.iris.core.pregenerator.cache;
import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.documentation.RegionCoordinates;
import java.io.File;
public interface PregenCache {
default boolean isThreadSafe() {
return false;
}
@ChunkCoordinates
boolean isChunkCached(int x, int z);
@RegionCoordinates
boolean isRegionCached(int x, int z);
@ChunkCoordinates
void cacheChunk(int x, int z);
@RegionCoordinates
void cacheRegion(int x, int z);
void write();
static PregenCache create(File directory) {
if (directory == null) return EMPTY;
return new PregenCacheImpl(directory);
}
default PregenCache sync() {
if (isThreadSafe()) return this;
return new SynchronizedCache(this);
}
PregenCache EMPTY = new PregenCache() {
@Override
public boolean isThreadSafe() {
return true;
}
@Override
public boolean isChunkCached(int x, int z) {
return false;
}
@Override
public boolean isRegionCached(int x, int z) {
return false;
}
@Override
public void cacheChunk(int x, int z) {
}
@Override
public void cacheRegion(int x, int z) {
}
@Override
public void write() {
}
};
}
@@ -0,0 +1,215 @@
package com.volmit.iris.core.pregenerator.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.volmit.iris.Iris;
import com.volmit.iris.util.data.Varint;
import com.volmit.iris.util.documentation.ChunkCoordinates;
import com.volmit.iris.util.documentation.RegionCoordinates;
import com.volmit.iris.util.parallel.HyperLock;
import lombok.RequiredArgsConstructor;
import net.jpountz.lz4.LZ4BlockInputStream;
import net.jpountz.lz4.LZ4BlockOutputStream;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
@NotThreadSafe
@RequiredArgsConstructor
class PregenCacheImpl implements PregenCache {
private static final int SIZE = 32;
private final File directory;
private final HyperLock hyperLock = new HyperLock(SIZE * 2, true);
private final LoadingCache<Pos, Plate> cache = Caffeine.newBuilder()
.expireAfterAccess(10, TimeUnit.SECONDS)
.maximumSize(SIZE)
.removalListener(this::onRemoval)
.evictionListener(this::onRemoval)
.build(this::load);
@ChunkCoordinates
public boolean isChunkCached(int x, int z) {
var plate = cache.get(new Pos(x >> 10, z >> 10));
if (plate == null) return false;
return plate.isCached((x >> 5) & 31, (z >> 5) & 31, r -> r.isCached(x & 31, z & 31));
}
@RegionCoordinates
public boolean isRegionCached(int x, int z) {
var plate = cache.get(new Pos(x >> 5, z >> 5));
if (plate == null) return false;
return plate.isCached(x & 31, z & 31, Region::isCached);
}
@ChunkCoordinates
public void cacheChunk(int x, int z) {
var plate = cache.get(new Pos(x >> 10, z >> 10));
plate.cache((x >> 5) & 31, (z >> 5) & 31, r -> r.cache(x & 31, z & 31));
}
@RegionCoordinates
public void cacheRegion(int x, int z) {
var plate = cache.get(new Pos(x >> 5, z >> 5));
plate.cache(x & 31, z & 31, Region::cache);
}
public void write() {
cache.asMap().values().forEach(this::write);
}
private Plate load(Pos key) {
hyperLock.lock(key.x, key.z);
try {
File file = fileForPlate(key);
if (!file.exists()) return new Plate(key);
try (var in = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) {
return new Plate(key, in);
} catch (IOException e){
Iris.error("Failed to read pregen cache " + file);
e.printStackTrace();
return new Plate(key);
}
} finally {
hyperLock.unlock(key.x, key.z);
}
}
private void write(Plate plate) {
hyperLock.lock(plate.pos.x, plate.pos.z);
try {
File file = fileForPlate(plate.pos);
try (var out = new DataOutputStream(new LZ4BlockOutputStream(new FileOutputStream(file)))) {
plate.write(out);
} catch (IOException e) {
Iris.error("Failed to write pregen cache " + file);
e.printStackTrace();
}
} finally {
hyperLock.unlock(plate.pos.x, plate.pos.z);
}
}
private void onRemoval(@Nullable Pos key, @Nullable Plate plate, RemovalCause cause) {
if (plate == null) return;
write(plate);
}
private File fileForPlate(Pos pos) {
if (!directory.exists() && !directory.mkdirs())
throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath());
return new File(directory, "c." + pos.x + "." + pos.z + ".lz4b");
}
private static class Plate {
private final Pos pos;
private short count;
private Region[] regions;
public Plate(Pos pos) {
this.pos = pos;
count = 0;
regions = new Region[1024];
}
public Plate(Pos pos, DataInput in) throws IOException {
this.pos = pos;
count = (short) Varint.readSignedVarInt(in);
if (count == 1024) return;
regions = new Region[1024];
for (int i = 0; i < 1024; i++) {
if (in.readBoolean()) continue;
regions[i] = new Region(in);
}
}
public boolean isCached(int x, int z, Predicate<Region> predicate) {
if (count == 1024) return true;
Region region = regions[x * 32 + z];
if (region == null) return false;
return predicate.test(region);
}
public void cache(int x, int z, Predicate<Region> predicate) {
if (count == 1024) return;
Region region = regions[x * 32 + z];
if (region == null) regions[x * 32 + z] = region = new Region();
if (predicate.test(region)) count++;
}
public void write(DataOutput out) throws IOException {
Varint.writeSignedVarInt(count, out);
if (count == 1024) return;
for (Region region : regions) {
out.writeBoolean(region == null);
if (region == null) continue;
region.write(out);
}
}
}
private static class Region {
private short count;
private long[] words;
public Region() {
count = 0;
words = new long[64];
}
public Region(DataInput in) throws IOException {
count = (short) Varint.readSignedVarInt(in);
if (count == 1024) return;
words = new long[64];
for (int i = 0; i < 64; i++) {
words[i] = Varint.readUnsignedVarLong(in);
}
}
public boolean cache() {
if (count == 1024) return false;
count = 1024;
words = null;
return true;
}
public boolean cache(int x, int z) {
if (count == 1024) return false;
int i = x * 32 + z;
int w = i >> 6;
long b = 1L << (i & 63);
var cur = (words[w] & b) != 0;
if (cur) return false;
if (++count == 1024) {
words = null;
return true;
} else words[w] |= b;
return false;
}
public boolean isCached() {
return count == 1024;
}
public boolean isCached(int x, int z) {
int i = x * 32 + z;
return count == 1024 || (words[i >> 6] & 1L << (i & 63)) != 0;
}
public void write(DataOutput out) throws IOException {
Varint.writeSignedVarInt(count, out);
if (isCached()) return;
for (long word : words) {
Varint.writeUnsignedVarLong(word, out);
}
}
}
private record Pos(int x, int z) {}
}
@@ -0,0 +1,48 @@
package com.volmit.iris.core.pregenerator.cache;
import lombok.AllArgsConstructor;
@AllArgsConstructor
class SynchronizedCache implements PregenCache {
private final PregenCache cache;
@Override
public boolean isThreadSafe() {
return true;
}
@Override
public boolean isChunkCached(int x, int z) {
synchronized (cache) {
return cache.isChunkCached(x, z);
}
}
@Override
public boolean isRegionCached(int x, int z) {
synchronized (cache) {
return cache.isRegionCached(x, z);
}
}
@Override
public void cacheChunk(int x, int z) {
synchronized (cache) {
cache.cacheChunk(x, z);
}
}
@Override
public void cacheRegion(int x, int z) {
synchronized (cache) {
cache.cacheRegion(x, z);
}
}
@Override
public void write() {
synchronized (cache) {
cache.write();
}
}
}
@@ -19,6 +19,7 @@
package com.volmit.iris.core.pregenerator.methods;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.core.tools.IrisToolbelt;
@@ -33,12 +34,15 @@ import org.bukkit.World;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
public class AsyncPregenMethod implements PregeneratorMethod {
private final World world;
private final MultiBurst burst;
private final ExecutorService service;
private final Semaphore semaphore;
private final int threads;
private final Map<Chunk, Long> lastUse;
public AsyncPregenMethod(World world, int threads) {
@@ -47,8 +51,11 @@ public class AsyncPregenMethod implements PregeneratorMethod {
}
this.world = world;
burst = new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
semaphore = new Semaphore(256);
service = IrisSettings.get().getPregen().isUseVirtualThreads() ?
Executors.newVirtualThreadPerTaskExecutor() :
new MultiBurst("Iris Async Pregen", Thread.MIN_PRIORITY);
this.threads = IrisSettings.get().getPregen().getMaxConcurrency();
semaphore = new Semaphore(threads);
this.lastUse = new KMap<>();
}
@@ -101,9 +108,9 @@ public class AsyncPregenMethod implements PregeneratorMethod {
@Override
public void close() {
semaphore.acquireUninterruptibly(256);
semaphore.acquireUninterruptibly(threads);
unloadAndSaveAllChunks();
burst.close();
service.shutdown();
}
@Override
@@ -129,7 +136,7 @@ public class AsyncPregenMethod implements PregeneratorMethod {
} catch (InterruptedException e) {
return;
}
burst.complete(() -> completeChunk(x, z, listener));
service.submit(() -> completeChunk(x, z, listener));
}
@Override
@@ -0,0 +1,86 @@
package com.volmit.iris.core.pregenerator.methods;
import com.volmit.iris.Iris;
import com.volmit.iris.core.pregenerator.PregenListener;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.core.pregenerator.cache.PregenCache;
import com.volmit.iris.core.service.GlobalCacheSVC;
import com.volmit.iris.util.mantle.Mantle;
import lombok.AllArgsConstructor;
@AllArgsConstructor
public class CachedPregenMethod implements PregeneratorMethod {
private final PregeneratorMethod method;
private final PregenCache cache;
public CachedPregenMethod(PregeneratorMethod method, String worldName) {
this.method = method;
var cache = Iris.service(GlobalCacheSVC.class).get(worldName);
if (cache == null) {
Iris.debug("Could not find existing cache for " + worldName + " creating fallback");
cache = GlobalCacheSVC.createDefault(worldName);
}
this.cache = cache;
}
@Override
public void init() {
method.init();
}
@Override
public void close() {
method.close();
cache.write();
}
@Override
public void save() {
method.save();
cache.write();
}
@Override
public boolean supportsRegions(int x, int z, PregenListener listener) {
return cache.isRegionCached(x, z) || method.supportsRegions(x, z, listener);
}
@Override
public String getMethod(int x, int z) {
return method.getMethod(x, z);
}
@Override
public void generateRegion(int x, int z, PregenListener listener) {
if (cache.isRegionCached(x, z)) {
listener.onRegionGenerated(x, z);
int rX = x << 5, rZ = z << 5;
for (int cX = 0; cX < 32; cX++) {
for (int cZ = 0; cZ < 32; cZ++) {
listener.onChunkGenerated(rX + cX, rZ + cZ, true);
listener.onChunkCleaned(rX + cX, rZ + cZ);
}
}
return;
}
method.generateRegion(x, z, listener);
cache.cacheRegion(x, z);
}
@Override
public void generateChunk(int x, int z, PregenListener listener) {
if (cache.isChunkCached(x, z)) {
listener.onChunkGenerated(x, z, true);
listener.onChunkCleaned(x, z);
return;
}
method.generateChunk(x, z, listener);
cache.cacheChunk(x, z);
}
@Override
public Mantle getMantle() {
return method.getMantle();
}
}
@@ -1,6 +1,7 @@
package com.volmit.iris.core.safeguard;
import com.volmit.iris.Iris;
import com.volmit.iris.core.IrisSettings;
public class IrisSafeguard {
public static boolean unstablemode = false;
@@ -11,5 +12,13 @@ public class IrisSafeguard {
Iris.info("Enabled Iris SafeGuard");
ServerBootSFG.BootCheck();
}
public static void earlySplash() {
if (ServerBootSFG.safeguardPassed || IrisSettings.get().getGeneral().DoomsdayAnnihilationSelfDestructMode)
return;
Iris.instance.splash();
UtilsSFG.splash();
}
}
@@ -3,6 +3,7 @@ package com.volmit.iris.core.safeguard;
import com.volmit.iris.Iris;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.nms.v1X.NMSBinding1X;
import com.volmit.iris.engine.object.IrisContextInjector;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
@@ -29,6 +30,7 @@ public class ServerBootSFG {
public static boolean isJRE = false;
public static boolean hasPrivileges = true;
public static boolean unsuportedversion = false;
public static boolean missingDimensionTypes = false;
protected static boolean safeguardPassed;
public static boolean passedserversoftware = true;
protected static int count;
@@ -110,6 +112,12 @@ public class ServerBootSFG {
severityMedium++;
}
if (IrisContextInjector.isMissingDimensionTypes()) {
missingDimensionTypes = true;
joiner.add("Missing Dimension Types");
severityHigh++;
}
allIncompatibilities = joiner.toString();
safeguardPassed = (severityHigh == 0 && severityMedium == 0 && severityLow == 0);
@@ -39,6 +39,11 @@ public class UtilsSFG {
Iris.safeguard(C.RED + "Server Version");
Iris.safeguard(C.RED + "- Iris only supports 1.20.1 > 1.21.4");
}
if (ServerBootSFG.missingDimensionTypes) {
Iris.safeguard(C.RED + "Dimension Types");
Iris.safeguard(C.RED + "- Required Iris dimension types were not loaded.");
Iris.safeguard(C.RED + "- If this still happens after a restart please contact support.");
}
if (!ServerBootSFG.passedserversoftware) {
Iris.safeguard(C.YELLOW + "Unsupported Server Software");
Iris.safeguard(C.YELLOW + "- Please consider using Paper or Purpur instead.");
@@ -0,0 +1,104 @@
package com.volmit.iris.core.service;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.volmit.iris.core.IrisSettings;
import com.volmit.iris.core.pregenerator.cache.PregenCache;
import com.volmit.iris.util.collection.KMap;
import com.volmit.iris.util.plugin.IrisService;
import lombok.NonNull;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.world.ChunkLoadEvent;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.event.world.WorldUnloadEvent;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.function.Function;
public class GlobalCacheSVC implements IrisService {
private static final Cache<String, PregenCache> REFERENCE_CACHE = Caffeine.newBuilder().weakValues().build();
private final KMap<String, PregenCache> globalCache = new KMap<>();
private transient boolean lastState;
@Override
public void onEnable() {
lastState = !IrisSettings.get().getWorld().isGlobalPregenCache();
if (lastState) return;
Bukkit.getWorlds().forEach(this::createCache);
}
@Override
public void onDisable() {
globalCache.values().forEach(PregenCache::write);
}
@Nullable
public PregenCache get(@NonNull World world) {
return globalCache.get(world.getName());
}
@Nullable
public PregenCache get(@NonNull String world) {
return globalCache.get(world);
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(WorldInitEvent event) {
if (isDisabled()) return;
createCache(event.getWorld());
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(WorldUnloadEvent event) {
var cache = globalCache.remove(event.getWorld().getName());
if (cache == null) return;
cache.write();
}
@EventHandler(priority = EventPriority.MONITOR)
public void on(ChunkLoadEvent event) {
var cache = get(event.getWorld());
if (cache == null) return;
cache.cacheChunk(event.getChunk().getX(), event.getChunk().getZ());
}
private void createCache(World world) {
globalCache.computeIfAbsent(world.getName(), GlobalCacheSVC::createDefault);
}
private boolean isDisabled() {
boolean conf = IrisSettings.get().getWorld().isGlobalPregenCache();
if (lastState != conf)
return lastState;
if (conf) {
Bukkit.getWorlds().forEach(this::createCache);
} else {
globalCache.values().removeIf(cache -> {
cache.write();
return true;
});
}
return lastState = !conf;
}
@NonNull
public static PregenCache createCache(@NonNull String worldName, @NonNull Function<String, PregenCache> provider) {
return REFERENCE_CACHE.get(worldName, provider);
}
@NonNull
public static PregenCache createDefault(@NonNull String worldName) {
return createCache(worldName, GlobalCacheSVC::createDefault0);
}
private static PregenCache createDefault0(String worldName) {
return PregenCache.create(new File(Bukkit.getWorldContainer(), String.join(File.separator, worldName, "iris", "pregen"))).sync();
}
}
@@ -1,625 +0,0 @@
package com.volmit.iris.core.tools;
import com.volmit.iris.Iris;
import com.volmit.iris.util.format.C;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import oshi.hardware.HWDiskStore;
import oshi.software.os.OperatingSystem;
import java.io.*;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;
import java.util.zip.Deflater;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import static com.google.common.math.LongMath.isPrime;
import static com.volmit.iris.util.misc.getHardware.getCPUModel;
public class IrisBenchmarking {
static String ServerOS;
static String filePath = "benchmark.dat";
static double avgWriteSpeedMBps;
static double avgReadSpeedMBps;
static double highestWriteSpeedMBps;
static double highestReadSpeedMBps;
static double lowestWriteSpeedMBps;
static double lowestReadSpeedMBps;
static double calculateIntegerMath;
static double calculateFloatingPoint;
static double calculatePrimeNumbers;
static double calculateStringSorting;
static double calculateDataEncryption;
static double calculateDataCompression;
static String currentRunning = "None";
static int BenchmarksCompleted = 0;
static int BenchmarksTotal = 7;
static int totalTasks = 10;
static int currentTasks = 0;
static double WindowsCPUCompression;
static double WindowsCPUEncryption;
static double WindowsCPUCSHA1;
static double elapsedTimeNs;
static boolean Winsat = false;
static boolean WindowsDiskSpeed = false;
public static boolean inProgress = false;
static double startTime;
// Good enough for now. . .
public static void runBenchmark() throws InterruptedException {
inProgress = true;
getServerOS();
deleteTestFile(filePath);
AtomicReference<Double> doneCalculateDiskSpeed = new AtomicReference<>((double) 0);
startBenchmarkTimer();
Iris.info("Benchmark Started!");
Iris.warn("Although it may seem momentarily paused, it's actively processing.");
BenchmarksCompleted = 0;
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
currentRunning = "calculateDiskSpeed";
progressBar();
if (ServerOS.contains("Windows") && isRunningAsAdmin()) {
WindowsDiskSpeed = true;
WindowsDiskSpeedTest();
} else {
warningFallback();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
doneCalculateDiskSpeed.set(roundToTwoDecimalPlaces(calculateDiskSpeed()));
BenchmarksCompleted++;
}
}).thenRun(() -> {
currentRunning = "WindowsCpuSpeedTest";
progressBar();
if (ServerOS.contains("Windows") && isRunningAsAdmin()) {
Winsat = true;
WindowsCpuSpeedTest();
} else {
Iris.info("Skipping:" + C.BLUE + " Windows System Assessment Tool Benchmarks");
if (!ServerOS.contains("Windows")) {
Iris.info("Required Software:" + C.BLUE + " Windows");
BenchmarksTotal = 6;
}
if (!isRunningAsAdmin()) {
Iris.info(C.RED + "ERROR: " + C.DARK_RED + "Elevated privileges missing");
BenchmarksTotal = 6;
}
}
}).thenRun(() -> {
currentRunning = "calculateIntegerMath";
progressBar();
calculateIntegerMath = roundToTwoDecimalPlaces(calculateIntegerMath());
BenchmarksCompleted++;
}).thenRun(() -> {
currentRunning = "calculateFloatingPoint";
progressBar();
calculateFloatingPoint = roundToTwoDecimalPlaces(calculateFloatingPoint());
BenchmarksCompleted++;
}).thenRun(() -> {
currentRunning = "calculateStringSorting";
progressBar();
calculateStringSorting = roundToTwoDecimalPlaces(calculateStringSorting());
BenchmarksCompleted++;
}).thenRun(() -> {
currentRunning = "calculatePrimeNumbers";
progressBar();
calculatePrimeNumbers = roundToTwoDecimalPlaces(calculatePrimeNumbers());
BenchmarksCompleted++;
}).thenRun(() -> {
currentRunning = "calculateDataEncryption";
progressBar();
calculateDataEncryption = roundToTwoDecimalPlaces(calculateDataEncryption());
BenchmarksCompleted++;
}).thenRun(() -> {
currentRunning = "calculateDataCompression";
progressBar();
calculateDataCompression = roundToTwoDecimalPlaces(calculateDataCompression());
BenchmarksCompleted++;
}).thenRun(() -> {
elapsedTimeNs = stopBenchmarkTimer();
results();
inProgress = false;
});
try {
future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
public static void progressBar() {
Iris.info("-----------------------------------------------------");
Iris.info("Currently Running: " + C.BLUE + currentRunning);
// Iris.info("Tasks: " + "Current Tasks: " + C.BLUE + currentTasks + C.WHITE + " / " + "Total Tasks: " + C.BLUE + totalTasks);
Iris.info("Benchmarks Completed: " + C.BLUE + BenchmarksCompleted + C.WHITE + " / " + "Total: " + C.BLUE + BenchmarksTotal);
Iris.info("-----------------------------------------------------");
}
public static void results() {
SystemInfo systemInfo = new SystemInfo();
GlobalMemory globalMemory = systemInfo.getHardware().getMemory();
long totalMemoryMB = globalMemory.getTotal() / (1024 * 1024);
long availableMemoryMB = globalMemory.getAvailable() / (1024 * 1024);
long totalPageSize = globalMemory.getPageSize() / (1024 * 1024);
long usedMemoryMB = totalMemoryMB - availableMemoryMB;
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
Iris.info("OS: " + ServerOS);
if (!isRunningAsAdmin() || !ServerOS.contains("Windows")) {
Iris.info(C.GOLD + "For the full results use Windows + Admin Rights..");
}
Iris.info("CPU Model: " + getCPUModel());
Iris.info("CPU Score: " + "WIP");
Iris.info("- Integer Math: " + calculateIntegerMath + " MOps/Sec");
Iris.info("- Floating Point Math: " + calculateFloatingPoint + " MOps/Sec");
Iris.info("- Find Prime Numbers: " + calculatePrimeNumbers + " Primes/Sec");
Iris.info("- Random String Sorting: " + calculateStringSorting + " Thousand Strings/Sec");
Iris.info("- Data Encryption: " + formatDouble(calculateDataEncryption) + " MBytes/Sec");
Iris.info("- Data Compression: " + formatDouble(calculateDataCompression) + " MBytes/Sec");
if (WindowsDiskSpeed) {
//Iris.info("Disk Model: " + getDiskModel());
Iris.info(C.BLUE + "- Running with Windows System Assessment Tool");
Iris.info("- Sequential 64.0 Write: " + C.BLUE + formatDouble(avgWriteSpeedMBps) + " Mbps");
Iris.info("- Sequential 64.0 Read: " + C.BLUE + formatDouble(avgReadSpeedMBps) + " Mbps");
} else {
// Iris.info("Disk Model: " + getDiskModel());
Iris.info(C.GREEN + "- Running in Native Mode");
Iris.info("- Average Write Speed: " + C.GREEN + formatDouble(avgWriteSpeedMBps) + " Mbps");
Iris.info("- Average Read Speed: " + C.GREEN + formatDouble(avgReadSpeedMBps) + " Mbps");
Iris.info("- Highest Write Speed: " + formatDouble(highestWriteSpeedMBps) + " Mbps");
Iris.info("- Highest Read Speed: " + formatDouble(highestReadSpeedMBps) + " Mbps");
Iris.info("- Lowest Write Speed: " + formatDouble(lowestWriteSpeedMBps) + " Mbps");
Iris.info("- Lowest Read Speed: " + formatDouble(lowestReadSpeedMBps) + " Mbps");
}
Iris.info("Ram Usage: ");
Iris.info("- Total Ram: " + totalMemoryMB + " MB");
Iris.info("- Used Ram: " + usedMemoryMB + " MB");
Iris.info("- Total Process Ram: " + C.BLUE + getMaxMemoryUsage() + " MB");
Iris.info("- Total Paging Size: " + totalPageSize + " MB");
if (Winsat) {
Iris.info(C.BLUE + "Windows System Assessment Tool: ");
Iris.info("- CPU LZW Compression:" + C.BLUE + formatDouble(WindowsCPUCompression) + " MB/s");
Iris.info("- CPU AES256 Encryption: " + C.BLUE + formatDouble(WindowsCPUEncryption) + " MB/s");
Iris.info("- CPU SHA1 Hash: " + C.BLUE + formatDouble(WindowsCPUCSHA1) + " MB/s");
Iris.info("Duration: " + roundToTwoDecimalPlaces(elapsedTimeNs) + " Seconds");
}
}
public static long getMaxMemoryUsage() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
long maxHeapMemory = heapMemoryUsage.getMax();
long maxNonHeapMemory = nonHeapMemoryUsage.getMax();
long maxMemoryUsageMB = (maxHeapMemory + maxNonHeapMemory) / (1024 * 1024);
return maxMemoryUsageMB;
}
public static void getServerOS() {
SystemInfo systemInfo = new SystemInfo();
OperatingSystem os = systemInfo.getOperatingSystem();
ServerOS = os.toString();
}
public static boolean isRunningAsAdmin() {
if (ServerOS.contains("Windows")) {
try {
Process process = Runtime.getRuntime().exec("winsat disk");
process.waitFor();
return process.exitValue() == 0;
} catch (IOException | InterruptedException e) {
// Hmm
}
}
return false;
}
public static void warningFallback() {
Iris.info(C.RED + "Using the " + C.DARK_RED + "FALLBACK" + C.RED + " method due to compatibility issues. ");
Iris.info(C.RED + "Please note that this may result in less accurate results.");
}
private static String formatDouble(double value) {
return String.format("%.2f", value);
}
private static void startBenchmarkTimer() {
startTime = System.nanoTime();
}
private static double stopBenchmarkTimer() {
long endTime = System.nanoTime();
return (endTime - startTime) / 1_000_000_000.0;
}
private static double calculateIntegerMath() {
final int numIterations = 1_000_000_000;
final int numRuns = 30;
double totalMopsPerSec = 0;
for (int run = 0; run < numRuns; run++) {
long startTime = System.nanoTime();
int result = 0;
for (int i = 0; i < numIterations; i++) {
result += i * 2;
result -= i / 2;
result ^= i;
result <<= 1;
result >>= 1;
}
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0;
totalMopsPerSec += mopsPerSec;
}
double averageMopsPerSec = totalMopsPerSec / numRuns;
return averageMopsPerSec;
}
private static double calculateFloatingPoint() {
long numIterations = 85_000_000;
int numRuns = 30;
double totalMopsPerSec = 0;
for (int run = 0; run < numRuns; run++) {
double result = 0;
long startTime = System.nanoTime();
for (int i = 0; i < numIterations; i++) {
result += Math.sqrt(i) * Math.sin(i) / (i + 1);
}
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double mopsPerSec = (numIterations / elapsedSeconds) / 1_000_000.0;
totalMopsPerSec += mopsPerSec;
}
double averageMopsPerSec = totalMopsPerSec / numRuns;
return averageMopsPerSec;
}
private static double calculatePrimeNumbers() {
int primeCount;
long numIterations = 1_000_000;
int numRuns = 30;
double totalMopsPerSec = 0;
for (int run = 0; run < numRuns; run++) {
primeCount = 0;
long startTime = System.nanoTime();
for (int num = 2; primeCount < numIterations; num++) {
if (isPrime(num)) {
primeCount++;
}
}
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double mopsPerSec = (primeCount / elapsedSeconds) / 1_000_000.0;
totalMopsPerSec += mopsPerSec;
}
double averageMopsPerSec = totalMopsPerSec / numRuns;
return averageMopsPerSec;
}
private static double calculateStringSorting() {
int stringCount = 1_000_000;
int stringLength = 100;
int numRuns = 30;
double totalMopsPerSec = 0;
for (int run = 0; run < numRuns; run++) {
List<String> randomStrings = generateRandomStrings(stringCount, stringLength);
long startTime = System.nanoTime();
randomStrings.sort(String::compareTo);
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double mopsPerSec = (stringCount / elapsedSeconds) / 1_000.0;
totalMopsPerSec += mopsPerSec;
}
double averageMopsPerSec = totalMopsPerSec / numRuns;
return averageMopsPerSec;
}
public static double calculateDataEncryption() {
int dataSizeMB = 100;
byte[] dataToEncrypt = generateRandomData(dataSizeMB * 1024 * 1024);
int numRuns = 20;
double totalMBytesPerSec = 0;
for (int run = 0; run < numRuns; run++) {
long startTime = System.nanoTime();
byte[] encryptedData = performEncryption(dataToEncrypt, 1);
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double mbytesPerSec = (dataToEncrypt.length / (1024 * 1024.0)) / elapsedSeconds;
totalMBytesPerSec += mbytesPerSec;
}
double averageMBytesPerSec = totalMBytesPerSec / numRuns;
return averageMBytesPerSec;
}
private static byte[] performEncryption(byte[] data, int numRuns) {
byte[] key = "MyEncryptionKey".getBytes();
byte[] result = Arrays.copyOf(data, data.length);
for (int run = 0; run < numRuns; run++) {
for (int i = 0; i < result.length; i++) {
result[i] ^= key[i % key.length];
}
}
return result;
}
public static double calculateDataCompression() {
int dataSizeMB = 500;
byte[] dataToCompress = generateRandomData(dataSizeMB * 1024 * 1024);
long startTime = System.nanoTime();
byte[] compressedData = performCompression(dataToCompress);
long endTime = System.nanoTime();
double elapsedSeconds = (endTime - startTime) / 1e9;
double mbytesPerSec = (compressedData.length / (1024.0 * 1024.0)) / elapsedSeconds;
return mbytesPerSec;
}
private static byte[] performCompression(byte[] data) {
Deflater deflater = new Deflater();
deflater.setInput(data);
deflater.finish();
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
byte[] buffer = new byte[1024];
while (!deflater.finished()) {
int count = deflater.deflate(buffer);
outputStream.write(buffer, 0, count);
}
deflater.end();
return outputStream.toByteArray();
}
private static List<String> generateRandomStrings(int count, int length) {
SecureRandom random = new SecureRandom();
List<String> randomStrings = new ArrayList<>();
IntStream.range(0, count).forEach(i -> {
byte[] bytes = new byte[length];
random.nextBytes(bytes);
randomStrings.add(Base64.getEncoder().encodeToString(bytes));
});
return randomStrings;
}
private static byte[] generateRandomData(int size) {
SecureRandom random = new SecureRandom();
byte[] data = new byte[size];
random.nextBytes(data);
return data;
}
private static double roundToTwoDecimalPlaces(double value) {
return Double.parseDouble(String.format("%.2f", value));
}
private static double calculateCPUScore(long elapsedTimeNs) {
return 1.0 / (elapsedTimeNs / 1_000_000.0);
}
public static double calculateDiskSpeed() {
int numRuns = 10;
int fileSizeMB = 1000;
double[] writeSpeeds = new double[numRuns];
double[] readSpeeds = new double[numRuns];
for (int run = 0; run < numRuns; run++) {
long writeStartTime = System.nanoTime();
deleteTestFile(filePath);
createTestFile(filePath, fileSizeMB);
long writeEndTime = System.nanoTime();
long readStartTime = System.nanoTime();
readTestFile(filePath);
long readEndTime = System.nanoTime();
double writeSpeed = calculateDiskSpeedMBps(fileSizeMB, writeStartTime, writeEndTime);
double readSpeed = calculateDiskSpeedMBps(fileSizeMB, readStartTime, readEndTime);
writeSpeeds[run] = writeSpeed;
readSpeeds[run] = readSpeed;
if (run == 0) {
lowestWriteSpeedMBps = writeSpeed;
highestWriteSpeedMBps = writeSpeed;
lowestReadSpeedMBps = readSpeed;
highestReadSpeedMBps = readSpeed;
} else {
if (writeSpeed < lowestWriteSpeedMBps) {
lowestWriteSpeedMBps = writeSpeed;
}
if (writeSpeed > highestWriteSpeedMBps) {
highestWriteSpeedMBps = writeSpeed;
}
if (readSpeed < lowestReadSpeedMBps) {
lowestReadSpeedMBps = readSpeed;
}
if (readSpeed > highestReadSpeedMBps) {
highestReadSpeedMBps = readSpeed;
}
}
}
avgWriteSpeedMBps = calculateAverage(writeSpeeds);
avgReadSpeedMBps = calculateAverage(readSpeeds);
return 2;
}
public static void createTestFile(String filePath, int fileSizeMB) {
try {
File file = new File(filePath);
byte[] data = new byte[1024 * 1024];
Arrays.fill(data, (byte) 0);
FileOutputStream fos = new FileOutputStream(file);
for (int i = 0; i < fileSizeMB; i++) {
fos.write(data);
}
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void readTestFile(String filePath) {
try {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
byte[] buffer = new byte[1024];
while (fis.read(buffer) != -1) {
}
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public static void deleteTestFile(String filePath) {
File file = new File(filePath);
file.delete();
}
public static double calculateDiskSpeedMBps(int fileSizeMB, long startTime, long endTime) {
double elapsedSeconds = (endTime - startTime) / 1_000_000_000.0;
double writeSpeed = (fileSizeMB / elapsedSeconds);
return writeSpeed;
}
public static double calculateAverage(double[] values) {
double sum = 0;
for (double value : values) {
sum += value;
}
return sum / values.length;
}
public static void WindowsDiskSpeedTest() {
try {
String command = "winsat disk";
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
Iris.debug(line);
if (line.contains("Disk Sequential 64.0 Read")) {
avgReadSpeedMBps = extractSpeed(line);
} else if (line.contains("Disk Sequential 64.0 Write")) {
avgWriteSpeedMBps = extractSpeed(line);
}
}
process.waitFor();
process.destroy();
Iris.debug("Sequential Read Speed: " + avgReadSpeedMBps + " MB/s");
Iris.debug("Sequential Write Speed: " + avgWriteSpeedMBps + " MB/s");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private static double extractSpeed(String line) {
String[] tokens = line.split("\\s+");
for (int i = 0; i < tokens.length; i++) {
if (tokens[i].endsWith("MB/s") && i > 0) {
try {
return Double.parseDouble(tokens[i - 1]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
return 0.0;
}
public static void WindowsCpuSpeedTest() {
try {
String command = "winsat cpuformal";
Process process = Runtime.getRuntime().exec(command);
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
Iris.debug(line);
if (line.contains("CPU AES256 Encryption")) {
WindowsCPUEncryption = extractCpuInfo(line);
}
if (line.contains("CPU LZW Compression")) {
WindowsCPUCompression = extractCpuInfo(line);
}
if (line.contains("CPU SHA1 Hash")) {
WindowsCPUCSHA1 = extractCpuInfo(line);
}
}
process.waitFor();
process.destroy();
Iris.debug("Winsat Encryption: " + WindowsCPUEncryption + " MB/s");
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
private static double extractCpuInfo(String line) {
String[] tokens = line.split("\\s+");
for (int i = 0; i < tokens.length; i++) {
if (tokens[i].endsWith("MB/s") && i > 0) {
try {
return Double.parseDouble(tokens[i - 1]);
} catch (NumberFormatException e) {
e.printStackTrace();
}
}
}
return 0.0;
}
}
@@ -146,8 +146,8 @@ public class IrisPackBenchmarking {
IrisToolbelt.pregenerate(PregenTask
.builder()
.gui(gui)
.width(radius)
.height(radius)
.radiusX(radius)
.radiusZ(radius)
.build(), Bukkit.getWorld("benchmark")
);
}
@@ -24,6 +24,7 @@ import com.volmit.iris.core.gui.PregeneratorJob;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.pregenerator.PregenTask;
import com.volmit.iris.core.pregenerator.PregeneratorMethod;
import com.volmit.iris.core.pregenerator.methods.CachedPregenMethod;
import com.volmit.iris.core.pregenerator.methods.HybridPregenMethod;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.engine.framework.Engine;
@@ -141,7 +142,18 @@ public class IrisToolbelt {
* @return the pregenerator job (already started)
*/
public static PregeneratorJob pregenerate(PregenTask task, PregeneratorMethod method, Engine engine) {
return new PregeneratorJob(task, method, engine);
return pregenerate(task, method, engine, true);
}
/**
* Start a pregenerator task
*
* @param task the scheduled task
* @param method the method to execute the task
* @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);
}
/**
@@ -75,6 +75,7 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Nullable;
import java.awt.Color;
import java.util.Arrays;
@@ -852,6 +853,25 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
return getBiomeOrMantle(l.getBlockX(), l.getBlockY(), l.getBlockZ());
}
@Nullable
@BlockCoordinates
default Position2 getNearestStronghold(Position2 pos) {
KList<Position2> p = getDimension().getStrongholds(getSeedManager().getMantle());
if (p.isEmpty()) return null;
Position2 pr = null;
double d = Double.MAX_VALUE;
for (Position2 i : p) {
double dx = i.distance(pos);
if (dx < d) {
d = dx;
pr = i;
}
}
return pr;
}
default void gotoBiome(IrisBiome biome, Player player, boolean teleport) {
Set<String> regionKeys = getDimension()
.getAllRegions(this).stream()
@@ -872,31 +892,10 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat
default void gotoJigsaw(IrisJigsawStructure s, Player player, boolean teleport) {
if (s.getLoadKey().equals(getDimension().getStronghold())) {
KList<Position2> p = getDimension().getStrongholds(getSeedManager().getMantle());
if (p.isEmpty()) {
Position2 pr = getNearestStronghold(new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ()));
if (pr == null) {
player.sendMessage(C.GOLD + "No strongholds in world.");
}
Position2 px = new Position2(player.getLocation().getBlockX(), player.getLocation().getBlockZ());
Position2 pr = null;
double d = Double.MAX_VALUE;
Iris.debug("Ps: " + p.size());
for (Position2 i : p) {
Iris.debug("- " + i.getX() + " " + i.getZ());
}
for (Position2 i : p) {
double dx = i.distance(px);
if (dx < d) {
d = dx;
pr = i;
}
}
if (pr != null) {
} else {
Location ll = new Location(player.getWorld(), pr.getX(), 40, pr.getZ());
J.s(() -> player.teleport(ll));
}
@@ -97,51 +97,6 @@ public abstract class EngineAssignedWorldManager extends EngineAssignedComponent
}
}
@EventHandler
public void onItemUse(PlayerInteractEvent e) {
if (e.getItem() == null || e.getHand() != EquipmentSlot.HAND) {
return;
}
if (e.getAction() == Action.LEFT_CLICK_BLOCK || e.getAction() == Action.LEFT_CLICK_AIR) {
return;
}
if (e.getPlayer().getWorld().equals(getTarget().getWorld().realWorld()) && e.getItem().getType() == Material.ENDER_EYE) {
if (e.getClickedBlock() != null && e.getClickedBlock().getType() == Material.END_PORTAL_FRAME) {
return;
}
KList<Position2> positions = getEngine().getDimension().getStrongholds(getEngine().getSeedManager().getMantle());
if (positions.isEmpty()) {
return;
}
Position2 playerPos = new Position2(e.getPlayer().getLocation().getBlockX(), e.getPlayer().getLocation().getBlockZ());
Position2 pr = positions.get(0);
double d = pr.distance(playerPos);
for (Position2 pos : positions) {
double distance = pos.distance(playerPos);
if (distance < d) {
d = distance;
pr = pos;
}
}
if (e.getPlayer().getGameMode() != GameMode.CREATIVE) {
if (e.getItem().getAmount() > 1) {
e.getPlayer().getInventory().getItemInMainHand().setAmount(e.getItem().getAmount() - 1);
} else {
e.getPlayer().getInventory().setItemInMainHand(null);
}
}
EnderSignal eye = e.getPlayer().getWorld().spawn(e.getPlayer().getLocation().clone().add(0, 0.5F, 0), EnderSignal.class);
eye.setTargetLocation(new Location(e.getPlayer().getWorld(), pr.getX(), 40, pr.getZ()));
eye.getWorld().playSound(eye, Sound.ENTITY_ENDER_EYE_LAUNCH, 1, 1);
Iris.debug("ESignal: " + eye.getTargetLocation().getBlockX() + " " + eye.getTargetLocation().getBlockX());
}
}
@EventHandler
public void on(WorldUnloadEvent e) {
if (e.getWorld().equals(getTarget().getWorld().realWorld())) {
@@ -2,11 +2,15 @@ package com.volmit.iris.engine.object;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.misc.ServerProperties;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldInitEvent;
@@ -15,15 +19,12 @@ import java.util.List;
import static com.volmit.iris.Iris.instance;
public class IrisContextInjector implements Listener {
@Getter
private static boolean missingDimensionTypes = false;
private AutoClosing autoClosing = null;
private final int totalWorlds;
private int worldCounter = 0;
public IrisContextInjector() {
if (!Bukkit.getWorlds().isEmpty()) {
totalWorlds = 0;
return;
}
if (!Bukkit.getWorlds().isEmpty()) return;
String levelName = ServerProperties.LEVEL_NAME;
List<String> irisWorlds = irisWorlds();
@@ -31,22 +32,20 @@ public class IrisContextInjector implements Listener {
boolean nether = irisWorlds.contains(levelName + "_nether");
boolean end = irisWorlds.contains(levelName + "_end");
int i = 1;
if (Bukkit.getAllowNether()) i++;
if (Bukkit.getAllowEnd()) i++;
if (INMS.get().missingDimensionTypes(overworld, nether, end)) {
missingDimensionTypes = true;
return;
}
if (overworld || nether || end) {
var pair = INMS.get().injectUncached(overworld, nether, end);
i += pair.getA() - 3;
autoClosing = pair.getB();
autoClosing = INMS.get().injectUncached(overworld, nether, end);
}
totalWorlds = i;
instance.registerListener(this);
}
@EventHandler
@EventHandler(priority = EventPriority.LOWEST)
public void on(WorldInitEvent event) {
if (++worldCounter < totalWorlds) return;
if (autoClosing != null) {
autoClosing.close();
autoClosing = null;
@@ -21,6 +21,7 @@ package com.volmit.iris.engine.platform;
import com.volmit.iris.Iris;
import com.volmit.iris.core.loader.IrisData;
import com.volmit.iris.core.nms.INMS;
import com.volmit.iris.core.nms.container.AutoClosing;
import com.volmit.iris.core.service.StudioSVC;
import com.volmit.iris.engine.IrisEngine;
import com.volmit.iris.engine.data.chunk.TerrainChunk;
@@ -48,6 +49,7 @@ import org.bukkit.block.data.BlockData;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.world.WorldInitEvent;
import org.bukkit.generator.BiomeProvider;
@@ -122,11 +124,13 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun
}
}
@EventHandler
@EventHandler(priority = EventPriority.LOWEST)
public void onWorldInit(WorldInitEvent event) {
try {
if (initialized || !world.name().equals(event.getWorld().getName()))
return;
AutoClosing.closeContext();
INMS.get().removeCustomDimensions(event.getWorld());
world.setRawWorldSeed(event.getWorld().getSeed());
Engine engine = getEngine(event.getWorld());
if (engine == null) {
@@ -26,6 +26,8 @@ import com.volmit.iris.util.math.RNG;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collector;
import java.util.stream.Collectors;
@SuppressWarnings("ALL")
public class KList<T> extends ArrayList<T> implements List<T> {
@@ -65,6 +67,10 @@ public class KList<T> extends ArrayList<T> implements List<T> {
return s;
}
public static <T> Collector<T, ?, KList<T>> collector() {
return Collectors.toCollection(KList::new);
}
public static KList<String> asStringList(List<?> oo) {
KList<String> s = new KList<String>();
@@ -63,7 +63,7 @@ public class Spiraler {
public void next() {
if ((-sizeX / 2 <= x) && (x <= sizeX / 2) && (-sizeZ / 2 <= z) && (z <= sizeZ / 2)) {
spiraled.on(x + ox, z + ox);
spiraled.on(x + ox, z + oz);
}
if ((x == z) || ((x < 0) && (x == -z)) || ((x > 0) && (x == 1 - z))) {
@@ -10,7 +10,7 @@ public class ServerProperties {
public static final File SERVER_PROPERTIES;
public static final File BUKKIT_YML;
public static final String LEVEL_NAME = DATA.getProperty("level-name", "world");
public static final String LEVEL_NAME;
static {
String[] args = ProcessHandle.current()
@@ -20,11 +20,13 @@ public class ServerProperties {
String propertiesPath = "server.properties";
String bukkitYml = "bukkit.yml";
String levelName = null;
for (int i = 0; i < args.length - 1; i++) {
switch (args[i]) {
case "-c", "--config" -> propertiesPath = args[i + 1];
case "-b", "--bukkit-settings" -> bukkitYml = args[i + 1];
case "-w", "--level-name", "--world" -> levelName = args[i + 1];
}
}
@@ -35,5 +37,8 @@ public class ServerProperties {
} catch (IOException e) {
throw new RuntimeException(e);
}
if (levelName != null) LEVEL_NAME = levelName;
else LEVEL_NAME = DATA.getProperty("level-name", "world");
}
}
@@ -24,12 +24,14 @@ import com.volmit.iris.core.service.PreservationSVC;
import com.volmit.iris.util.collection.KList;
import com.volmit.iris.util.math.M;
import com.volmit.iris.util.scheduling.PrecisionStopwatch;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
public class MultiBurst {
public class MultiBurst implements ExecutorService {
public static final MultiBurst burst = new MultiBurst();
private final AtomicLong last;
private final String name;
@@ -144,8 +146,86 @@ public class MultiBurst {
return getService().submit(o);
}
@Override
public void shutdown() {
close();
}
@NotNull
@Override
public List<Runnable> shutdownNow() {
close();
return List.of();
}
@Override
public boolean isShutdown() {
return service == null || service.isShutdown();
}
@Override
public boolean isTerminated() {
return service == null || service.isTerminated();
}
@Override
public boolean awaitTermination(long timeout, @NotNull TimeUnit unit) throws InterruptedException {
return service == null || service.awaitTermination(timeout, unit);
}
@Override
public void execute(@NotNull Runnable command) {
getService().execute(command);
}
@NotNull
@Override
public <T> Future<T> submit(@NotNull Callable<T> task) {
return getService().submit(task);
}
@NotNull
@Override
public <T> Future<T> submit(@NotNull Runnable task, T result) {
return getService().submit(task, result);
}
@NotNull
@Override
public Future<?> submit(@NotNull Runnable task) {
return getService().submit(task);
}
@NotNull
@Override
public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException {
return getService().invokeAll(tasks);
}
@NotNull
@Override
public <T> List<Future<T>> invokeAll(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException {
return getService().invokeAll(tasks, timeout, unit);
}
@NotNull
@Override
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
return getService().invokeAny(tasks);
}
@Override
public <T> T invokeAny(@NotNull Collection<? extends Callable<T>> tasks, long timeout, @NotNull TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return getService().invokeAny(tasks, timeout, unit);
}
public void close() {
if (service != null) {
close(service);
}
}
public static void close(ExecutorService service) {
service.shutdown();
PrecisionStopwatch p = PrecisionStopwatch.start();
try {
@@ -169,4 +249,3 @@ public class MultiBurst {
}
}
}
}
@@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
@@ -122,6 +123,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -38,7 +38,9 @@ import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityDimensions;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@@ -47,6 +49,9 @@ import net.minecraft.world.level.chunk.ChunkStatus;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -640,50 +645,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new com.volmit.iris.core.nms.container.Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -692,7 +655,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -700,26 +668,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().K.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.registryOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()))),
levelStems.getOrThrow(key).generator()
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), Lifecycle.stable());
}
@@ -732,4 +740,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return new ResourceLocation("iris", key.location().getPath());
}
}
@@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
@@ -122,6 +123,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -28,9 +28,14 @@ import net.minecraft.server.WorldLoader;
import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -641,50 +646,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new com.volmit.iris.core.nms.container.Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -693,7 +656,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -701,26 +669,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().K.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.registryOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()))),
levelStems.getOrThrow(key).generator()
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), Lifecycle.stable());
}
@@ -733,4 +741,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return new ResourceLocation("iris", key.location().getPath());
}
}
@@ -125,6 +125,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -28,9 +28,14 @@ import net.minecraft.server.WorldLoader;
import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.tags.TagKey;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -642,50 +647,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new com.volmit.iris.core.nms.container.Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -694,7 +657,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -702,26 +670,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().K.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.registryOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.getHolderOrThrow(ResourceKey.create(Registries.DIMENSION_TYPE, new ResourceLocation("iris", key.location().getPath()))),
levelStems.getOrThrow(key).generator()
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), Lifecycle.stable());
}
@@ -734,4 +742,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return new ResourceLocation("iris", key.location().getPath());
}
}
@@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
@@ -120,6 +121,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -38,11 +38,16 @@ import net.minecraft.server.level.ServerLevel;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -667,50 +672,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new com.volmit.iris.core.nms.container.Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -719,7 +682,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -727,26 +695,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().K.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.registryOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.getHolder(new ResourceLocation("iris", key.location().getPath())).orElseThrow(),
levelStems.getOrThrow(key).generator()
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), RegistrationInfo.BUILT_IN);
}
@@ -759,4 +767,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return new ResourceLocation("iris", key.location().getPath());
}
}
@@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
@@ -121,6 +122,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -32,11 +32,16 @@ import net.minecraft.server.level.ChunkMap;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.WorldGenContext;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -671,50 +676,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public com.volmit.iris.core.nms.container.Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new com.volmit.iris.core.nms.container.Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -723,7 +686,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -731,26 +699,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.registryOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().registryOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().K.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.registryOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.registryOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.registry(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.registryOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.getHolder(ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath())).orElseThrow(),
levelStems.getOrThrow(key).generator()
dimensions.getHolder(ResourceKey.create(Registries.DIMENSION_TYPE, loc)).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), RegistrationInfo.BUILT_IN);
}
@@ -763,4 +771,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
}
}
@@ -23,6 +23,7 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
@@ -120,6 +121,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -27,11 +27,16 @@ import net.minecraft.server.commands.data.BlockDataAccessor;
import net.minecraft.tags.TagKey;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.WorldGenContext;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -661,50 +666,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -713,7 +676,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -721,26 +689,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().L.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.lookupOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.get(ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath())).orElseThrow(),
levelStems.getValueOrThrow(key).generator()
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), RegistrationInfo.BUILT_IN);
}
@@ -753,4 +761,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
}
}
@@ -20,9 +20,11 @@ import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.level.WorldGenRegion;
import net.minecraft.tags.StructureTags;
import net.minecraft.tags.TagKey;
import net.minecraft.util.random.WeightedRandomList;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.item.EnderEyeItem;
import net.minecraft.world.level.*;
import net.minecraft.world.level.biome.*;
import net.minecraft.world.level.chunk.ChunkAccess;
@@ -31,6 +33,7 @@ import net.minecraft.world.level.chunk.ChunkGeneratorStructureState;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.blending.Blender;
import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureTemplateManager;
@@ -114,6 +117,11 @@ public class IrisChunkGenerator extends CustomChunkGenerator {
@Override
public @Nullable Pair<BlockPos, Holder<Structure>> findNearestMapStructure(ServerLevel level, HolderSet<Structure> holders, BlockPos pos, int radius, boolean findUnexplored) {
if (holders.size() == 0) return null;
if (holders.unwrapKey().orElse(null) == StructureTags.EYE_OF_ENDER_LOCATED) {
var next = engine.getNearestStronghold(new Position2(pos.getX(), pos.getZ()));
return next == null ? null : new Pair<>(new BlockPos(next.getX(), 0, next.getZ()), holders.get(0));
}
if (engine.getDimension().isDisableExplorerMaps())
return null;
@@ -39,7 +39,9 @@ import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.item.component.CustomData;
import net.minecraft.world.level.LevelReader;
import net.minecraft.world.level.biome.Biomes;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.state.BlockState;
@@ -49,6 +51,9 @@ import net.minecraft.world.level.chunk.status.ChunkStatus;
import net.minecraft.world.level.chunk.status.WorldGenContext;
import net.minecraft.world.level.dimension.DimensionType;
import net.minecraft.world.level.dimension.LevelStem;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.flat.FlatLayerInfo;
import net.minecraft.world.level.levelgen.flat.FlatLevelGeneratorSettings;
import org.bukkit.*;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
@@ -660,50 +665,8 @@ public class NMSBinding implements INMSBinding {
}
@Override
@SneakyThrows
public AutoClosing injectLevelStems() {
return inject(this::supplier);
}
@Override
@SneakyThrows
public Pair<Integer, AutoClosing> injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
AutoClosing closing = inject(old -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), true, overworld, nether, end)
)
);
var injected = ((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions().lookupOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new Pair<>(
injected.size(),
new AutoClosing(() -> {
closing.close();
field.set(reg, old);
}));
}
private WorldLoader.DataLoadContext supplier(WorldLoader.DataLoadContext old) {
return dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
));
}
@SneakyThrows
private AutoClosing inject(Function<WorldLoader.DataLoadContext, WorldLoader.DataLoadContext> transformer) {
if (!dataContextLock.tryLock()) throw new IllegalStateException("Failed to inject data context!");
var server = ((CraftServer) Bukkit.getServer());
@@ -712,7 +675,12 @@ public class NMSBinding implements INMSBinding {
var old = nmsServer.worldLoader;
field.setAccessible(true);
field.set(nmsServer, transformer.apply(old));
field.set(nmsServer, dataLoadContext.aquire(() -> new WorldLoader.DataLoadContext(
old.resources(),
old.dataConfiguration(),
old.datapackWorldgen(),
createRegistryAccess(old.datapackDimensions(), false, true, true, true)
)));
return new AutoClosing(() -> {
field.set(nmsServer, old);
@@ -720,26 +688,66 @@ public class NMSBinding implements INMSBinding {
});
}
@Override
@SneakyThrows
public AutoClosing injectUncached(boolean overworld, boolean nether, boolean end) {
var reg = registry();
var field = getField(RegistryAccess.ImmutableRegistryAccess.class, Map.class);
field.setAccessible(true);
var access = createRegistryAccess(((CraftServer) Bukkit.getServer()).getServer().worldLoader.datapackDimensions(), true, overworld, nether, end);
var injected = access.lookupOrThrow(Registries.LEVEL_STEM);
var old = (Map<ResourceKey<? extends Registry<?>>, Registry<?>>) field.get(reg);
var fake = new HashMap<>(old);
fake.put(Registries.LEVEL_STEM, injected);
field.set(reg, fake);
return new AutoClosing(() -> field.set(reg, old));
}
@Override
public boolean missingDimensionTypes(boolean overworld, boolean nether, boolean end) {
var registry = registry().lookupOrThrow(Registries.DIMENSION_TYPE);
if (overworld) overworld = !registry.containsKey(createIrisKey(LevelStem.OVERWORLD));
if (nether) nether = !registry.containsKey(createIrisKey(LevelStem.NETHER));
if (end) end = !registry.containsKey(createIrisKey(LevelStem.END));
return overworld || nether || end;
}
@Override
public void removeCustomDimensions(World world) {
((CraftWorld) world).getHandle().L.customDimensions = null;
}
private RegistryAccess.Frozen createRegistryAccess(RegistryAccess.Frozen datapack, boolean copy, boolean overworld, boolean nether, boolean end) {
var access = registry();
var dimensions = access.lookupOrThrow(Registries.DIMENSION_TYPE);
var levelStems = access.lookupOrThrow(Registries.LEVEL_STEM);
var settings = new FlatLevelGeneratorSettings(
Optional.empty(),
access.lookupOrThrow(Registries.BIOME).getOrThrow(Biomes.THE_VOID),
List.of()
);
settings.getLayersInfo().add(new FlatLayerInfo(1, Blocks.AIR));
settings.updateLayers();
var source = new FlatLevelSource(settings);
var fake = new MappedRegistry<>(Registries.LEVEL_STEM, Lifecycle.experimental());
if (overworld) register(fake, levelStems, dimensions, LevelStem.OVERWORLD);
if (nether) register(fake, levelStems, dimensions, LevelStem.NETHER);
if (end) register(fake, levelStems, dimensions, LevelStem.END);
if (overworld) register(fake, dimensions, source, LevelStem.OVERWORLD);
if (nether) register(fake, dimensions, source, LevelStem.NETHER);
if (end) register(fake, dimensions, source, LevelStem.END);
copy(fake, datapack.lookup(Registries.LEVEL_STEM).orElse(null));
if (copy) copy(fake, levelStems);
if (copy) copy(fake, access.lookupOrThrow(Registries.LEVEL_STEM));
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake.freeze())).freeze();
return new RegistryAccess.Frozen.ImmutableRegistryAccess(List.of(fake)).freeze();
}
private void register(MappedRegistry<LevelStem> target, Registry<LevelStem> levelStems, Registry<DimensionType> dimensions, ResourceKey<LevelStem> key) {
private void register(MappedRegistry<LevelStem> target, Registry<DimensionType> dimensions, FlatLevelSource source, ResourceKey<LevelStem> key) {
var loc = createIrisKey(key);
target.register(key, new LevelStem(
dimensions.get(ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath())).orElseThrow(),
levelStems.getValueOrThrow(key).generator()
dimensions.get(loc).orElseThrow(() -> new IllegalStateException("Missing dimension type " + loc + " in " + dimensions.keySet())),
source
), RegistrationInfo.BUILT_IN);
}
@@ -752,4 +760,8 @@ public class NMSBinding implements INMSBinding {
target.register(key, value, info);
});
}
private ResourceLocation createIrisKey(ResourceKey<LevelStem> key) {
return ResourceLocation.fromNamespaceAndPath("iris", key.location().getPath());
}
}