diff --git a/build.gradle.kts b/build.gradle.kts index dab32c3ac..bb954ed12 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,3 @@ -import com.volmit.nmstools.NMSToolsExtension -import com.volmit.nmstools.NMSToolsPlugin import de.undercouch.gradle.tasks.download.Download import xyz.jpenilla.runpaper.task.RunServer import kotlin.system.exitProcess @@ -30,7 +28,6 @@ buildscript { plugins { java `java-library` - alias(libs.plugins.shadow) alias(libs.plugins.download) alias(libs.plugins.runPaper) } @@ -58,13 +55,15 @@ registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins") registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins") // ============================================================== -val serverMinHeap = "2G" -val serverMaxHeap = "8G" +val serverMinHeap = "10G" +val serverMaxHeap = "10G" +val additionalFlags = "-XX:+AlwaysPreTouch" //Valid values are: none, truecolor, indexed256, indexed16, indexed8 val color = "truecolor" val errorReporting = findProperty("errorReporting") as Boolean? ?: false val nmsBindings = mapOf( + "v1_21_R6" to "1.21.10-R0.1-SNAPSHOT", "v1_21_R5" to "1.21.8-R0.1-SNAPSHOT", "v1_21_R4" to "1.21.5-R0.1-SNAPSHOT", "v1_21_R3" to "1.21.4-R0.1-SNAPSHOT", @@ -76,14 +75,14 @@ val nmsBindings = mapOf( "v1_20_R1" to "1.20.1-R0.1-SNAPSHOT", ) val jvmVersion = mapOf() -nmsBindings.forEach { key, value -> +nmsBindings.forEach { (key, value) -> project(":nms:$key") { apply() - apply() - extensions.configure(NMSToolsExtension::class) { + nmsBinding { jvm = jvmVersion.getOrDefault(key, 21) version = value + type = NMSBinding.Type.DIRECT } dependencies { @@ -106,17 +105,23 @@ nmsBindings.forEach { key, value -> systemProperty("com.mojang.eula.agree", true) systemProperty("iris.suppressReporting", !errorReporting) jvmArgs("-javaagent:${project(":core:agent").tasks.jar.flatMap { it.archiveFile }.get().asFile.absolutePath}") + jvmArgs(additionalFlags.split(' ')) } } +val jarJar: Configuration by configurations.creating +dependencies { + for (key in nmsBindings.keys) { + implementation(project(":nms:$key", "reobf")) + } + implementation(project(":core", "shadow")) + jarJar(project(":core:agent")) +} + tasks { jar { duplicatesStrategy = DuplicatesStrategy.EXCLUDE - nmsBindings.forEach { key, _ -> - from(project(":nms:$key").tasks.named("remap").map { zipTree(it.outputs.files.singleFile) }) - } - from(project(":core").tasks.shadowJar.flatMap { it.archiveFile }.map { zipTree(it) }) - from(project(":core:agent").tasks.jar.flatMap { it.archiveFile }) + from(jarJar, configurations.runtimeClasspath.map { it.resolve().map(::zipTree) }) archiveFileName.set("Iris-${project.version}.jar") } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index c94238170..192a8ab77 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,11 +1,16 @@ plugins { - kotlin("jvm") version "2.0.20" + kotlin("jvm") version embeddedKotlinVersion } repositories { mavenCentral() + gradlePluginPortal() + maven("https://jitpack.io") } dependencies { implementation("org.ow2.asm:asm:9.8") + implementation("com.github.VolmitSoftware:NMSTools:c88961416f") + implementation("io.papermc.paperweight:paperweight-userdev:2.0.0-beta.18") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") } \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/NMSBinding.kt b/buildSrc/src/main/kotlin/NMSBinding.kt new file mode 100644 index 000000000..937f98dde --- /dev/null +++ b/buildSrc/src/main/kotlin/NMSBinding.kt @@ -0,0 +1,182 @@ +import NMSBinding.Type +import com.volmit.nmstools.NMSToolsExtension +import com.volmit.nmstools.NMSToolsPlugin +import io.papermc.paperweight.userdev.PaperweightUser +import io.papermc.paperweight.userdev.PaperweightUserDependenciesExtension +import io.papermc.paperweight.userdev.PaperweightUserExtension +import io.papermc.paperweight.userdev.attribute.Obfuscation +import io.papermc.paperweight.util.constants.REOBF_CONFIG +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import org.gradle.api.* +import org.gradle.api.attributes.Bundling +import org.gradle.api.attributes.Category +import org.gradle.api.attributes.LibraryElements +import org.gradle.api.attributes.Usage +import org.gradle.api.model.ObjectFactory +import org.gradle.api.plugins.JavaPluginExtension +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.extensions.core.extra +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.gradle.jvm.toolchain.JavaToolchainService +import org.gradle.work.DisableCachingByDefault +import java.io.RandomAccessFile +import javax.inject.Inject + +class NMSBinding : Plugin { + override fun apply(target: Project): Unit = with(target) { + val config = extra["nms"] as? Config ?: throw GradleException("No NMS binding configuration found") + val jvm = config.jvm + val type = config.type + + if (type == Type.USER_DEV) { + plugins.apply(PaperweightUser::class.java) + dependencies.extensions.findByType(PaperweightUserDependenciesExtension::class.java) + ?.paperDevBundle(config.version) + + val java = extensions.findByType(JavaPluginExtension::class.java) ?: throw GradleException("Java plugin not found") + java.toolchain.languageVersion.set(JavaLanguageVersion.of(jvm)) + + val javaToolchains = project.extensions.getByType(JavaToolchainService::class.java) ?: throw GradleException("Java toolchain service not found") + extensions.configure(PaperweightUserExtension::class.java) { + it.javaLauncher.set(javaToolchains.launcherFor(java.toolchain)) + } + } else { + extra["nmsTools.useBuildTools"] = type == Type.BUILD_TOOLS + plugins.apply(NMSToolsPlugin::class.java) + extensions.configure(NMSToolsExtension::class.java) { + it.jvm.set(jvm) + it.version.set(config.version) + } + + configurations.register(REOBF_CONFIG) { conf -> + conf.isCanBeConsumed = true + conf.isCanBeResolved = false + conf.attributes { + it.attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage.JAVA_RUNTIME)) + it.attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.LIBRARY)) + it.attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, objects.named(LibraryElements.JAR)) + it.attribute(Bundling.BUNDLING_ATTRIBUTE, objects.named(Bundling.EXTERNAL)) + it.attribute(Obfuscation.OBFUSCATION_ATTRIBUTE, objects.named(Obfuscation.OBFUSCATED)) + } + conf.outgoing.artifact(tasks.named("remap")) + } + } + + val (major, minor) = config.version.parseVersion() + if (major <= 20 && minor <= 4) return@with + tasks.register("convert", ConversionTask::class.java, type) + tasks.named("compileJava") { it.dependsOn("convert") } + rootProject.tasks.named("prepareKotlinBuildScriptModel") { it.dependsOn("$path:convert") } + } + + @DisableCachingByDefault + abstract class ConversionTask @Inject constructor(type: Type) : DefaultTask() { + private val pattern: Regex + private val replacement: String + + init { + group = "nms" + inputs.property("type", type) + val java = project.extensions.findByType(JavaPluginExtension::class.java) ?: throw GradleException("Java plugin not found") + val source = java.sourceSets.named("main").map { it.allJava } + inputs.files(source) + outputs.files(source) + + if (type == Type.USER_DEV) { + pattern = "org\\.bukkit\\.craftbukkit\\.${project.name}".toRegex() + replacement = "org.bukkit.craftbukkit" + } else { + pattern = "org\\.bukkit\\.craftbukkit\\.(?!${project.name})".toRegex() + replacement = "org.bukkit.craftbukkit.${project.name}." + } + } + + @TaskAction + fun process() { + val dispatcher = Dispatchers.IO.limitedParallelism(16) + runBlocking { + for (file in inputs.files) { + if (file.extension !in listOf("java")) + continue + + launch(dispatcher) { + val output = ArrayList() + var changed = false + + file.bufferedReader().use { + for (line in it.lines()) { + if (line.startsWith("package") || line.isBlank()) { + output += line + continue + } + + if (!line.startsWith("import")) { + if (!changed) return@launch + else { + output += line + continue + } + } + + if (!line.contains(pattern)) { + output += line + continue + } + + output += line.replace(pattern, replacement) + changed = true + } + } + + if (changed) { + RandomAccessFile(file, "r").use { raf -> + val bytes = ByteArray(NEW_LINE_BYTES.size) + raf.seek(raf.length() - bytes.size) + raf.readFully(bytes) + if (bytes.contentEquals(NEW_LINE_BYTES)) + output += "" + } + + file.writer().use { + val iterator = output.iterator() + while (iterator.hasNext()) { + it.append(iterator.next()) + if (iterator.hasNext()) + it.append(NEW_LINE) + } + } + } + } + } + } + } + } + + enum class Type { + USER_DEV, + BUILD_TOOLS, + DIRECT, + } +} + +private val NEW_LINE = System.lineSeparator() +private val NEW_LINE_BYTES = NEW_LINE.encodeToByteArray() +private fun String.parseVersion() = substringBefore('-').split(".").let { + it[1].toInt() to it[2].toInt() +} + +class Config( + var jvm: Int = 21, + var type: Type = Type.DIRECT +) { + lateinit var version: String +} + +fun Project.nmsBinding(action: Config.() -> Unit) { + extra["nms"] = Config().apply(action) + plugins.apply(NMSBinding::class.java) +} + +private inline fun ObjectFactory.named(name: String): T = named(T::class.java, name) \ No newline at end of file diff --git a/core/build.gradle.kts b/core/build.gradle.kts index 8e4894bd4..b695ade78 100644 --- a/core/build.gradle.kts +++ b/core/build.gradle.kts @@ -142,6 +142,7 @@ slimJar { relocate("com.google.inject", "$lib.guice") relocate("org.dom4j", "$lib.dom4j") relocate("org.jaxen", "$lib.jaxen") + relocate("com.github.benmanes.caffeine", "$lib.caffeine") } tasks { diff --git a/core/src/main/java/com/volmit/iris/Iris.java b/core/src/main/java/com/volmit/iris/Iris.java index 14607fdba..9b782a5ed 100644 --- a/core/src/main/java/com/volmit/iris/Iris.java +++ b/core/src/main/java/com/volmit/iris/Iris.java @@ -58,6 +58,7 @@ import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.VolmitPlugin; import com.volmit.iris.util.plugin.VolmitSender; +import com.volmit.iris.util.plugin.chunk.ChunkTickets; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.Queue; import com.volmit.iris.util.scheduling.ShurikenQueue; @@ -95,6 +96,7 @@ public class Iris extends VolmitPlugin implements Listener { public static MultiverseCoreLink linkMultiverseCore; public static IrisCompat compat; public static FileWatcher configWatcher; + public static ChunkTickets tickets; private static VolmitSender sender; private static Thread shutdownHook; @@ -450,6 +452,7 @@ public class Iris extends VolmitPlugin implements Listener { IrisSafeguard.IrisSafeguardSystem(); getSender().setTag(getTag()); IrisSafeguard.splash(true); + tickets = new ChunkTickets(); linkMultiverseCore = new MultiverseCoreLink(); configWatcher = new FileWatcher(getDataFile("settings.json")); services.values().forEach(IrisService::onEnable); diff --git a/core/src/main/java/com/volmit/iris/core/IrisSettings.java b/core/src/main/java/com/volmit/iris/core/IrisSettings.java index 212b1551f..4a613db89 100644 --- a/core/src/main/java/com/volmit/iris/core/IrisSettings.java +++ b/core/src/main/java/com/volmit/iris/core/IrisSettings.java @@ -159,7 +159,7 @@ public class IrisSettings { private IrisSettingsEngineSVC engineSVC = new IrisSettingsEngineSVC(); public boolean trimMantleInStudio = false; public int mantleKeepAlive = 30; - public int cacheSize = 4_096; + public int noiseCacheSize = 1_024; public int resourceLoaderCacheSize = 1_024; public int objectLoaderCacheSize = 4_096; public int scriptLoaderCacheSize = 512; @@ -177,6 +177,9 @@ public class IrisSettings { @Data public static class IrisSettingsUpdater { public int maxConcurrency = 256; + public boolean nativeThreads = false; + public double threadMultiplier = 2; + public double chunkLoadSensitivity = 0.7; public MsRange emptyMsRange = new MsRange(80, 100); public MsRange defaultMsRange = new MsRange(20, 40); @@ -185,6 +188,10 @@ public class IrisSettings { return Math.max(Math.abs(maxConcurrency), 1); } + public double getThreadMultiplier() { + return Math.min(Math.abs(threadMultiplier), 0.1); + } + public double getChunkLoadSensitivity() { return Math.min(chunkLoadSensitivity, 0.9); } @@ -243,6 +250,7 @@ public class IrisSettings { public int maxBiomeChildDepth = 4; public boolean preventLeafDecay = true; public boolean useMulticore = false; + public boolean useMulticoreMantle = false; public boolean offsetNoiseTypes = false; public boolean earlyCustomBlocks = false; } diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java index fd057b300..9786c3ec4 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandObject.java @@ -38,7 +38,6 @@ import com.volmit.iris.util.decree.specialhandlers.ObjectHandler; import com.volmit.iris.util.format.C; import com.volmit.iris.util.math.Direction; import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.scheduling.Queue; import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.data.BlockData; @@ -79,9 +78,9 @@ public class CommandObject implements DecreeExecutor { futureBlockChanges.put(block, block.getBlockData()); if (d instanceof IrisCustomData data) { - block.setBlockData(data.getBase()); + block.setBlockData(data.getBase(), false); Iris.warn("Tried to place custom block at " + x + ", " + y + ", " + z + " which is not supported!"); - } else block.setBlockData(d); + } else block.setBlockData(d, false); } @Override @@ -124,6 +123,16 @@ public class CommandObject implements DecreeExecutor { tile.toBukkitTry(world.getBlockAt(xx, yy, zz)); } + @Override + public void setData(int xx, int yy, int zz, T data) { + + } + + @Override + public T getData(int xx, int yy, int zz, Class t) { + return null; + } + @Override public Engine getEngine() { return null; @@ -140,7 +149,7 @@ public class CommandObject implements DecreeExecutor { sender().sendMessage("Object Size: " + o.getW() + " * " + o.getH() + " * " + o.getD() + ""); sender().sendMessage("Blocks Used: " + NumberFormat.getIntegerInstance().format(o.getBlocks().size())); - Queue queue = o.getBlocks().enqueueValues(); + var queue = o.getBlocks().values(); Map> unsorted = new HashMap<>(); Map amounts = new HashMap<>(); Map materials = new HashMap<>(); diff --git a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java index 555ce4f48..001eea7a2 100644 --- a/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/com/volmit/iris/core/commands/CommandStudio.java @@ -58,7 +58,7 @@ import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.J; import com.volmit.iris.util.scheduling.O; import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import com.volmit.iris.util.scheduling.jobs.ParallelQueueJob; +import com.volmit.iris.util.scheduling.jobs.ParallelRadiusJob; import io.papermc.lib.PaperLib; import org.bukkit.*; import org.bukkit.event.inventory.InventoryType; @@ -78,6 +78,7 @@ import java.util.Arrays; import java.util.Date; import java.util.Objects; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; @@ -174,56 +175,49 @@ public class CommandStudio implements DecreeExecutor { PlatformChunkGenerator plat = IrisToolbelt.access(world); Engine engine = plat.getEngine(); DecreeContext.touch(sender); - try (SyncExecutor executor = new SyncExecutor(20)) { + try (SyncExecutor executor = new SyncExecutor(20); + var service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()) + ) { int x = loc.getBlockX() >> 4; int z = loc.getBlockZ() >> 4; int rad = engine.getMantle().getRadius(); var mantle = engine.getMantle().getMantle(); var chunkMap = new KMap(); - ParallelQueueJob prep = new ParallelQueueJob<>() { + ParallelRadiusJob prep = new ParallelRadiusJob(Integer.MAX_VALUE, service) { @Override - public void execute(Position2 pos) { - var cpos = pos.add(x, z); - if (Math.abs(pos.getX()) <= radius && Math.abs(pos.getZ()) <= radius) { - mantle.deleteChunk(cpos.getX(), cpos.getZ()); + protected void execute(int rX, int rZ) { + if (Math.abs(rX) <= radius && Math.abs(rZ) <= radius) { + mantle.deleteChunk(rX + x, rZ + z); return; } - chunkMap.put(cpos, mantle.getChunk(cpos.getX(), cpos.getZ())); - mantle.deleteChunk(cpos.getX(), cpos.getZ()); + rX += x; + rZ += z; + chunkMap.put(new Position2(rX, rZ), mantle.getChunk(rX, rZ)); + mantle.deleteChunk(rX, rZ); } @Override public String getName() { return "Preparing Mantle"; } - }; - for (int xx = -(radius + rad); xx <= radius + rad; xx++) { - for (int zz = -(radius + rad); zz <= radius + rad; zz++) { - prep.queue(new Position2(xx, zz)); - } - } + }.retarget(radius + rad, 0, 0); CountDownLatch pLatch = new CountDownLatch(1); prep.execute(sender(), pLatch::countDown); pLatch.await(); - ParallelQueueJob job = new ParallelQueueJob<>() { + ParallelRadiusJob job = new ParallelRadiusJob(Integer.MAX_VALUE, service) { @Override - public void execute(Position2 p) { - plat.injectChunkReplacement(world, p.getX(), p.getZ(), executor); + protected void execute(int x, int z) { + plat.injectChunkReplacement(world, x, z, executor); } @Override public String getName() { return "Regenerating"; } - }; - for (int i = -radius; i <= radius; i++) { - for (int j = -radius; j <= radius; j++) { - job.queue(new Position2(i + x, j + z)); - } - } + }.retarget(radius, x, z); CountDownLatch latch = new CountDownLatch(1); job.execute(sender(), latch::countDown); latch.await(); @@ -339,11 +333,15 @@ public class CommandStudio implements DecreeExecutor { O ta = new O<>(); ta.set(-1); + var sender = sender(); + var player = player(); + var engine = engine(); + ta.set(Bukkit.getScheduler().scheduleSyncRepeatingTask(Iris.instance, () -> { - if (!player().getOpenInventory().getType().equals(InventoryType.CHEST)) { + if (!player.getOpenInventory().getType().equals(InventoryType.CHEST)) { Bukkit.getScheduler().cancelTask(ta.get()); - sender().sendMessage(C.GREEN + "Opened inventory!"); + sender.sendMessage(C.GREEN + "Opened inventory!"); return; } @@ -351,7 +349,7 @@ public class CommandStudio implements DecreeExecutor { inv.clear(); } - engine().addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player().getWorld(), player().getLocation().getBlockX(), player().getLocation().getBlockY(), player().getLocation().getBlockZ(), 1); + engine.addItems(true, inv, new RNG(RNG.r.imax()), tables, InventorySlotType.STORAGE, player.getWorld(), player.getLocation().getBlockX(), player.getLocation().getBlockY(), player.getLocation().getBlockZ(), 1); }, 0, fast ? 5 : 35)); sender().sendMessage(C.GREEN + "Opening inventory now!"); diff --git a/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java index bbf2cd197..eb17801d7 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/HMCLeavesDataProvider.java @@ -67,7 +67,7 @@ public class HMCLeavesDataProvider extends ExternalDataProvider { BlockData blockData = Bukkit.createBlockData(material); if (IrisSettings.get().getGenerator().preventLeafDecay && blockData instanceof Leaves leaves) leaves.setPersistent(true); - return new IrisCustomData(blockData, ExternalDataSVC.buildState(blockId, state)); + return IrisCustomData.of(blockData, ExternalDataSVC.buildState(blockId, state)); } @NotNull diff --git a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java index 64ad7e52a..3dbdb0dbe 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/ItemAdderDataProvider.java @@ -47,7 +47,7 @@ public class ItemAdderDataProvider extends ExternalDataProvider { if (block == null) { throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); } - return new IrisCustomData(block.getBaseBlockData(), blockId); + return IrisCustomData.of(block.getBaseBlockData(), blockId); } @NotNull diff --git a/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java index ea4f6e4ef..acb2b47d6 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/KGeneratorsDataProvider.java @@ -33,7 +33,7 @@ public class KGeneratorsDataProvider extends ExternalDataProvider { @Override public @NotNull BlockData getBlockData(@NotNull Identifier blockId, @NotNull KMap state) throws MissingResourceException { if (Main.getGenerators().get(blockId.key()) == null) throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - return new IrisCustomData(Material.STRUCTURE_VOID.createBlockData(), ExternalDataSVC.buildState(blockId, state)); + return IrisCustomData.of(Material.STRUCTURE_VOID.createBlockData(), ExternalDataSVC.buildState(blockId, state)); } @Override diff --git a/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java index 86d2ce327..b264e144a 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/MythicCrucibleDataProvider.java @@ -72,7 +72,7 @@ public class MythicCrucibleDataProvider extends ExternalDataProvider { CustomBlockItemContext blockItemContext = crucibleItem.getBlockData(); FurnitureItemContext furnitureItemContext = crucibleItem.getFurnitureData(); if (furnitureItemContext != null) { - return new IrisCustomData(B.getAir(), ExternalDataSVC.buildState(blockId, state)); + return IrisCustomData.of(B.getAir(), ExternalDataSVC.buildState(blockId, state)); } else if (blockItemContext != null) { return blockItemContext.getBlockData(); } diff --git a/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java index a0029c320..4eeff9a31 100644 --- a/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java +++ b/core/src/main/java/com/volmit/iris/core/link/data/NexoDataProvider.java @@ -49,9 +49,9 @@ public class NexoDataProvider extends ExternalDataProvider { BlockData data = NexoBlocks.blockData(blockId.key()); if (data == null) throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); - return new IrisCustomData(data, blockState); + return IrisCustomData.of(data, blockState); } else if (NexoFurniture.isFurniture(blockId.key())) { - return new IrisCustomData(B.getAir(), blockState); + return IrisCustomData.of(B.getAir(), blockState); } throw new MissingResourceException("Failed to find BlockData!", blockId.namespace(), blockId.key()); diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java index aaf1a49de..97215ef20 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisData.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisData.java @@ -50,16 +50,15 @@ import java.io.FileReader; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.Objects; -import java.util.Optional; +import java.util.*; @Data public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private static final KMap dataLoaders = new KMap<>(); private final File dataFolder; private final int id; - private final PackEnvironment environment; private boolean closed = false; + private PackEnvironment environment; private ResourceLoader biomeLoader; private ResourceLoader lootLoader; private ResourceLoader regionLoader; @@ -92,7 +91,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.engine = null; this.dataFolder = dataFolder; this.id = RNG.r.imax(); - this.environment = PackEnvironment.create(this); hotloaded(); } @@ -350,7 +348,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { public synchronized void hotloaded() { closed = false; - environment.close(); possibleSnippets = new KMap<>(); builder = new GsonBuilder() .addDeserializationExclusionStrategy(this) @@ -382,6 +379,7 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.imageLoader = registerLoader(IrisImage.class); this.scriptLoader = registerLoader(IrisScript.class); this.matterObjectLoader = registerLoader(IrisMatterObject.class); + this.environment = PackEnvironment.create(this); builder.registerTypeAdapterFactory(KeyedType::createTypeAdapter); gson = builder.create(); @@ -389,6 +387,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { .map(IrisDimension::getDataScripts) .flatMap(KList::stream) .forEach(environment::execute); + + if (engine != null) { + engine.hotload(); + } } public void dump() { @@ -404,6 +406,33 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { possibleSnippets.clear(); } + public Set> resolveSnippets() { + var result = new HashSet>(); + var processed = new HashSet>(); + var excluder = gson.excluder(); + + var queue = new LinkedList>(loaders.keySet()); + while (!queue.isEmpty()) { + var type = queue.poll(); + if (excluder.excludeClass(type, false) || !processed.add(type)) + continue; + if (type.isAnnotationPresent(Snippet.class)) + result.add(type); + + try { + for (var field : type.getDeclaredFields()) { + if (excluder.excludeField(field, false)) + continue; + + queue.add(field.getType()); + } + } catch (Throwable ignored) { + } + } + + return result; + } + public String toLoadKey(File f) { if (f.getPath().startsWith(getDataFolder().getPath())) { String[] full = f.getPath().split("\\Q" + File.separator + "\\E"); diff --git a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java index b20ac02d4..123fd6f2e 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java +++ b/core/src/main/java/com/volmit/iris/core/loader/IrisRegistrant.java @@ -35,7 +35,7 @@ import java.io.File; @Data public abstract class IrisRegistrant { - @Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'object' and modify properties about this object before it's used.\nFile extension: .prox.kts") + @Desc("Preprocess this object in-memory when it's loaded, run scripts using the variable 'object' and modify properties about this object before it's used.\nFile extension: .proc.kts") @RegistryListResource(IrisScript.class) @ArrayType(min = 1, type = String.class) private KList preprocessors = new KList<>(); diff --git a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java index 25d37642d..6e06a9772 100644 --- a/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java +++ b/core/src/main/java/com/volmit/iris/core/loader/ResourceLoader.java @@ -46,6 +46,7 @@ import lombok.ToString; import java.io.*; import java.util.Arrays; +import java.util.HashSet; import java.util.Locale; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; @@ -170,7 +171,6 @@ public class ResourceLoader implements MeteredCache { return possibleKeys; } - KSet m = new KSet<>(); KList files = getFolders(); if (files == null) { @@ -178,6 +178,7 @@ public class ResourceLoader implements MeteredCache { return possibleKeys; } + HashSet m = new HashSet<>(); for (File i : files) { for (File j : matchAllFiles(i, (f) -> f.getName().endsWith(".json"))) { m.add(i.toURI().relativize(j.toURI()).getPath().replaceAll("\\Q.json\\E", "")); @@ -319,7 +320,8 @@ public class ResourceLoader implements MeteredCache { return null; } - firstAccess.add(name); + var set = firstAccess; + if (set != null) firstAccess.add(name); return loadCache.get(name); } @@ -342,21 +344,24 @@ public class ResourceLoader implements MeteredCache { } din.close(); - file.deleteOnExit(); Iris.info("Loading " + s.size() + " prefetch " + getFolderName()); + firstAccess = null; loadAllParallel(s); } public void saveFirstAccess(Engine engine) throws IOException { + if (firstAccess == null) return; String id = "DIM" + Math.abs(engine.getSeedManager().getSeed() + engine.getDimension().getVersion() + engine.getDimension().getLoadKey().hashCode()); File file = Iris.instance.getDataFile("prefetch/" + id + "/" + Math.abs(getFolderName().hashCode()) + ".ipfch"); file.getParentFile().mkdirs(); FileOutputStream fos = new FileOutputStream(file); GZIPOutputStream gzo = new CustomOutputStream(fos, 9); DataOutputStream dos = new DataOutputStream(gzo); - dos.writeInt(firstAccess.size()); + var set = firstAccess; + firstAccess = null; + dos.writeInt(set.size()); - for (String i : firstAccess) { + for (String i : set) { dos.writeUTF(i); } diff --git a/core/src/main/java/com/volmit/iris/core/nms/INMS.java b/core/src/main/java/com/volmit/iris/core/nms/INMS.java index e4d6bc682..6e1543cd8 100644 --- a/core/src/main/java/com/volmit/iris/core/nms/INMS.java +++ b/core/src/main/java/com/volmit/iris/core/nms/INMS.java @@ -28,9 +28,10 @@ import java.util.List; public class INMS { private static final Version CURRENT = Boolean.getBoolean("iris.no-version-limit") ? new Version(Integer.MAX_VALUE, Integer.MAX_VALUE, null) : - new Version(21, 8, null); + new Version(21, 10, null); private static final List REVISION = List.of( + new Version(21, 9, "v1_21_R6"), new Version(21, 6, "v1_21_R5"), new Version(21, 5, "v1_21_R4"), new Version(21, 4, "v1_21_R3"), diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java index bce79233c..1dbd26172 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/ChunkUpdater.java @@ -2,16 +2,15 @@ package com.volmit.iris.core.pregenerator; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.service.PreservationSVC; import com.volmit.iris.core.tools.IrisToolbelt; -import com.volmit.iris.engine.data.cache.Cache; import com.volmit.iris.engine.framework.Engine; -import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.M; import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RollingSequence; +import com.volmit.iris.util.plugin.chunk.TicketHolder; import com.volmit.iris.util.profile.LoadBalancer; import com.volmit.iris.util.scheduling.J; import io.papermc.lib.PaperLib; @@ -21,7 +20,6 @@ import org.bukkit.World; import java.io.File; -import java.util.ArrayList; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; @@ -31,7 +29,7 @@ public class ChunkUpdater { private static final String REGION_PATH = "region" + File.separator + "r."; private final AtomicBoolean paused = new AtomicBoolean(); private final AtomicBoolean cancelled = new AtomicBoolean(); - private final KMap> lastUse = new KMap<>(); + private final TicketHolder holder; private final RollingSequence chunksPerSecond = new RollingSequence(5); private final AtomicInteger totalMaxChunks = new AtomicInteger(); private final AtomicInteger chunksProcessed = new AtomicInteger(); @@ -40,13 +38,13 @@ public class ChunkUpdater { private final AtomicBoolean serverEmpty = new AtomicBoolean(true); private final AtomicLong lastCpsTime = new AtomicLong(M.ms()); private final int maxConcurrency = IrisSettings.get().getUpdater().getMaxConcurrency(); + private final int coreLimit = (int) Math.max(Runtime.getRuntime().availableProcessors() * IrisSettings.get().getUpdater().getThreadMultiplier(), 1); private final Semaphore semaphore = new Semaphore(maxConcurrency); private final LoadBalancer loadBalancer = new LoadBalancer(semaphore, maxConcurrency, IrisSettings.get().getUpdater().emptyMsRange); private final AtomicLong startTime = new AtomicLong(); private final Dimensions dimensions; private final PregenTask task; - private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - private final ExecutorService chunkExecutor = Executors.newVirtualThreadPerTaskExecutor(); + private final ExecutorService chunkExecutor = IrisSettings.get().getUpdater().isNativeThreads() ? Executors.newFixedThreadPool(coreLimit) : Executors.newVirtualThreadPerTaskExecutor(); private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); private final CountDownLatch latch; private final Engine engine; @@ -55,6 +53,7 @@ public class ChunkUpdater { public ChunkUpdater(World world) { this.engine = IrisToolbelt.access(world).getEngine(); this.world = world; + this.holder = Iris.tickets.getHolder(world); this.dimensions = calculateWorldDimensions(new File(world.getWorldFolder(), "region")); this.task = dimensions.task(); this.totalMaxChunks.set(dimensions.count * 1024); @@ -113,7 +112,6 @@ public class ChunkUpdater { e.printStackTrace(); } }, 0, 3, TimeUnit.SECONDS); - scheduler.scheduleAtFixedRate(this::unloadChunks, 0, 1, TimeUnit.SECONDS); scheduler.scheduleAtFixedRate(() -> { boolean empty = Bukkit.getOnlinePlayers().isEmpty(); if (serverEmpty.getAndSet(empty) == empty) @@ -128,6 +126,7 @@ public class ChunkUpdater { t.setPriority(Thread.MAX_PRIORITY); t.start(); + Iris.service(PreservationSVC.class).register(t); } catch (Exception e) { e.printStackTrace(); } @@ -140,8 +139,6 @@ public class ChunkUpdater { chunkExecutor.shutdown(); chunkExecutor.awaitTermination(5, TimeUnit.SECONDS); - executor.shutdown(); - executor.awaitTermination(5, TimeUnit.SECONDS); scheduler.shutdownNow(); unloadAndSaveAllChunks(); } catch (Exception ignored) {} @@ -200,20 +197,16 @@ public class ChunkUpdater { return; } + var mc = engine.getMantle().getMantle().getChunk(x, z).use(); try { Chunk c = world.getChunkAt(x, z); - engine.getMantle().getMantle().getChunk(c); engine.updateChunk(c); - for (int xx = -1; xx <= 1; xx++) { - for (int zz = -1; zz <= 1; zz++) { - var counter = lastUse.get(Cache.key(x + xx, z + zz)); - if (counter != null) counter.getB().decrementAndGet(); - } - } + removeTickets(x, z); } finally { chunksUpdated.incrementAndGet(); chunksProcessed.getAndIncrement(); + mc.release(); } } @@ -235,41 +228,16 @@ public class ChunkUpdater { for (int dz = -1; dz <= 1; dz++) { int xx = x + dx; int zz = z + dz; - executor.submit(() -> { - try { - Chunk c; - try { - c = PaperLib.getChunkAtAsync(world, xx, zz, false, true) - .thenApply(chunk -> { - if (chunk != null) - chunk.addPluginChunkTicket(Iris.instance); - return chunk; - }).get(); - } catch (InterruptedException | ExecutionException e) { - generated.set(false); - return; - } - - if (c == null) { - generated.set(false); - return; - } - - if (!c.isLoaded()) { - var future = J.sfut(() -> c.load(false)); - if (future != null) future.join(); - } - - if (!PaperLib.isChunkGenerated(c.getWorld(), xx, zz)) - generated.set(false); - - var pair = lastUse.computeIfAbsent(Cache.key(c), k -> new Pair<>(0L, new AtomicInteger(-1))); - pair.setA(M.ms()); - pair.getB().updateAndGet(i -> i == -1 ? 1 : ++i); - } finally { - latch.countDown(); - } - }); + PaperLib.getChunkAtAsync(world, xx, zz, false, true) + .thenAccept(chunk -> { + if (chunk == null || !chunk.isGenerated()) { + latch.countDown(); + generated.set(false); + return; + } + holder.addTicket(chunk); + latch.countDown(); + }); } } @@ -278,27 +246,16 @@ public class ChunkUpdater { } catch (InterruptedException e) { Iris.info("Interrupted while waiting for chunks to load"); } - return generated.get(); + + if (generated.get()) return true; + removeTickets(x, z); + return false; } - private synchronized void unloadChunks() { - for (var key : new ArrayList<>(lastUse.keySet())) { - if (key == null) continue; - var pair = lastUse.get(key); - if (pair == null) continue; - var lastUseTime = pair.getA(); - var counter = pair.getB(); - if (lastUseTime == null || counter == null) - continue; - - if (M.ms() - lastUseTime >= 5000 && counter.get() == 0) { - int x = Cache.keyX(key); - int z = Cache.keyZ(key); - J.s(() -> { - world.removePluginChunkTicket(x, z, Iris.instance); - world.unloadChunk(x, z); - lastUse.remove(key); - }); + private void removeTickets(int x, int z) { + for (int xx = -1; xx <= 1; xx++) { + for (int zz = -1; zz <= 1; zz++) { + holder.removeTicket(x + xx, z + zz); } } } @@ -311,7 +268,6 @@ public class ChunkUpdater { return; } - unloadChunks(); world.save(); }).get(); } catch (Throwable e) { diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java index dff3840cd..e085975c2 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCache.java @@ -24,9 +24,11 @@ public interface PregenCache { void write(); + void trim(long unloadDuration); + static PregenCache create(File directory) { if (directory == null) return EMPTY; - return new PregenCacheImpl(directory); + return new PregenCacheImpl(directory, 16); } default PregenCache sync() { @@ -51,19 +53,16 @@ public interface PregenCache { } @Override - public void cacheChunk(int x, int z) { - - } + public void cacheChunk(int x, int z) {} @Override - public void cacheRegion(int x, int z) { - - } + public void cacheRegion(int x, int z) {} @Override - public void write() { + public void write() {} - } + @Override + public void trim(long unloadDuration) {} }; diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java deleted file mode 100644 index d124b25bc..000000000 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.java +++ /dev/null @@ -1,220 +0,0 @@ -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.github.benmanes.caffeine.cache.Scheduler; -import com.volmit.iris.Iris; -import com.volmit.iris.util.data.KCache; -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.io.IO; -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 java.io.*; -import java.util.concurrent.TimeUnit; -import java.util.function.Predicate; - -@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 cache = Caffeine.newBuilder() - .expireAfterAccess(10, TimeUnit.SECONDS) - .executor(KCache.EXECUTOR) - .scheduler(Scheduler.systemScheduler()) - .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); - Iris.reportError(e); - 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 { - IO.write(file, out -> new DataOutputStream(new LZ4BlockOutputStream(out)), plate::write); - } catch (IOException e) { - Iris.error("Failed to write pregen cache " + file); - Iris.reportError(e); - 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 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 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) {} -} diff --git a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java index 52f3b7774..18efefa43 100644 --- a/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java +++ b/core/src/main/java/com/volmit/iris/core/pregenerator/cache/SynchronizedCache.java @@ -1,11 +1,6 @@ package com.volmit.iris.core.pregenerator.cache; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -class SynchronizedCache implements PregenCache { - private final PregenCache cache; - +record SynchronizedCache(PregenCache cache) implements PregenCache { @Override public boolean isThreadSafe() { return true; @@ -45,4 +40,11 @@ class SynchronizedCache implements PregenCache { cache.write(); } } + + @Override + public void trim(long unloadDuration) { + synchronized (cache) { + cache.trim(unloadDuration); + } + } } diff --git a/core/src/main/java/com/volmit/iris/core/project/Gradle.java b/core/src/main/java/com/volmit/iris/core/project/Gradle.java index c28b28ca4..565e7d39f 100644 --- a/core/src/main/java/com/volmit/iris/core/project/Gradle.java +++ b/core/src/main/java/com/volmit/iris/core/project/Gradle.java @@ -9,9 +9,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.URI; -import java.util.HashMap; -import java.util.Optional; -import java.util.Scanner; +import java.util.*; public class Gradle { private static final boolean WINDOWS = System.getProperty("os.name").toLowerCase().contains("win"); @@ -38,10 +36,15 @@ public class Gradle { cmd[0] = gradle.getAbsolutePath(); System.arraycopy(args, 0, cmd, 1, args.length); var process = Runtime.getRuntime().exec(cmd, ENVIRONMENT, projectDir); - attach(process.getInputStream()); - attach(process.getErrorStream()); + var lines = Collections.synchronizedList(new ArrayList()); + attach(process.getInputStream(), lines); + attach(process.getErrorStream(), lines); var code = process.waitFor(); - if (code == 0) return; + if (code == 0) { + lines.forEach(Iris::debug); + return; + } + lines.forEach(Iris::error); throw new RuntimeException("Gradle exited with code " + code); } @@ -91,12 +94,12 @@ public class Gradle { .orElseThrow(() -> new RuntimeException("Failed to find java home, please set java.home system property")); } - private static void attach(InputStream stream) { - Thread.ofVirtual().start(() -> { + private static void attach(InputStream stream, List list) { + Thread.ofPlatform().start(() -> { try (var in = new Scanner(stream)) { while (in.hasNextLine()) { String line = in.nextLine(); - Iris.debug("[GRADLE] " + line); + list.add(line); } } }); diff --git a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java index ad5a42bff..7e753f5c0 100644 --- a/core/src/main/java/com/volmit/iris/core/project/IrisProject.java +++ b/core/src/main/java/com/volmit/iris/core/project/IrisProject.java @@ -24,7 +24,6 @@ import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.loader.IrisRegistrant; import com.volmit.iris.core.loader.ResourceLoader; -import com.volmit.iris.core.scripting.environment.SimpleEnvironment; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.object.*; import com.volmit.iris.engine.object.annotations.Snippet; @@ -326,7 +325,7 @@ public class IrisProject { } } - for (Class i : Iris.getClasses("com.volmit.iris.engine.object.", Snippet.class)) { + for (Class i : dm.resolveSnippets()) { try { String snipType = i.getDeclaredAnnotation(Snippet.class).value(); JSONObject o = new JSONObject(); diff --git a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java index 7e61233f3..5c4373452 100644 --- a/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java +++ b/core/src/main/java/com/volmit/iris/core/project/SchemaBuilder.java @@ -38,6 +38,7 @@ import org.jetbrains.annotations.NotNull; import java.awt.*; import java.lang.reflect.Field; +import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; @@ -117,49 +118,13 @@ public class SchemaBuilder { JSONArray required = new JSONArray(); JSONArray extended = new JSONArray(); - if (c.isAssignableFrom(IrisRegistrant.class) || IrisRegistrant.class.isAssignableFrom(c)) { - for (Field k : IrisRegistrant.class.getDeclaredFields()) { - k.setAccessible(true); - - if (Modifier.isStatic(k.getModifiers()) || Modifier.isFinal(k.getModifiers()) || Modifier.isTransient(k.getModifiers())) { - continue; - } - - JSONObject property = buildProperty(k, c); - - if (Boolean.TRUE == property.remove("!required")) { - required.put(k.getName()); - } - - if (Boolean.TRUE == property.remove("!top")) { - extended.put(property); - continue; - } - - properties.put(k.getName(), property); - } + var parent = c.getSuperclass(); + while (parent != null && IrisRegistrant.class.isAssignableFrom(parent)) { + buildProperties(properties, required, extended, parent); + parent = parent.getSuperclass(); } - for (Field k : c.getDeclaredFields()) { - k.setAccessible(true); - - if (Modifier.isStatic(k.getModifiers()) || Modifier.isFinal(k.getModifiers()) || Modifier.isTransient(k.getModifiers())) { - continue; - } - - JSONObject property = buildProperty(k, c); - - if (Boolean.TRUE == property.remove("!required")) { - required.put(k.getName()); - } - - if (Boolean.TRUE == property.remove("!top")) { - extended.put(property); - continue; - } - - properties.put(k.getName(), property); - } + buildProperties(properties, required, extended, c); if (required.length() > 0) { o.put("required", required); @@ -174,6 +139,33 @@ public class SchemaBuilder { return buildSnippet(o, c); } + private void buildProperties(JSONObject properties, JSONArray required, JSONArray extended, Class c) { + for (Field k : c.getDeclaredFields()) { + if (Modifier.isStatic(k.getModifiers()) || Modifier.isFinal(k.getModifiers()) || Modifier.isTransient(k.getModifiers())) { + continue; + } + + try { + k.setAccessible(true); + } catch (InaccessibleObjectException e) { + continue; + } + + JSONObject property = buildProperty(k, c); + + if (Boolean.TRUE == property.remove("!top")) { + extended.put(property); + continue; + } + + if (Boolean.TRUE == property.remove("!required")) { + required.put(k.getName()); + } + + properties.put(k.getName(), property); + } + } + private JSONObject buildProperty(Field k, Class cl) { JSONObject prop = new JSONObject(); String type = getType(k.getType()); @@ -616,7 +608,7 @@ public class SchemaBuilder { if (present) d.add(" "); if (value instanceof List) { d.add(SYMBOL_LIMIT__N + " Default Value is an empty list"); - } else if (!cl.isPrimitive() && !(value instanceof Number) && !(value instanceof String) && !(cl.isEnum()) && !KeyedType.isKeyed(cl)) { + } else if (!k.getType().isPrimitive() && !(value instanceof Number) && !(value instanceof String) && !(value instanceof Enum) && !KeyedType.isKeyed(k.getType())) { d.add(SYMBOL_LIMIT__N + " Default Value is a default object (create this object to see default properties)"); } else { d.add(SYMBOL_LIMIT__N + " Default Value is " + value); diff --git a/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java b/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java index c5dbccf22..4f0dfd6bb 100644 --- a/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java +++ b/core/src/main/java/com/volmit/iris/core/scripting/environment/PackEnvironment.java @@ -2,6 +2,7 @@ package com.volmit.iris.core.scripting.environment; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.core.scripting.kotlin.environment.IrisPackExecutionEnvironment; +import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.util.math.RNG; import lombok.NonNull; import org.jetbrains.annotations.Nullable; @@ -16,4 +17,6 @@ public interface PackEnvironment extends SimpleEnvironment { @Nullable Object createNoise(@NonNull String script, @NonNull RNG rng); + + EngineEnvironment with(@NonNull Engine engine); } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java b/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java index 4eb7e0ce8..6d543cdca 100644 --- a/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java +++ b/core/src/main/java/com/volmit/iris/core/scripting/environment/SimpleEnvironment.java @@ -27,8 +27,4 @@ public interface SimpleEnvironment { @Nullable Object evaluate(@NonNull String script, @NonNull Class type, @Nullable Map<@NonNull String, Object> vars); - - default void close() { - - } } \ No newline at end of file diff --git a/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java index ceeedbee7..4d6e2db76 100644 --- a/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/GlobalCacheSVC.java @@ -1,13 +1,11 @@ package com.volmit.iris.core.service; -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.github.benmanes.caffeine.cache.Scheduler; import com.volmit.iris.core.IrisSettings; import com.volmit.iris.core.pregenerator.cache.PregenCache; +import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.plugin.IrisService; +import com.volmit.iris.util.scheduling.Looper; import lombok.NonNull; import org.bukkit.Bukkit; import org.bukkit.World; @@ -19,21 +17,33 @@ import org.bukkit.event.world.WorldUnloadEvent; import org.jetbrains.annotations.Nullable; import java.io.File; +import java.lang.ref.Reference; +import java.lang.ref.WeakReference; import java.util.function.Function; public class GlobalCacheSVC implements IrisService { - private static final Cache REFERENCE_CACHE = Caffeine.newBuilder() - .executor(KCache.EXECUTOR) - .scheduler(Scheduler.systemScheduler()) - .weakValues() - .build(); + private static final KMap> REFERENCE_CACHE = new KMap<>(); private final KMap globalCache = new KMap<>(); private transient boolean lastState; private static boolean disabled = true; + private Looper trimmer; @Override public void onEnable() { disabled = false; + trimmer = new Looper() { + @Override + protected long loop() { + var it = REFERENCE_CACHE.values().iterator(); + while (it.hasNext()) { + var cache = it.next().get(); + if (cache == null) it.remove(); + else cache.trim(10_000); + } + return disabled ? -1 : 2_000; + } + }; + trimmer.start(); lastState = !IrisSettings.get().getWorld().isGlobalPregenCache(); if (lastState) return; Bukkit.getWorlds().forEach(this::createCache); @@ -42,6 +52,9 @@ public class GlobalCacheSVC implements IrisService { @Override public void onDisable() { disabled = true; + try { + trimmer.join(); + } catch (InterruptedException ignored) {} globalCache.qclear((world, cache) -> cache.write()); } @@ -76,6 +89,7 @@ public class GlobalCacheSVC implements IrisService { } private void createCache(World world) { + if (!IrisToolbelt.isIrisWorld(world)) return; globalCache.computeIfAbsent(world.getName(), GlobalCacheSVC::createDefault); } @@ -99,7 +113,15 @@ public class GlobalCacheSVC implements IrisService { @NonNull public static PregenCache createCache(@NonNull String worldName, @NonNull Function provider) { - return REFERENCE_CACHE.get(worldName, provider); + PregenCache[] holder = new PregenCache[1]; + REFERENCE_CACHE.compute(worldName, (name, ref) -> { + if (ref != null) { + if ((holder[0] = ref.get()) != null) + return ref; + } + return new WeakReference<>(holder[0] = provider.apply(worldName)); + }); + return holder[0]; } @NonNull diff --git a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java index a9b31244d..36e4b6d98 100644 --- a/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/IrisEngineSVC.java @@ -3,7 +3,7 @@ package com.volmit.iris.core.service; import com.google.common.util.concurrent.AtomicDouble; import com.volmit.iris.Iris; import com.volmit.iris.core.IrisSettings; -import com.volmit.iris.core.loader.IrisData; +import com.volmit.iris.core.loader.ResourceLoader; import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.engine.framework.Engine; import com.volmit.iris.engine.platform.PlatformChunkGenerator; @@ -14,6 +14,8 @@ import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.plugin.VolmitSender; import com.volmit.iris.util.scheduling.Looper; +import com.volmit.iris.util.stream.utility.CachedStream2D; +import com.volmit.iris.util.stream.utility.CachedStream3D; import lombok.Synchronized; import org.bukkit.Bukkit; import org.bukkit.World; @@ -27,6 +29,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; public class IrisEngineSVC implements IrisService { + private static final int TRIM_PERIOD = 2_000; private final AtomicInteger tectonicLimit = new AtomicInteger(30); private final AtomicInteger tectonicPlates = new AtomicInteger(); private final AtomicInteger queuedTectonicPlates = new AtomicInteger(); @@ -64,10 +67,26 @@ public class IrisEngineSVC implements IrisService { } public void engineStatus(VolmitSender sender) { + long[] sizes = new long[4]; + long[] count = new long[4]; + + for (var cache : Iris.service(PreservationSVC.class).getCaches()) { + var type = switch (cache) { + case ResourceLoader ignored -> 0; + case CachedStream2D ignored -> 1; + case CachedStream3D ignored -> 2; + default -> 3; + }; + + sizes[type] += cache.getSize(); + count[type]++; + } + sender.sendMessage(C.DARK_PURPLE + "-------------------------"); sender.sendMessage(C.DARK_PURPLE + "Status:"); sender.sendMessage(C.DARK_PURPLE + "- Service: " + C.LIGHT_PURPLE + (service.isShutdown() ? "Shutdown" : "Running")); sender.sendMessage(C.DARK_PURPLE + "- Updater: " + C.LIGHT_PURPLE + (updateTicker.isAlive() ? "Running" : "Stopped")); + sender.sendMessage(C.DARK_PURPLE + "- Period: " + C.LIGHT_PURPLE + Form.duration(TRIM_PERIOD)); sender.sendMessage(C.DARK_PURPLE + "- Trimmers: " + C.LIGHT_PURPLE + trimmerAlive.get()); sender.sendMessage(C.DARK_PURPLE + "- Unloaders: " + C.LIGHT_PURPLE + unloaderAlive.get()); sender.sendMessage(C.DARK_PURPLE + "Tectonic Plates:"); @@ -76,10 +95,14 @@ public class IrisEngineSVC implements IrisService { sender.sendMessage(C.DARK_PURPLE + "- Queued: " + C.LIGHT_PURPLE + queuedTectonicPlates.get()); sender.sendMessage(C.DARK_PURPLE + "- Max Idle Duration: " + C.LIGHT_PURPLE + Form.duration(maxIdleDuration.get(), 2)); sender.sendMessage(C.DARK_PURPLE + "- Min Idle Duration: " + C.LIGHT_PURPLE + Form.duration(minIdleDuration.get(), 2)); + sender.sendMessage(C.DARK_PURPLE + "Caches:"); + sender.sendMessage(C.DARK_PURPLE + "- Resource: " + C.LIGHT_PURPLE + sizes[0] + " (" + count[0] + ")"); + sender.sendMessage(C.DARK_PURPLE + "- 2D Stream: " + C.LIGHT_PURPLE + sizes[1] + " (" + count[1] + ")"); + sender.sendMessage(C.DARK_PURPLE + "- 3D Stream: " + C.LIGHT_PURPLE + sizes[2] + " (" + count[2] + ")"); + sender.sendMessage(C.DARK_PURPLE + "- Other: " + C.LIGHT_PURPLE + sizes[3] + " (" + count[3] + ")"); sender.sendMessage(C.DARK_PURPLE + "Other:"); sender.sendMessage(C.DARK_PURPLE + "- Iris Worlds: " + C.LIGHT_PURPLE + totalWorlds.get()); sender.sendMessage(C.DARK_PURPLE + "- Loaded Chunks: " + C.LIGHT_PURPLE + loadedChunks.get()); - sender.sendMessage(C.DARK_PURPLE + "- Cache Size: " + C.LIGHT_PURPLE + Form.f(IrisData.cacheSize())); sender.sendMessage(C.DARK_PURPLE + "-------------------------"); } @@ -113,12 +136,12 @@ public class IrisEngineSVC implements IrisService { @Override protected long loop() { try { - queuedTectonicPlates.set(0); - tectonicPlates.set(0); - loadedChunks.set(0); - unloaderAlive.set(0); - trimmerAlive.set(0); - totalWorlds.set(0); + int queuedPlates = 0; + int totalPlates = 0; + long chunks = 0; + int unloaders = 0; + int trimmers = 0; + int iris = 0; double maxDuration = Long.MIN_VALUE; double minDuration = Long.MAX_VALUE; @@ -126,23 +149,30 @@ public class IrisEngineSVC implements IrisService { var registered = entry.getValue(); if (registered.closed) continue; - totalWorlds.incrementAndGet(); - unloaderAlive.addAndGet(registered.unloaderAlive() ? 1 : 0); - trimmerAlive.addAndGet(registered.trimmerAlive() ? 1 : 0); + iris++; + if (registered.unloaderAlive()) unloaders++; + if (registered.trimmerAlive()) trimmers++; var engine = registered.getEngine(); if (engine == null) continue; - queuedTectonicPlates.addAndGet((int) engine.getMantle().getUnloadRegionCount()); - tectonicPlates.addAndGet(engine.getMantle().getLoadedRegionCount()); - loadedChunks.addAndGet(entry.getKey().getLoadedChunks().length); + queuedPlates += engine.getMantle().getUnloadRegionCount(); + totalPlates += engine.getMantle().getLoadedRegionCount(); + chunks += entry.getKey().getLoadedChunks().length; double duration = engine.getMantle().getAdjustedIdleDuration(); if (duration > maxDuration) maxDuration = duration; if (duration < minDuration) minDuration = duration; } + + trimmerAlive.set(trimmers); + unloaderAlive.set(unloaders); + tectonicPlates.set(totalPlates); + queuedTectonicPlates.set(queuedPlates); maxIdleDuration.set(maxDuration); minIdleDuration.set(minDuration); + loadedChunks.set(chunks); + totalWorlds.set(iris); worlds.values().forEach(Registered::update); } catch (Throwable e) { @@ -157,7 +187,7 @@ public class IrisEngineSVC implements IrisService { private final class Registered { private final String name; private final PlatformChunkGenerator access; - private final int offset = RNG.r.nextInt(1000); + private final int offset = RNG.r.nextInt(TRIM_PERIOD); private transient ScheduledFuture trimmer; private transient ScheduledFuture unloader; private transient boolean closed; @@ -194,7 +224,7 @@ public class IrisEngineSVC implements IrisService { Iris.error("EngineSVC: Failed to trim for " + name); e.printStackTrace(); } - }, offset, 2000, TimeUnit.MILLISECONDS); + }, offset, TRIM_PERIOD, TimeUnit.MILLISECONDS); } if (unloader == null || unloader.isDone() || unloader.isCancelled()) { @@ -214,7 +244,7 @@ public class IrisEngineSVC implements IrisService { Iris.error("EngineSVC: Failed to unload for " + name); e.printStackTrace(); } - }, offset + 1000, 2000, TimeUnit.MILLISECONDS); + }, offset + TRIM_PERIOD / 2, TRIM_PERIOD, TimeUnit.MILLISECONDS); } } diff --git a/core/src/main/java/com/volmit/iris/core/service/PreservationSVC.java b/core/src/main/java/com/volmit/iris/core/service/PreservationSVC.java index 317e1731e..2fb0e8d74 100644 --- a/core/src/main/java/com/volmit/iris/core/service/PreservationSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/PreservationSVC.java @@ -24,19 +24,22 @@ import com.volmit.iris.engine.framework.MeteredCache; import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.IrisService; import com.volmit.iris.util.scheduling.Looper; +import org.jetbrains.annotations.Unmodifiable; +import java.lang.ref.WeakReference; import java.util.List; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.stream.Collectors; +import java.util.stream.Stream; public class PreservationSVC implements IrisService { private final List threads = new CopyOnWriteArrayList<>(); private final List services = new CopyOnWriteArrayList<>(); - private final List caches = new CopyOnWriteArrayList<>(); + private final List> caches = new CopyOnWriteArrayList<>(); private Looper dereferencer; public void register(Thread t) { @@ -48,22 +51,18 @@ public class PreservationSVC implements IrisService { } public void printCaches() { - long s = caches.stream().filter(i -> !i.isClosed()).mapToLong(MeteredCache::getSize).sum(); - long m = caches.stream().filter(i -> !i.isClosed()).mapToLong(MeteredCache::getMaxSize).sum(); + var c = getCaches(); + long s = 0; + long m = 0; double p = 0; - double mf = 0; + double mf = Math.max(c.size(), 1); - for (MeteredCache i : caches) { - if (i.isClosed()) { - continue; - } - - mf++; + for (MeteredCache i : c) { + s += i.getSize(); + m += i.getMaxSize(); p += i.getUsage(); } - mf = mf == 0 ? 1 : mf; - Iris.info("Cached " + Form.f(s) + " / " + Form.f(m) + " (" + Form.pc(p / mf) + ") from " + caches.size() + " Caches"); } @@ -119,14 +118,29 @@ public class PreservationSVC implements IrisService { } public void updateCaches() { - caches.removeIf(MeteredCache::isClosed); + caches.removeIf(ref -> { + var c = ref.get(); + return c == null || c.isClosed(); + }); } public void registerCache(MeteredCache cache) { - caches.add(cache); + caches.add(new WeakReference<>(cache)); } public List> caches() { - return caches.stream().map(MeteredCache::getRawCache).collect(Collectors.toList()); + return cacheStream().map(MeteredCache::getRawCache).collect(Collectors.toList()); + } + + @Unmodifiable + public List getCaches() { + return cacheStream().toList(); + } + + private Stream cacheStream() { + return caches.stream() + .map(WeakReference::get) + .filter(Objects::nonNull) + .filter(cache -> !cache.isClosed()); } } diff --git a/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java b/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java index 58be7738c..d11cd911a 100644 --- a/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/TreeSVC.java @@ -189,6 +189,16 @@ public class TreeSVC implements IrisService { } + @Override + public void setData(int xx, int yy, int zz, T data) { + + } + + @Override + public T getData(int xx, int yy, int zz, Class t) { + return null; + } + @Override public Engine getEngine() { return engine; @@ -225,7 +235,7 @@ public class TreeSVC implements IrisService { if (d instanceof IrisCustomData data) { block.setBlockData(data.getBase(), false); Iris.service(ExternalDataSVC.class).processUpdate(engine, block, data.getCustom()); - } else block.setBlockData(d); + } else block.setBlockData(d, false); } } }); diff --git a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java index 8d984ba91..e55991245 100644 --- a/core/src/main/java/com/volmit/iris/core/service/WandSVC.java +++ b/core/src/main/java/com/volmit/iris/core/service/WandSVC.java @@ -90,6 +90,7 @@ public class WandSVC implements IrisService { int total = c.getSizeX() * c.getSizeY() * c.getSizeZ(); var latch = new CountDownLatch(1); + var holder = Iris.tickets.getHolder(p.getWorld()); new Job() { private int i; private Chunk chunk; @@ -108,7 +109,7 @@ public class WandSVC implements IrisService { while (time > M.ms()) { if (!it.hasNext()) { if (chunk != null) { - chunk.removePluginChunkTicket(Iris.instance); + holder.removeTicket(chunk); chunk = null; } @@ -122,9 +123,10 @@ public class WandSVC implements IrisService { var bChunk = b.getChunk(); if (chunk == null) { chunk = bChunk; - chunk.addPluginChunkTicket(Iris.instance); + holder.addTicket(chunk); } else if (chunk != bChunk) { - chunk.removePluginChunkTicket(Iris.instance); + holder.removeTicket(chunk); + holder.addTicket(bChunk); chunk = bChunk; } diff --git a/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java b/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java index 06651213c..68812036f 100644 --- a/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java +++ b/core/src/main/java/com/volmit/iris/core/tools/IrisToolbelt.java @@ -34,6 +34,7 @@ import com.volmit.iris.util.plugin.VolmitSender; import org.bukkit.Bukkit; import org.bukkit.World; import org.bukkit.entity.Player; +import org.jetbrains.annotations.ApiStatus; import java.io.File; import java.io.IOException; @@ -45,6 +46,7 @@ import java.util.Map; * Hope you packed snacks & road sodas. */ public class IrisToolbelt { + @ApiStatus.Internal public static Map toolbeltConfiguration = new HashMap<>(); /** @@ -232,7 +234,11 @@ public class IrisToolbelt { } public static void retainMantleDataForSlice(String className) { - toolbeltConfiguration.put("retain.mantle." + className, true); + toolbeltConfiguration.put("retain.mantle." + className, Boolean.TRUE); + } + + public static boolean isRetainingMantleDataForSlice(String className) { + return !toolbeltConfiguration.isEmpty() && toolbeltConfiguration.get("retain.mantle." + className) == Boolean.TRUE; } public static T getMantleData(World world, int x, int y, int z, Class of) { diff --git a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java index 9435dc822..98e73444e 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisComplex.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisComplex.java @@ -89,7 +89,7 @@ public class IrisComplex implements DataProvider { } public IrisComplex(Engine engine, boolean simple) { - int cacheSize = IrisSettings.get().getPerformance().getCacheSize(); + int cacheSize = IrisSettings.get().getPerformance().getNoiseCacheSize(); IrisBiome emptyBiome = new IrisBiome(); UUID focusUUID = UUID.nameUUIDFromBytes("focus".getBytes()); this.rng = new RNG(engine.getSeedManager().getComplex()); diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java index 3586965e7..9f570d642 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngine.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngine.java @@ -1,578 +1,583 @@ -/* - * 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 . - */ - -package com.volmit.iris.engine; - -import com.google.common.util.concurrent.AtomicDouble; -import com.google.gson.Gson; -import com.volmit.iris.Iris; -import com.volmit.iris.core.ServerConfigurator; -import com.volmit.iris.core.events.IrisEngineHotloadEvent; -import com.volmit.iris.core.gui.PregeneratorJob; -import com.volmit.iris.core.loader.ResourceLoader; -import com.volmit.iris.core.nms.container.BlockPos; -import com.volmit.iris.core.nms.container.Pair; -import com.volmit.iris.core.project.IrisProject; -import com.volmit.iris.core.scripting.environment.EngineEnvironment; -import com.volmit.iris.core.service.PreservationSVC; -import com.volmit.iris.engine.data.cache.AtomicCache; -import com.volmit.iris.engine.framework.*; -import com.volmit.iris.engine.mantle.EngineMantle; -import com.volmit.iris.engine.object.*; -import com.volmit.iris.util.atomics.AtomicRollingSequence; -import com.volmit.iris.util.collection.KMap; -import com.volmit.iris.util.context.ChunkContext; -import com.volmit.iris.util.context.IrisContext; -import com.volmit.iris.util.documentation.BlockCoordinates; -import com.volmit.iris.util.format.C; -import com.volmit.iris.util.format.Form; -import com.volmit.iris.util.hunk.Hunk; -import com.volmit.iris.util.io.IO; -import com.volmit.iris.util.mantle.flag.MantleFlag; -import com.volmit.iris.util.math.M; -import com.volmit.iris.util.math.RNG; -import com.volmit.iris.util.matter.MatterStructurePOI; -import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer; -import com.volmit.iris.util.scheduling.ChronoLatch; -import com.volmit.iris.util.scheduling.J; -import com.volmit.iris.util.scheduling.PrecisionStopwatch; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.ToString; -import org.bukkit.Material; -import org.bukkit.block.Biome; -import org.bukkit.block.data.BlockData; -import org.bukkit.command.CommandSender; - -import java.io.File; -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -@Data -@EqualsAndHashCode(exclude = "context") -@ToString(exclude = "context") -public class IrisEngine implements Engine { - private final AtomicInteger bud; - private final AtomicInteger buds; - private final AtomicInteger generated; - private final AtomicInteger generatedLast; - private final AtomicDouble perSecond; - private final AtomicLong lastGPS; - private final EngineTarget target; - private final IrisContext context; - private final EngineMantle mantle; - private final ChronoLatch perSecondLatch; - private final ChronoLatch perSecondBudLatch; - private final EngineMetrics metrics; - private final boolean studio; - private final AtomicRollingSequence wallClock; - private final int art; - private final AtomicCache engineData = new AtomicCache<>(); - private final AtomicBoolean cleaning; - private final ChronoLatch cleanLatch; - private final SeedManager seedManager; - private CompletableFuture hash32; - private EngineMode mode; - private EngineEffects effects; - private EngineEnvironment execution; - private EngineWorldManager worldManager; - private volatile int parallelism; - private boolean failing; - private boolean closed; - private int cacheId; - private double maxBiomeObjectDensity; - private double maxBiomeLayerDensity; - private double maxBiomeDecoratorDensity; - private IrisComplex complex; - - public IrisEngine(EngineTarget target, boolean studio) { - this.studio = studio; - this.target = target; - getEngineData(); - verifySeed(); - this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); - bud = new AtomicInteger(0); - buds = new AtomicInteger(0); - metrics = new EngineMetrics(32); - cleanLatch = new ChronoLatch(10000); - generatedLast = new AtomicInteger(0); - perSecond = new AtomicDouble(0); - perSecondLatch = new ChronoLatch(1000, false); - perSecondBudLatch = new ChronoLatch(1000, false); - wallClock = new AtomicRollingSequence(32); - lastGPS = new AtomicLong(M.ms()); - generated = new AtomicInteger(0); - mantle = new IrisEngineMantle(this); - context = new IrisContext(this); - cleaning = new AtomicBoolean(false); - context.touch(); - getData().setEngine(this); - getData().loadPrefetch(this); - Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); - failing = false; - closed = false; - art = J.ar(this::tickRandomPlayer, 0); - setupEngine(); - Iris.debug("Engine Initialized " + getCacheID()); - } - - private void verifySeed() { - if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { - target.getWorld().setRawWorldSeed(getEngineData().getSeed()); - } - } - - private void tickRandomPlayer() { - recycle(); - if (perSecondBudLatch.flip()) { - buds.set(bud.get()); - bud.set(0); - } - - if (effects != null) { - effects.tickRandomPlayer(); - } - } - - private void prehotload() { - worldManager.close(); - complex.close(); - execution.close(); - effects.close(); - mode.close(); - - J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); - } - - private void setupEngine() { - try { - Iris.debug("Setup Engine " + getCacheID()); - cacheId = RNG.r.nextInt(); - worldManager = new IrisWorldManager(this); - complex = new IrisComplex(this); - execution = EngineEnvironment.create(this); - effects = new IrisEngineEffects(this); - hash32 = new CompletableFuture<>(); - mantle.hotload(); - setupMode(); - getDimension().getEngineScripts().forEach(execution::execute); - J.a(this::computeBiomeMaxes); - J.a(() -> { - File[] roots = getData().getLoaders() - .values() - .stream() - .map(ResourceLoader::getFolderName) - .map(n -> new File(getData().getDataFolder(), n)) - .filter(File::exists) - .filter(File::isDirectory) - .toArray(File[]::new); - hash32.complete(IO.hashRecursive(roots)); - }); - } catch (Throwable e) { - Iris.error("FAILED TO SETUP ENGINE!"); - e.printStackTrace(); - } - - Iris.debug("Engine Setup Complete " + getCacheID()); - } - - private void setupMode() { - if (mode != null) { - mode.close(); - } - - mode = getDimension().getMode().create(this); - } - - @Override - public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { - getMantle().generateMatter(x, z, multicore, context); - } - - @Override - public Set getObjectsAt(int x, int z) { - return getMantle().getObjectComponent().guess(x, z); - } - - @Override - public Set> getPOIsAt(int chunkX, int chunkY) { - Set> pois = new HashSet<>(); - getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); - return pois; - } - - @Override - public IrisJigsawStructure getStructureAt(int x, int z) { - return getMantle().getJigsawComponent().guess(x, z); - } - - @Override - public IrisJigsawStructure getStructureAt(int x, int y, int z) { - var container = getMantle().getMantle().get(x, y, z, JigsawStructureContainer.class); - return container == null ? null : container.load(getData()); - } - - private void warmupChunk(int x, int z) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - int xx = x + (i << 4); - int zz = z + (z << 4); - getComplex().getTrueBiomeStream().get(xx, zz); - getComplex().getHeightStream().get(xx, zz); - } - } - } - - @Override - public void hotload() { - hotloadSilently(); - Iris.callEvent(new IrisEngineHotloadEvent(this)); - } - - public void hotloadComplex() { - complex.close(); - complex = new IrisComplex(this); - } - - public void hotloadSilently() { - getData().dump(); - getData().clearLists(); - getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); - prehotload(); - setupEngine(); - J.a(() -> { - synchronized (ServerConfigurator.class) { - ServerConfigurator.installDataPacks(false); - } - }); - } - - @Override - public IrisEngineData getEngineData() { - return engineData.aquire(() -> { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - IrisEngineData data = null; - - if (f.exists()) { - try { - data = new Gson().fromJson(IO.readAll(f), IrisEngineData.class); - if (data == null) { - Iris.error("Failed to read Engine Data! Corrupted File? recreating..."); - } - } catch (IOException e) { - e.printStackTrace(); - } - } - - if (data == null) { - data = new IrisEngineData(); - data.getStatistics().setVersion(Iris.instance.getIrisVersion()); - data.getStatistics().setMCVersion(Iris.instance.getMCVersion()); - data.getStatistics().setUpgradedVersion(Iris.instance.getIrisVersion()); - if (data.getStatistics().getVersion() == -1 || data.getStatistics().getMCVersion() == -1 ) { - Iris.error("Failed to setup Engine Data!"); - } - - if (f.getParentFile().exists() || f.getParentFile().mkdirs()) { - try { - IO.writeAll(f, new Gson().toJson(data)); - } catch (IOException e) { - e.printStackTrace(); - } - } else { - Iris.error("Failed to setup Engine Data!"); - } - } - - return data; - }); - } - - @Override - public int getGenerated() { - return generated.get(); - } - - @Override - public double getGeneratedPerSecond() { - if (perSecondLatch.flip()) { - double g = generated.get() - generatedLast.get(); - generatedLast.set(generated.get()); - - if (g == 0) { - return 0; - } - - long dur = M.ms() - lastGPS.get(); - lastGPS.set(M.ms()); - perSecond.set(g / ((double) (dur) / 1000D)); - } - - return perSecond.get(); - } - - @Override - public boolean isStudio() { - return studio; - } - - private void computeBiomeMaxes() { - for (IrisBiome i : getDimension().getAllBiomes(this)) { - double density = 0; - - for (IrisObjectPlacement j : i.getObjects()) { - density += j.getDensity() * j.getChance(); - } - - maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); - density = 0; - - for (IrisDecorator j : i.getDecorators()) { - density += Math.max(j.getStackMax(), 1) * j.getChance(); - } - - maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); - density = 0; - - for (IrisBiomePaletteLayer j : i.getLayers()) { - density++; - } - - maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); - } - } - - @Override - public int getBlockUpdatesPerSecond() { - return buds.get(); - } - - public void printMetrics(CommandSender sender) { - KMap totals = new KMap<>(); - KMap weights = new KMap<>(); - double masterWallClock = wallClock.getAverage(); - KMap timings = getMetrics().pull(); - double totalWeight = 0; - double wallClock = getMetrics().getTotal().getAverage(); - - for (double j : timings.values()) { - totalWeight += j; - } - - for (String j : timings.k()) { - weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); - } - - totals.put(getName(), wallClock); - - double mtotals = 0; - - for (double i : totals.values()) { - mtotals += i; - } - - for (String i : totals.k()) { - totals.put(i, (masterWallClock / mtotals) * totals.get(i)); - } - - double v = 0; - - for (double i : weights.values()) { - v += i; - } - - for (String i : weights.k()) { - weights.put(i, weights.get(i) / v); - } - - sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); - - for (String i : totals.k()) { - sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); - } - - sender.sendMessage("Details: "); - - for (String i : weights.sortKNumber().reverse()) { - String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; - String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; - String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; - - sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); - } - } - - @Override - public void close() { - PregeneratorJob.shutdownInstance(); - closed = true; - J.car(art); - getWorldManager().close(); - getTarget().close(); - saveEngineData(); - getMantle().close(); - getComplex().close(); - mode.close(); - getData().dump(); - getData().clearLists(); - Iris.service(PreservationSVC.class).dereference(); - Iris.debug("Engine Fully Shutdown!"); - complex = null; - } - - @Override - public boolean isClosed() { - return closed; - } - - @Override - public void recycle() { - if (!cleanLatch.flip()) { - return; - } - - if (cleaning.get()) { - cleanLatch.flipDown(); - return; - } - - cleaning.set(true); - - J.a(() -> { - try { - getData().getObjectLoader().clean(); - } catch (Throwable e) { - Iris.reportError(e); - Iris.error("Cleanup failed! Enable debug to see stacktrace."); - } - - cleaning.lazySet(false); - }); - } - - @BlockCoordinates - @Override - public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { - if (closed) { - throw new WrongEngineBroException(); - } - - context.touch(); - getEngineData().getStatistics().generatedChunk(); - try { - PrecisionStopwatch p = PrecisionStopwatch.start(); - Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t)); - - if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { - for (int i = 0; i < 16; i++) { - for (int j = 0; j < 16; j++) { - blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); - } - } - } else { - mode.generate(x, z, blocks, vbiomes, multicore); - } - - getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); - getMetrics().getTotal().put(p.getMilliseconds()); - generated.incrementAndGet(); - - if (generated.get() == 661) { - J.a(() -> getData().savePrefetch(this)); - } - } catch (Throwable e) { - Iris.reportError(e); - fail("Failed to generate " + x + ", " + z, e); - } - } - - @Override - public void saveEngineData() { - //TODO: Method this file - File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); - f.getParentFile().mkdirs(); - try { - IO.writeAll(f, new Gson().toJson(getEngineData())); - Iris.debug("Saved Engine Data"); - } catch (IOException e) { - Iris.error("Failed to save Engine Data"); - e.printStackTrace(); - } - } - - @Override - public void blockUpdatedMetric() { - bud.incrementAndGet(); - } - - @Override - public IrisBiome getFocus() { - if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { - return null; - } - - return getData().getBiomeLoader().load(getDimension().getFocus()); - } - - @Override - public IrisRegion getFocusRegion() { - if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { - return null; - } - - return getData().getRegionLoader().load(getDimension().getFocusRegion()); - } - - @Override - public void fail(String error, Throwable e) { - failing = true; - Iris.error(error); - e.printStackTrace(); - } - - @Override - public boolean hasFailed() { - return failing; - } - - @Override - public int getCacheID() { - return cacheId; - } - - private boolean EngineSafe() { - // Todo: this has potential if done right - int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); - int EngineIrisVersion = getEngineData().getStatistics().getVersion(); - int MinecraftVersion = Iris.instance.getMCVersion(); - int IrisVersion = Iris.instance.getIrisVersion(); - if (EngineIrisVersion != IrisVersion) { - return false; - } - if (EngineMCVersion != MinecraftVersion) { - return false; - } - return true; - } -} +/* + * 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 . + */ + +package com.volmit.iris.engine; + +import com.google.common.util.concurrent.AtomicDouble; +import com.google.gson.Gson; +import com.volmit.iris.Iris; +import com.volmit.iris.core.ServerConfigurator; +import com.volmit.iris.core.events.IrisEngineHotloadEvent; +import com.volmit.iris.core.gui.PregeneratorJob; +import com.volmit.iris.core.loader.ResourceLoader; +import com.volmit.iris.core.nms.container.BlockPos; +import com.volmit.iris.core.nms.container.Pair; +import com.volmit.iris.core.project.IrisProject; +import com.volmit.iris.core.scripting.environment.EngineEnvironment; +import com.volmit.iris.core.service.PreservationSVC; +import com.volmit.iris.engine.data.cache.AtomicCache; +import com.volmit.iris.engine.framework.*; +import com.volmit.iris.engine.mantle.EngineMantle; +import com.volmit.iris.engine.object.*; +import com.volmit.iris.util.atomics.AtomicRollingSequence; +import com.volmit.iris.util.collection.KMap; +import com.volmit.iris.util.context.ChunkContext; +import com.volmit.iris.util.context.IrisContext; +import com.volmit.iris.util.documentation.BlockCoordinates; +import com.volmit.iris.util.format.C; +import com.volmit.iris.util.format.Form; +import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.io.IO; +import com.volmit.iris.util.mantle.flag.MantleFlag; +import com.volmit.iris.util.math.M; +import com.volmit.iris.util.math.RNG; +import com.volmit.iris.util.matter.MatterStructurePOI; +import com.volmit.iris.util.matter.slices.container.JigsawStructureContainer; +import com.volmit.iris.util.scheduling.ChronoLatch; +import com.volmit.iris.util.scheduling.J; +import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.bukkit.Material; +import org.bukkit.block.Biome; +import org.bukkit.block.data.BlockData; +import org.bukkit.command.CommandSender; + +import java.io.File; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +@Data +@EqualsAndHashCode(exclude = "context") +@ToString(exclude = "context") +public class IrisEngine implements Engine { + private final AtomicInteger bud; + private final AtomicInteger buds; + private final AtomicInteger generated; + private final AtomicInteger generatedLast; + private final AtomicDouble perSecond; + private final AtomicLong lastGPS; + private final EngineTarget target; + private final IrisContext context; + private final EngineMantle mantle; + private final ChronoLatch perSecondLatch; + private final ChronoLatch perSecondBudLatch; + private final EngineMetrics metrics; + private final boolean studio; + private final AtomicRollingSequence wallClock; + private final int art; + private final AtomicCache engineData = new AtomicCache<>(); + private final AtomicBoolean cleaning; + private final ChronoLatch cleanLatch; + private final SeedManager seedManager; + private CompletableFuture hash32; + private EngineMode mode; + private EngineEffects effects; + private EngineEnvironment execution; + private EngineWorldManager worldManager; + private volatile int parallelism; + private boolean failing; + private boolean closed; + private int cacheId; + private double maxBiomeObjectDensity; + private double maxBiomeLayerDensity; + private double maxBiomeDecoratorDensity; + private IrisComplex complex; + + public IrisEngine(EngineTarget target, boolean studio) { + this.studio = studio; + this.target = target; + getEngineData(); + verifySeed(); + this.seedManager = new SeedManager(target.getWorld().getRawWorldSeed()); + bud = new AtomicInteger(0); + buds = new AtomicInteger(0); + metrics = new EngineMetrics(32); + cleanLatch = new ChronoLatch(10000); + generatedLast = new AtomicInteger(0); + perSecond = new AtomicDouble(0); + perSecondLatch = new ChronoLatch(1000, false); + perSecondBudLatch = new ChronoLatch(1000, false); + wallClock = new AtomicRollingSequence(32); + lastGPS = new AtomicLong(M.ms()); + generated = new AtomicInteger(0); + mantle = new IrisEngineMantle(this); + context = new IrisContext(this); + cleaning = new AtomicBoolean(false); + execution = getData().getEnvironment().with(this); + if (studio) { + getData().dump(); + getData().clearLists(); + getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); + } + context.touch(); + getData().setEngine(this); + getData().loadPrefetch(this); + Iris.info("Initializing Engine: " + target.getWorld().name() + "/" + target.getDimension().getLoadKey() + " (" + target.getDimension().getDimensionHeight() + " height) Seed: " + getSeedManager().getSeed()); + failing = false; + closed = false; + art = J.ar(this::tickRandomPlayer, 0); + setupEngine(); + Iris.debug("Engine Initialized " + getCacheID()); + } + + private void verifySeed() { + if (getEngineData().getSeed() != null && getEngineData().getSeed() != target.getWorld().getRawWorldSeed()) { + target.getWorld().setRawWorldSeed(getEngineData().getSeed()); + } + } + + private void tickRandomPlayer() { + recycle(); + if (perSecondBudLatch.flip()) { + buds.set(bud.get()); + bud.set(0); + } + + if (effects != null) { + effects.tickRandomPlayer(); + } + } + + private void prehotload() { + worldManager.close(); + complex.close(); + effects.close(); + mode.close(); + execution = getData().getEnvironment().with(this); + + J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); + } + + private void setupEngine() { + try { + Iris.debug("Setup Engine " + getCacheID()); + cacheId = RNG.r.nextInt(); + worldManager = new IrisWorldManager(this); + complex = new IrisComplex(this); + effects = new IrisEngineEffects(this); + hash32 = new CompletableFuture<>(); + mantle.hotload(); + setupMode(); + getDimension().getEngineScripts().forEach(execution::execute); + J.a(this::computeBiomeMaxes); + J.a(() -> { + File[] roots = getData().getLoaders() + .values() + .stream() + .map(ResourceLoader::getFolderName) + .map(n -> new File(getData().getDataFolder(), n)) + .filter(File::exists) + .filter(File::isDirectory) + .toArray(File[]::new); + hash32.complete(IO.hashRecursive(roots)); + }); + } catch (Throwable e) { + Iris.error("FAILED TO SETUP ENGINE!"); + e.printStackTrace(); + } + + Iris.debug("Engine Setup Complete " + getCacheID()); + } + + private void setupMode() { + if (mode != null) { + mode.close(); + } + + mode = getDimension().getMode().create(this); + } + + @Override + public void generateMatter(int x, int z, boolean multicore, ChunkContext context) { + getMantle().generateMatter(x, z, multicore, context); + } + + @Override + public Set getObjectsAt(int x, int z) { + return getMantle().getObjectComponent().guess(x, z); + } + + @Override + public Set> getPOIsAt(int chunkX, int chunkY) { + Set> pois = new HashSet<>(); + getMantle().getMantle().iterateChunk(chunkX, chunkY, MatterStructurePOI.class, (x, y, z, d) -> pois.add(new Pair<>(d.getType(), new BlockPos(x, y, z)))); + return pois; + } + + @Override + public IrisJigsawStructure getStructureAt(int x, int z) { + return getMantle().getJigsawComponent().guess(x, z); + } + + @Override + public IrisJigsawStructure getStructureAt(int x, int y, int z) { + var container = getMantle().getMantle().get(x, y, z, JigsawStructureContainer.class); + return container == null ? null : container.load(getData()); + } + + private void warmupChunk(int x, int z) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + int xx = x + (i << 4); + int zz = z + (z << 4); + getComplex().getTrueBiomeStream().get(xx, zz); + getComplex().getHeightStream().get(xx, zz); + } + } + } + + @Override + public void hotload() { + hotloadSilently(); + Iris.callEvent(new IrisEngineHotloadEvent(this)); + } + + public void hotloadComplex() { + complex.close(); + complex = new IrisComplex(this); + } + + public void hotloadSilently() { + getData().dump(); + getData().clearLists(); + getTarget().setDimension(getData().getDimensionLoader().load(getDimension().getLoadKey())); + prehotload(); + setupEngine(); + J.a(() -> { + synchronized (ServerConfigurator.class) { + ServerConfigurator.installDataPacks(false); + } + }); + } + + @Override + public IrisEngineData getEngineData() { + return engineData.aquire(() -> { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + IrisEngineData data = null; + + if (f.exists()) { + try { + data = new Gson().fromJson(IO.readAll(f), IrisEngineData.class); + if (data == null) { + Iris.error("Failed to read Engine Data! Corrupted File? recreating..."); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + if (data == null) { + data = new IrisEngineData(); + data.getStatistics().setVersion(Iris.instance.getIrisVersion()); + data.getStatistics().setMCVersion(Iris.instance.getMCVersion()); + data.getStatistics().setUpgradedVersion(Iris.instance.getIrisVersion()); + if (data.getStatistics().getVersion() == -1 || data.getStatistics().getMCVersion() == -1 ) { + Iris.error("Failed to setup Engine Data!"); + } + + if (f.getParentFile().exists() || f.getParentFile().mkdirs()) { + try { + IO.writeAll(f, new Gson().toJson(data)); + } catch (IOException e) { + e.printStackTrace(); + } + } else { + Iris.error("Failed to setup Engine Data!"); + } + } + + return data; + }); + } + + @Override + public int getGenerated() { + return generated.get(); + } + + @Override + public double getGeneratedPerSecond() { + if (perSecondLatch.flip()) { + double g = generated.get() - generatedLast.get(); + generatedLast.set(generated.get()); + + if (g == 0) { + return 0; + } + + long dur = M.ms() - lastGPS.get(); + lastGPS.set(M.ms()); + perSecond.set(g / ((double) (dur) / 1000D)); + } + + return perSecond.get(); + } + + @Override + public boolean isStudio() { + return studio; + } + + private void computeBiomeMaxes() { + for (IrisBiome i : getDimension().getAllBiomes(this)) { + double density = 0; + + for (IrisObjectPlacement j : i.getObjects()) { + density += j.getDensity() * j.getChance(); + } + + maxBiomeObjectDensity = Math.max(maxBiomeObjectDensity, density); + density = 0; + + for (IrisDecorator j : i.getDecorators()) { + density += Math.max(j.getStackMax(), 1) * j.getChance(); + } + + maxBiomeDecoratorDensity = Math.max(maxBiomeDecoratorDensity, density); + density = 0; + + for (IrisBiomePaletteLayer j : i.getLayers()) { + density++; + } + + maxBiomeLayerDensity = Math.max(maxBiomeLayerDensity, density); + } + } + + @Override + public int getBlockUpdatesPerSecond() { + return buds.get(); + } + + public void printMetrics(CommandSender sender) { + KMap totals = new KMap<>(); + KMap weights = new KMap<>(); + double masterWallClock = wallClock.getAverage(); + KMap timings = getMetrics().pull(); + double totalWeight = 0; + double wallClock = getMetrics().getTotal().getAverage(); + + for (double j : timings.values()) { + totalWeight += j; + } + + for (String j : timings.k()) { + weights.put(getName() + "." + j, (wallClock / totalWeight) * timings.get(j)); + } + + totals.put(getName(), wallClock); + + double mtotals = 0; + + for (double i : totals.values()) { + mtotals += i; + } + + for (String i : totals.k()) { + totals.put(i, (masterWallClock / mtotals) * totals.get(i)); + } + + double v = 0; + + for (double i : weights.values()) { + v += i; + } + + for (String i : weights.k()) { + weights.put(i, weights.get(i) / v); + } + + sender.sendMessage("Total: " + C.BOLD + C.WHITE + Form.duration(masterWallClock, 0)); + + for (String i : totals.k()) { + sender.sendMessage(" Engine " + C.UNDERLINE + C.GREEN + i + C.RESET + ": " + C.BOLD + C.WHITE + Form.duration(totals.get(i), 0)); + } + + sender.sendMessage("Details: "); + + for (String i : weights.sortKNumber().reverse()) { + String befb = C.UNDERLINE + "" + C.GREEN + "" + i.split("\\Q[\\E")[0] + C.RESET + C.GRAY + "["; + String num = C.GOLD + i.split("\\Q[\\E")[1].split("]")[0] + C.RESET + C.GRAY + "]."; + String afb = C.ITALIC + "" + C.AQUA + i.split("\\Q]\\E")[1].substring(1) + C.RESET + C.GRAY; + + sender.sendMessage(" " + befb + num + afb + ": " + C.BOLD + C.WHITE + Form.pc(weights.get(i), 0)); + } + } + + @Override + public void close() { + PregeneratorJob.shutdownInstance(); + closed = true; + J.car(art); + getWorldManager().close(); + getTarget().close(); + saveEngineData(); + getMantle().close(); + getComplex().close(); + mode.close(); + getData().dump(); + getData().clearLists(); + Iris.service(PreservationSVC.class).dereference(); + Iris.debug("Engine Fully Shutdown!"); + complex = null; + } + + @Override + public boolean isClosed() { + return closed; + } + + @Override + public void recycle() { + if (!cleanLatch.flip()) { + return; + } + + if (cleaning.get()) { + cleanLatch.flipDown(); + return; + } + + cleaning.set(true); + + J.a(() -> { + try { + getData().getObjectLoader().clean(); + } catch (Throwable e) { + Iris.reportError(e); + Iris.error("Cleanup failed! Enable debug to see stacktrace."); + } + + cleaning.lazySet(false); + }); + } + + @BlockCoordinates + @Override + public void generate(int x, int z, Hunk vblocks, Hunk vbiomes, boolean multicore) throws WrongEngineBroException { + if (closed) { + throw new WrongEngineBroException(); + } + + context.touch(); + getEngineData().getStatistics().generatedChunk(); + try { + PrecisionStopwatch p = PrecisionStopwatch.start(); + Hunk blocks = vblocks.listen((xx, y, zz, t) -> catchBlockUpdates(x + xx, y, z + zz, t)); + + if (getDimension().isDebugChunkCrossSections() && ((x >> 4) % getDimension().getDebugCrossSectionsMod() == 0 || (z >> 4) % getDimension().getDebugCrossSectionsMod() == 0)) { + for (int i = 0; i < 16; i++) { + for (int j = 0; j < 16; j++) { + blocks.set(i, 0, j, Material.CRYING_OBSIDIAN.createBlockData()); + } + } + } else { + mode.generate(x, z, blocks, vbiomes, multicore); + } + + getMantle().getMantle().flag(x >> 4, z >> 4, MantleFlag.REAL, true); + getMetrics().getTotal().put(p.getMilliseconds()); + generated.incrementAndGet(); + + if (generated.get() == 661) { + J.a(() -> getData().savePrefetch(this)); + } + } catch (Throwable e) { + Iris.reportError(e); + fail("Failed to generate " + x + ", " + z, e); + } + } + + @Override + public void saveEngineData() { + //TODO: Method this file + File f = new File(getWorld().worldFolder(), "iris/engine-data/" + getDimension().getLoadKey() + ".json"); + f.getParentFile().mkdirs(); + try { + IO.writeAll(f, new Gson().toJson(getEngineData())); + Iris.debug("Saved Engine Data"); + } catch (IOException e) { + Iris.error("Failed to save Engine Data"); + e.printStackTrace(); + } + } + + @Override + public void blockUpdatedMetric() { + bud.incrementAndGet(); + } + + @Override + public IrisBiome getFocus() { + if (getDimension().getFocus() == null || getDimension().getFocus().trim().isEmpty()) { + return null; + } + + return getData().getBiomeLoader().load(getDimension().getFocus()); + } + + @Override + public IrisRegion getFocusRegion() { + if (getDimension().getFocusRegion() == null || getDimension().getFocusRegion().trim().isEmpty()) { + return null; + } + + return getData().getRegionLoader().load(getDimension().getFocusRegion()); + } + + @Override + public void fail(String error, Throwable e) { + failing = true; + Iris.error(error); + e.printStackTrace(); + } + + @Override + public boolean hasFailed() { + return failing; + } + + @Override + public int getCacheID() { + return cacheId; + } + + private boolean EngineSafe() { + // Todo: this has potential if done right + int EngineMCVersion = getEngineData().getStatistics().getMCVersion(); + int EngineIrisVersion = getEngineData().getStatistics().getVersion(); + int MinecraftVersion = Iris.instance.getMCVersion(); + int IrisVersion = Iris.instance.getIrisVersion(); + if (EngineIrisVersion != IrisVersion) { + return false; + } + if (EngineMCVersion != MinecraftVersion) { + return false; + } + return true; + } +} diff --git a/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java b/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java index b788502e9..71a412290 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisEngineMantle.java @@ -86,11 +86,13 @@ public class IrisEngineMantle implements EngineMantle { .map(components::get) .map(components -> { int radius = components.stream() + .filter(MantleComponent::isEnabled) .mapToInt(MantleComponent::getRadius) .max() .orElse(0); return new Pair<>(List.copyOf(components), radius); }) + .filter(pair -> !pair.getA().isEmpty()) .toList(); int radius = 0; diff --git a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java index 1edfee1f1..9e2d50a59 100644 --- a/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java +++ b/core/src/main/java/com/volmit/iris/engine/IrisWorldManager.java @@ -440,7 +440,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { //INMS.get().injectBiomesFromMantle(e, getMantle()); if (!IrisSettings.get().getGenerator().earlyCustomBlocks) return; - e.addPluginChunkTicket(Iris.instance); + Iris.tickets.addTicket(e); J.s(() -> { var chunk = getMantle().getChunk(e).use(); int minY = getTarget().getWorld().minHeight(); @@ -452,7 +452,7 @@ public class IrisWorldManager extends EngineAssignedWorldManager { }); } finally { chunk.release(); - e.removePluginChunkTicket(Iris.instance); + Iris.tickets.removeTicket(e); } }, RNG.r.i(20, 60)); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/BlockUpdater.java b/core/src/main/java/com/volmit/iris/engine/framework/BlockUpdater.java index 509794138..1ba9e7a18 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/BlockUpdater.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/BlockUpdater.java @@ -18,6 +18,7 @@ package com.volmit.iris.engine.framework; +import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.math.RNG; import org.bukkit.Chunk; import org.bukkit.block.data.BlockData; @@ -28,5 +29,5 @@ public interface BlockUpdater { void updateChunk(Chunk c); - void update(int x, int y, int z, Chunk c, RNG rf); + void update(int x, int y, int z, Chunk c, MantleChunk mc, RNG rf); } diff --git a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java index 651c25ddc..98f408976 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/Engine.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/Engine.java @@ -48,6 +48,7 @@ import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.format.C; import com.volmit.iris.util.function.Function2; import com.volmit.iris.util.hunk.Hunk; +import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.math.BlockPosition; import com.volmit.iris.util.math.M; @@ -295,7 +296,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat var chunk = mantle.getChunk(c).use(); try { Semaphore semaphore = new Semaphore(1024); - chunk.raiseFlag(MantleFlag.ETCHED, () -> { + chunk.raiseFlagUnchecked(MantleFlag.ETCHED, () -> { chunk.raiseFlagUnchecked(MantleFlag.TILE, run(semaphore, () -> { chunk.iterate(TileWrapper.class, (x, y, z, v) -> { Block block = c.getBlock(x & 15, y + getWorld().minHeight(), z & 15); @@ -340,14 +341,14 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat for (int z = 0; z < 16; z++) { if (grid[x][z] == Integer.MIN_VALUE) continue; - update(x, grid[x][z], z, c, rng); + update(x, grid[x][z], z, c, chunk, rng); } } chunk.iterate(MatterUpdate.class, (x, yf, z, v) -> { int y = yf + getWorld().minHeight(); if (v != null && v.isUpdate()) { - update(x, y, z, c, rng); + update(x, y, z, c, chunk, rng); } }); chunk.deleteSlices(MatterUpdate.class); @@ -394,7 +395,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat @BlockCoordinates @Override - default void update(int x, int y, int z, Chunk c, RNG rf) { + default void update(int x, int y, int z, Chunk c, MantleChunk mc, RNG rf) { Block block = c.getBlock(x, y, z); BlockData data = block.getBlockData(); blockUpdatedMetric(); @@ -407,17 +408,11 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } if (slot != null) { - KList tables = getLootTables(rx, block); + KList tables = getLootTables(rx, block, mc); try { Bukkit.getPluginManager().callEvent(new IrisLootEvent(this, block, slot, tables)); - - if (!tables.isEmpty()){ - Iris.debug("IrisLootEvent has been accessed"); - } - - if (tables.isEmpty()) - return; + if (tables.isEmpty()) return; InventoryHolder m = (InventoryHolder) block.getState(); addItems(false, m.getInventory(), rx, tables, slot, c.getWorld(), x, y, z, 15); @@ -483,13 +478,23 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat @BlockCoordinates @Override default KList getLootTables(RNG rng, Block b) { + MantleChunk mc = getMantle().getMantle().getChunk(b.getChunk()).use(); + try { + return getLootTables(rng, b, mc); + } finally { + mc.release(); + } + } + + @BlockCoordinates + default KList getLootTables(RNG rng, Block b, MantleChunk mc) { int rx = b.getX(); int rz = b.getZ(); int ry = b.getY() - getWorld().minHeight(); double he = getComplex().getHeightStream().get(rx, rz); KList tables = new KList<>(); - PlacedObject po = getObjectPlacement(rx, ry, rz); + PlacedObject po = getObjectPlacement(rx, ry, rz, mc); if (po != null && po.getPlacement() != null) { if (B.isStorageChest(b.getBlockData())) { IrisLootTable table = po.getPlacement().getTable(b.getBlockData(), getData()); @@ -812,7 +817,16 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat } default PlacedObject getObjectPlacement(int x, int y, int z) { - String objectAt = getMantle().getMantle().get(x, y, z, String.class); + MantleChunk chunk = getMantle().getMantle().getChunk(x >> 4, z >> 4).use(); + try { + return getObjectPlacement(x, y, z, chunk); + } finally { + chunk.release(); + } + } + + default PlacedObject getObjectPlacement(int x, int y, int z, MantleChunk chunk) { + String objectAt = chunk.get(x & 15, y, z & 15, String.class); if (objectAt == null || objectAt.isEmpty()) { return null; } @@ -822,7 +836,7 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat int id = Integer.parseInt(v[1]); - JigsawPieceContainer container = getMantle().getMantle().get(x, y, z, JigsawPieceContainer.class); + JigsawPieceContainer container = chunk.get(x & 15, y, z & 15, JigsawPieceContainer.class); if (container != null) { IrisJigsawPiece piece = container.load(getData()); if (piece.getObject().equals(object)) diff --git a/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java index b228eb00f..c6762e0b4 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/placer/HeightmapObjectPlacer.java @@ -24,7 +24,6 @@ import com.volmit.iris.engine.object.IObjectPlacer; import com.volmit.iris.engine.object.IrisObjectPlacement; import com.volmit.iris.engine.object.TileData; import com.volmit.iris.util.math.RNG; -import org.bukkit.block.TileState; import org.bukkit.block.data.BlockData; public class HeightmapObjectPlacer implements IObjectPlacer { @@ -83,6 +82,16 @@ public class HeightmapObjectPlacer implements IObjectPlacer { oplacer.setTile(param1Int1, param1Int2, param1Int3, param1TileData); } + @Override + public void setData(int xx, int yy, int zz, T data) { + oplacer.setData(xx, yy, zz, data); + } + + @Override + public T getData(int xx, int yy, int zz, Class t) { + return oplacer.getData(xx, yy, zz, t); + } + @Override public Engine getEngine() { return null; diff --git a/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java index d7a943eda..e75f00c6b 100644 --- a/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/framework/placer/WorldObjectPlacer.java @@ -59,6 +59,11 @@ public class WorldObjectPlacer implements IObjectPlacer { slot = InventorySlotType.STORAGE; } + if (d instanceof IrisCustomData data) { + block.setBlockData(data.getBase(), false); + Iris.warn("Tried to place custom block at " + x + ", " + y + ", " + z + " which is not supported!"); + } else block.setBlockData(d, false); + if (slot != null) { RNG rx = new RNG(Cache.key(x, z)); KList tables = engine.getLootTables(rx, block); @@ -78,12 +83,6 @@ public class WorldObjectPlacer implements IObjectPlacer { Iris.reportError(e); } } - - - if (d instanceof IrisCustomData data) { - block.setBlockData(data.getBase()); - Iris.warn("Tried to place custom block at " + x + ", " + y + ", " + z + " which is not supported!"); - } else block.setBlockData(d); } @Override @@ -125,4 +124,13 @@ public class WorldObjectPlacer implements IObjectPlacer { public void setTile(int xx, int yy, int zz, TileData tile) { tile.toBukkitTry(world.getBlockAt(xx, yy + world.getMinHeight(), zz)); } + + @Override + public void setData(int xx, int yy, int zz, T data) { + } + + @Override + public T getData(int xx, int yy, int zz, Class t) { + return null; + } } diff --git a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java index c045cf922..96be7cbc8 100644 --- a/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java +++ b/core/src/main/java/com/volmit/iris/engine/jigsaw/PlannedStructure.java @@ -154,9 +154,9 @@ public class PlannedStructure { JigsawStructureContainer structure = JigsawStructureContainer.toContainer(getStructure()); i.setRealPositions(xx, height, zz, placer); return v.place(xx, height, zz, placer, options, rng, (b, data) -> { - e.set(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id); - e.set(b.getX(), b.getY(), b.getZ(), structure); - e.set(b.getX(), b.getY(), b.getZ(), piece); + placer.setData(b.getX(), b.getY(), b.getZ(), v.getLoadKey() + "@" + id); + placer.setData(b.getX(), b.getY(), b.getZ(), structure); + placer.setData(b.getX(), b.getY(), b.getZ(), piece); }, null, getData().getEngine() != null ? getData() : eng.getData()) != -1; } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java index 16157fb6e..0ebe6cc53 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/EngineMantle.java @@ -29,8 +29,6 @@ import com.volmit.iris.engine.mantle.components.MantleObjectComponent; import com.volmit.iris.engine.object.IrisDimension; import com.volmit.iris.engine.object.IrisPosition; import com.volmit.iris.util.collection.KList; -import com.volmit.iris.util.context.ChunkContext; -import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.util.data.B; import com.volmit.iris.util.documentation.BlockCoordinates; import com.volmit.iris.util.documentation.ChunkCoordinates; @@ -38,7 +36,6 @@ import com.volmit.iris.util.hunk.Hunk; import com.volmit.iris.util.mantle.Mantle; import com.volmit.iris.util.mantle.MantleChunk; import com.volmit.iris.util.mantle.flag.MantleFlag; -import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.matter.*; import com.volmit.iris.util.matter.slices.UpdateMatter; import com.volmit.iris.util.parallel.MultiBurst; @@ -49,10 +46,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import static com.volmit.iris.util.parallel.StreamUtils.forEach; -import static com.volmit.iris.util.parallel.StreamUtils.streamRadius; - -public interface EngineMantle { +public interface EngineMantle extends MatterGenerator { BlockData AIR = B.get("AIR"); Mantle getMantle(); @@ -180,49 +174,6 @@ public interface EngineMantle { return getEngine().burst(); } - @ChunkCoordinates - default void generateMatter(int x, int z, boolean multicore, ChunkContext context) { - if (!getEngine().getDimension().isUseMantle() || getMantle().hasFlag(x, z, MantleFlag.PLANNED)) { - return; - } - - try (MantleWriter writer = getMantle().write(this, x, z, getRadius() * 2)) { - var iterator = getComponents().iterator(); - while (iterator.hasNext()) { - var pair = iterator.next(); - int radius = pair.getB(); - boolean last = !iterator.hasNext(); - forEach(streamRadius(x, z, radius), - pos -> pair.getA() - .stream() - .filter(MantleComponent::isEnabled) - .map(c -> new Pair<>(c, pos)), - p -> { - MantleComponent c = p.getA(); - Position2 pos = p.getB(); - int xx = pos.getX(); - int zz = pos.getZ(); - IrisContext.getOr(getEngine()).setChunkContext(context); - generateMantleComponent(writer, xx, zz, c, writer.acquireChunk(xx, zz), context); - }, - multicore ? burst() : null - ); - - if (!last) continue; - forEach(streamRadius(x, z, radius), - p -> writer.acquireChunk(x, z).flag(MantleFlag.PLANNED, true), - multicore ? burst() : null - ); - } - } - } - - default void generateMantleComponent(MantleWriter writer, int x, int z, MantleComponent c, MantleChunk mc, ChunkContext context) { - mc.raiseFlag(MantleFlag.PLANNED, c.getFlag(), () -> { - if (c.isEnabled()) c.generateLayer(writer, x, z, context); - }); - } - @ChunkCoordinates default void insertMatter(int x, int z, Class t, Hunk blocks, boolean multicore) { if (!getEngine().getDimension().isUseMantle()) { @@ -262,9 +213,6 @@ public interface EngineMantle { default int getLoadedRegionCount() { return getMantle().getLoadedRegionCount(); } - default long getLastUseMapMemoryUsage(){ - return getMantle().LastUseMapMemoryUsage(); - } MantleJigsawComponent getJigsawComponent(); @@ -290,7 +238,7 @@ public interface EngineMantle { if (!isCovered(x, z)) return; MantleChunk chunk = getMantle().getChunk(x, z).use(); try { - chunk.raiseFlag(MantleFlag.CLEANED, () -> { + chunk.raiseFlagUnchecked(MantleFlag.CLEANED, () -> { chunk.deleteSlices(BlockData.class); chunk.deleteSlices(String.class); chunk.deleteSlices(MatterCavern.class); @@ -301,7 +249,7 @@ public interface EngineMantle { } } - default long getUnloadRegionCount() { + default int getUnloadRegionCount() { return getMantle().getUnloadRegionCount(); } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java index 408704925..f5da22c02 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/MantleWriter.java @@ -44,9 +44,7 @@ import lombok.Data; import org.bukkit.block.data.BlockData; import org.bukkit.util.Vector; -import java.util.HashSet; -import java.util.List; -import java.util.Set; +import java.util.*; import static com.volmit.iris.engine.mantle.EngineMantle.AIR; @@ -54,20 +52,21 @@ import static com.volmit.iris.engine.mantle.EngineMantle.AIR; public class MantleWriter implements IObjectPlacer, AutoCloseable { private final EngineMantle engineMantle; private final Mantle mantle; - private final KMap cachedChunks; + private final Map cachedChunks; private final int radius; private final int x; private final int z; - public MantleWriter(EngineMantle engineMantle, Mantle mantle, int x, int z, int radius) { + public MantleWriter(EngineMantle engineMantle, Mantle mantle, int x, int z, int radius, boolean multicore) { this.engineMantle = engineMantle; this.mantle = mantle; - this.cachedChunks = new KMap<>(); - this.radius = radius; + this.radius = radius * 2; + int d = this.radius + 1; + this.cachedChunks = multicore ? new KMap<>(d * d, 0.75f, Math.max(32, Runtime.getRuntime().availableProcessors() * 4)) : new HashMap<>(d * d); this.x = x; this.z = z; - int r = radius / 4; + int r = radius / 2; for (int i = -r; i <= r; i++) { for (int j = -r; j <= r; j++) { cachedChunks.put(Cache.key(i + x, j + z), mantle.getChunk(i + x, j + z).use()); @@ -182,8 +181,13 @@ public class MantleWriter implements IObjectPlacer, AutoCloseable { Iris.error("Mantle Writer Accessed chunk out of bounds" + cx + "," + cz); return null; } - MantleChunk chunk = cachedChunks.computeIfAbsent(Cache.key(cx, cz), k -> mantle.getChunk(cx, cz).use()); - if (chunk == null) Iris.error("Mantle Writer Accessed " + cx + "," + cz + " and came up null (and yet within bounds!)"); + final Long key = Cache.key(cx, cz); + MantleChunk chunk = cachedChunks.get(key); + if (chunk == null) { + chunk = mantle.getChunk(cx, cz).use(); + var old = cachedChunks.put(key, chunk); + if (old != null) old.release(); + } return chunk; } diff --git a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java index 8e167a73e..fe4e45f0a 100644 --- a/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java +++ b/core/src/main/java/com/volmit/iris/engine/mantle/components/MantleJigsawComponent.java @@ -35,7 +35,6 @@ import com.volmit.iris.util.math.Position2; import com.volmit.iris.util.math.RNG; import com.volmit.iris.util.matter.slices.container.JigsawStructuresContainer; import com.volmit.iris.util.noise.CNG; -import lombok.Getter; import org.jetbrains.annotations.Nullable; import java.util.List; @@ -45,7 +44,7 @@ public class MantleJigsawComponent extends IrisMantleComponent { private final CNG cng; public MantleJigsawComponent(EngineMantle engineMantle) { - super(engineMantle, ReservedFlag.JIGSAW, 2); + super(engineMantle, ReservedFlag.JIGSAW, 1); cng = NoiseStyle.STATIC.create(new RNG(jigsaw())); } diff --git a/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java b/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java index dfa7bc1e8..f6fcd52c0 100644 --- a/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java +++ b/core/src/main/java/com/volmit/iris/engine/modifier/IrisDepositModifier.java @@ -119,7 +119,7 @@ public class IrisDepositModifier extends EngineAssignedModifier { if (y > k.getMaxHeight() || y < k.getMinHeight() || y > height - 2) continue; - for (BlockVector j : clump.getBlocks().keySet()) { + for (BlockVector j : clump.getBlocks().keys()) { int nx = j.getBlockX() + x; int ny = j.getBlockY() + y; int nz = j.getBlockZ() + z; diff --git a/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java b/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java index 3a7a89a38..9e2183b1c 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IObjectPlacer.java @@ -20,8 +20,8 @@ package com.volmit.iris.engine.object; import com.volmit.iris.core.loader.IrisData; import com.volmit.iris.engine.framework.Engine; -import org.bukkit.block.TileState; import org.bukkit.block.data.BlockData; +import org.jetbrains.annotations.Nullable; public interface IObjectPlacer { int getHighest(int x, int z, IrisData data); @@ -46,5 +46,9 @@ public interface IObjectPlacer { void setTile(int xx, int yy, int zz, TileData tile); + void setData(int xx, int yy, int zz, T data); + + @Nullable T getData(int xx, int yy, int zz, Class t); + Engine getEngine(); } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisCompat.java b/core/src/main/java/com/volmit/iris/engine/object/IrisCompat.java index 7f2d8ed73..8d2b32fb6 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisCompat.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisCompat.java @@ -122,6 +122,10 @@ public class IrisCompat { private static KList getDefaultBlockCompatabilityFilters() { KList filters = new KList<>(); + filters.add(new IrisCompatabilityBlockFilter("CHAIN", "IRON_CHAIN")); + filters.add(new IrisCompatabilityBlockFilter("GRASS", "SHORT_GRASS")); + filters.add(new IrisCompatabilityBlockFilter("SHORT_GRASS", "GRASS")); + // Below 1.16 filters.add(new IrisCompatabilityBlockFilter("WEEPING_VINES", "NETHER_FENCE")); filters.add(new IrisCompatabilityBlockFilter("WEEPING_VINES_PLANT", "NETHER_FENCE")); @@ -160,7 +164,7 @@ public class IrisCompat { filters.add(new IrisCompatabilityBlockFilter("CRACKED_NETHER_BRICKS", "NETHER_BRICKS")); filters.add(new IrisCompatabilityBlockFilter("CHISELED_NETHER_BRICKS", "NETHER_BRICKS")); filters.add(new IrisCompatabilityBlockFilter("NETHER_FENCE", "LEGACY_NETHER_FENCE")); - filters.add(new IrisCompatabilityBlockFilter("CHAIN", "IRON_BARS")); + filters.add(new IrisCompatabilityBlockFilter("IRON_CHAIN", "IRON_BARS")); filters.add(new IrisCompatabilityBlockFilter("NETHERITE_BLOCK", "QUARTZ_BLOCK")); filters.add(new IrisCompatabilityBlockFilter("BLACKSTONE", "COBBLESTONE")); filters.add(new IrisCompatabilityBlockFilter("BASALT", "STONE")); @@ -254,7 +258,6 @@ public class IrisCompat { filters.add(new IrisCompatabilityBlockFilter("BAMBOO", "BIRCH_FENCE")); filters.add(new IrisCompatabilityBlockFilter("BAMBOO_SAPLING", "BIRCH_SAPLING")); filters.add(new IrisCompatabilityBlockFilter("POTTED_BAMBOO", "POTTED_BIRCH_SAPLING")); - filters.add(new IrisCompatabilityBlockFilter("GRASS", "SHORT_GRASS")); return filters; } diff --git a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java index 62a11491c..8f6336990 100644 --- a/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java +++ b/core/src/main/java/com/volmit/iris/engine/object/IrisObject.java @@ -30,6 +30,7 @@ import com.volmit.iris.util.collection.KMap; import com.volmit.iris.util.context.IrisContext; import com.volmit.iris.util.data.B; import com.volmit.iris.util.data.IrisCustomData; +import com.volmit.iris.util.data.VectorMap; import com.volmit.iris.util.format.Form; import com.volmit.iris.util.interpolation.IrisInterpolation; import com.volmit.iris.util.json.JSONObject; @@ -38,7 +39,6 @@ import com.volmit.iris.util.matter.MatterMarker; import com.volmit.iris.util.parallel.BurstExecutor; import com.volmit.iris.util.parallel.MultiBurst; import com.volmit.iris.util.plugin.VolmitSender; -import com.volmit.iris.util.scheduling.IrisLock; import com.volmit.iris.util.scheduling.PrecisionStopwatch; import com.volmit.iris.util.scheduling.jobs.Job; import com.volmit.iris.util.stream.ProceduralStream; @@ -62,7 +62,10 @@ import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.BiConsumer; +import java.util.stream.StreamSupport; @Accessors(chain = true) @EqualsAndHashCode(callSuper = false) @@ -72,17 +75,17 @@ public class IrisObject extends IrisRegistrant { protected static final BlockData VAIR = B.get("VOID_AIR"); protected static final BlockData VAIR_DEBUG = B.get("COBWEB"); protected static final BlockData[] SNOW_LAYERS = new BlockData[]{B.get("minecraft:snow[layers=1]"), B.get("minecraft:snow[layers=2]"), B.get("minecraft:snow[layers=3]"), B.get("minecraft:snow[layers=4]"), B.get("minecraft:snow[layers=5]"), B.get("minecraft:snow[layers=6]"), B.get("minecraft:snow[layers=7]"), B.get("minecraft:snow[layers=8]")}; - protected transient final IrisLock readLock = new IrisLock("read-conclock"); + protected transient final Lock readLock; + protected transient final Lock writeLock; @Getter @Setter protected transient volatile boolean smartBored = false; - @Getter - @Setter - protected transient IrisLock lock = new IrisLock("Preloadcache"); @Setter protected transient AtomicCache aabb = new AtomicCache<>(); - private KMap blocks; - private KMap states; + @Getter + private VectorMap blocks; + @Getter + private VectorMap states; @Getter @Setter private int w; @@ -97,12 +100,15 @@ public class IrisObject extends IrisRegistrant { private transient Vector3i center; public IrisObject(int w, int h, int d) { - blocks = new KMap<>(); - states = new KMap<>(); + blocks = new VectorMap<>(); + states = new VectorMap<>(); this.w = w; this.h = h; this.d = d; center = new Vector3i(w / 2, h / 2, d / 2); + var lock = new ReentrantReadWriteLock(); + readLock = lock.readLock(); + writeLock = lock.writeLock(); } public IrisObject() { @@ -158,10 +164,10 @@ public class IrisObject extends IrisRegistrant { PrecisionStopwatch p = PrecisionStopwatch.start(); BlockData vair = debug ? VAIR_DEBUG : VAIR; - lock.lock(); + writeLock.lock(); AtomicInteger applied = new AtomicInteger(); - if (getBlocks().isEmpty()) { - lock.unlock(); + if (blocks.isEmpty()) { + writeLock.unlock(); Iris.warn("Cannot Smart Bore " + getLoadKey() + " because it has 0 blocks in it."); smartBored = true; return; @@ -170,7 +176,7 @@ public class IrisObject extends IrisRegistrant { BlockVector max = new BlockVector(Double.MIN_VALUE, Double.MIN_VALUE, Double.MIN_VALUE); BlockVector min = new BlockVector(Double.MAX_VALUE, Double.MAX_VALUE, Double.MAX_VALUE); - for (BlockVector i : getBlocks().keySet()) { + for (BlockVector i : blocks.keys()) { max.setX(Math.max(i.getX(), max.getX())); min.setX(Math.min(i.getX(), min.getX())); max.setY(Math.max(i.getY(), max.getY())); @@ -190,7 +196,7 @@ public class IrisObject extends IrisRegistrant { int end = Integer.MIN_VALUE; for (int ray = min.getBlockX(); ray <= max.getBlockX(); ray++) { - if (getBlocks().containsKey(new Vector3i(ray, finalRayY, rayZ))) { + if (blocks.containsKey(new Vector3i(ray, finalRayY, rayZ))) { start = Math.min(ray, start); end = Math.max(ray, end); } @@ -200,8 +206,8 @@ public class IrisObject extends IrisRegistrant { for (int i = start; i <= end; i++) { Vector3i v = new Vector3i(i, finalRayY, rayZ); - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); + if (!vair.equals(blocks.get(v))) { + blocks.computeIfAbsent(v, (vv) -> vair); applied.getAndIncrement(); } } @@ -219,7 +225,7 @@ public class IrisObject extends IrisRegistrant { int end = Integer.MIN_VALUE; for (int ray = min.getBlockY(); ray <= max.getBlockY(); ray++) { - if (getBlocks().containsKey(new Vector3i(finalRayX, ray, rayZ))) { + if (blocks.containsKey(new Vector3i(finalRayX, ray, rayZ))) { start = Math.min(ray, start); end = Math.max(ray, end); } @@ -229,8 +235,8 @@ public class IrisObject extends IrisRegistrant { for (int i = start; i <= end; i++) { Vector3i v = new Vector3i(finalRayX, i, rayZ); - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); + if (!vair.equals(blocks.get(v))) { + blocks.computeIfAbsent(v, (vv) -> vair); applied.getAndIncrement(); } } @@ -248,7 +254,7 @@ public class IrisObject extends IrisRegistrant { int end = Integer.MIN_VALUE; for (int ray = min.getBlockZ(); ray <= max.getBlockZ(); ray++) { - if (getBlocks().containsKey(new Vector3i(finalRayX, rayY, ray))) { + if (blocks.containsKey(new Vector3i(finalRayX, rayY, ray))) { start = Math.min(ray, start); end = Math.max(ray, end); } @@ -258,8 +264,8 @@ public class IrisObject extends IrisRegistrant { for (int i = start; i <= end; i++) { Vector3i v = new Vector3i(finalRayX, rayY, i); - if (!B.isAir(getBlocks().get(v))) { - getBlocks().computeIfAbsent(v, (vv) -> vair); + if (!vair.equals(blocks.get(v))) { + blocks.computeIfAbsent(v, (vv) -> vair); applied.getAndIncrement(); } } @@ -270,7 +276,7 @@ public class IrisObject extends IrisRegistrant { burst.complete(); smartBored = true; - lock.unlock(); + writeLock.unlock(); Iris.debug("Smart Bore: " + getLoadKey() + " in " + Form.duration(p.getMilliseconds(), 2) + " (" + Form.f(applied.get()) + ")"); } @@ -281,13 +287,8 @@ public class IrisObject extends IrisRegistrant { o.setLoadFile(getLoadFile()); o.setCenter(getCenter().clone()); - for (Vector3i i : getBlocks().keySet()) { - o.getBlocks().put(i.clone(), Objects.requireNonNull(getBlocks().get(i)).clone()); - } - - for (Vector3i i : getStates().keySet()) { - o.getStates().put(i.clone(), Objects.requireNonNull(getStates().get(i)).clone()); - } + blocks.forEach((i, v) -> o.blocks.put(i.clone(), v.clone())); + states.forEach((i, v) -> o.states.put(i.clone(), v.clone())); return o; } @@ -301,14 +302,14 @@ public class IrisObject extends IrisRegistrant { int s = din.readInt(); for (int i = 0; i < s; i++) { - getBlocks().put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(din.readUTF())); + blocks.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(din.readUTF())); } try { int size = din.readInt(); for (int i = 0; i < size; i++) { - getStates().put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); + states.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); } } catch (Throwable e) { Iris.reportError(e); @@ -336,13 +337,13 @@ public class IrisObject extends IrisRegistrant { s = din.readInt(); for (i = 0; i < s; i++) { - getBlocks().put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(palette.get(din.readShort()))); + blocks.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), B.get(palette.get(din.readShort()))); } s = din.readInt(); for (i = 0; i < s; i++) { - getStates().put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); + states.put(new Vector3i(din.readShort(), din.readShort(), din.readShort()), TileData.read(din)); } } @@ -354,7 +355,7 @@ public class IrisObject extends IrisRegistrant { dos.writeUTF("Iris V2 IOB;"); KList palette = new KList<>(); - for (BlockData i : getBlocks().values()) { + for (BlockData i : blocks.values()) { palette.addIfMissing(i.getAsString()); } @@ -364,21 +365,23 @@ public class IrisObject extends IrisRegistrant { dos.writeUTF(i); } - dos.writeInt(getBlocks().size()); + dos.writeInt(blocks.size()); - for (Vector3i i : getBlocks().keySet()) { + for (var entry : blocks) { + var i = entry.getKey(); dos.writeShort(i.getBlockX()); dos.writeShort(i.getBlockY()); dos.writeShort(i.getBlockZ()); - dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); + dos.writeShort(palette.indexOf(entry.getValue().getAsString())); } - dos.writeInt(getStates().size()); - for (Vector3i i : getStates().keySet()) { + dos.writeInt(states.size()); + for (var entry : states) { + var i = entry.getKey(); dos.writeShort(i.getBlockX()); dos.writeShort(i.getBlockY()); dos.writeShort(i.getBlockZ()); - getStates().get(i).toBinary(dos); + entry.getValue().toBinary(dos); } } @@ -386,7 +389,7 @@ public class IrisObject extends IrisRegistrant { AtomicReference ref = new AtomicReference<>(); CountDownLatch latch = new CountDownLatch(1); new Job() { - private int total = getBlocks().size() * 3 + getStates().size(); + private int total = blocks.size() * 3 + states.size(); private int c = 0; @Override @@ -405,11 +408,11 @@ public class IrisObject extends IrisRegistrant { KList palette = new KList<>(); - for (BlockData i : getBlocks().values()) { + for (BlockData i : blocks.values()) { palette.addIfMissing(i.getAsString()); ++c; } - total -= getBlocks().size() - palette.size(); + total -= blocks.size() - palette.size(); dos.writeShort(palette.size()); @@ -418,22 +421,24 @@ public class IrisObject extends IrisRegistrant { ++c; } - dos.writeInt(getBlocks().size()); + dos.writeInt(blocks.size()); - for (Vector3i i : getBlocks().keySet()) { + for (var entry : blocks) { + var i = entry.getKey(); dos.writeShort(i.getBlockX()); dos.writeShort(i.getBlockY()); dos.writeShort(i.getBlockZ()); - dos.writeShort(palette.indexOf(getBlocks().get(i).getAsString())); + dos.writeShort(palette.indexOf(entry.getValue().getAsString())); ++c; } - dos.writeInt(getStates().size()); - for (Vector3i i : getStates().keySet()) { + dos.writeInt(states.size()); + for (var entry : states) { + var i = entry.getKey(); dos.writeShort(i.getBlockX()); dos.writeShort(i.getBlockY()); dos.writeShort(i.getBlockZ()); - getStates().get(i).toBinary(dos); + entry.getValue().toBinary(dos); ++c; } } catch (IOException e) { @@ -502,7 +507,7 @@ public class IrisObject extends IrisRegistrant { BlockVector min = new BlockVector(); BlockVector max = new BlockVector(); - for (BlockVector i : getBlocks().keySet()) { + for (BlockVector i : blocks.keys()) { min.setX(Math.min(min.getX(), i.getX())); min.setY(Math.min(min.getY(), i.getY())); min.setZ(Math.min(min.getZ(), i.getZ())); @@ -518,21 +523,11 @@ public class IrisObject extends IrisRegistrant { } public void clean() { - KMap d = new KMap<>(); + VectorMap d = new VectorMap<>(); + d.putAll(blocks); - for (Vector3i i : getBlocks().keySet()) { - d.put(new Vector3i(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); - } - - KMap dx = new KMap<>(); - - for (Vector3i i : getBlocks().keySet()) { - d.put(new Vector3i(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getBlocks().get(i))); - } - - for (Vector3i i : getStates().keySet()) { - dx.put(new Vector3i(i.getBlockX(), i.getBlockY(), i.getBlockZ()), Objects.requireNonNull(getStates().get(i))); - } + VectorMap dx = new VectorMap<>(); + dx.putAll(states); blocks = d; states = dx; @@ -550,10 +545,10 @@ public class IrisObject extends IrisRegistrant { Vector3i v = getSigned(x, y, z); if (block == null) { - getBlocks().remove(v); - getStates().remove(v); + blocks.remove(v); + states.remove(v); } else { - getBlocks().put(v, block); + blocks.put(v, block); } } @@ -561,15 +556,15 @@ public class IrisObject extends IrisRegistrant { Vector3i v = getSigned(x, y, z); if (block == null) { - getBlocks().remove(v); - getStates().remove(v); + blocks.remove(v); + states.remove(v); } else { BlockData data = block.getBlockData(); - getBlocks().put(v, data); + blocks.put(v, data); TileData state = TileData.getTileState(block, legacy); if (state != null) { Iris.debug("Saved State " + v); - getStates().put(v, state); + states.put(v, state); } } } @@ -866,6 +861,9 @@ public class IrisObject extends IrisRegistrant { try { if (config.getMarkers().isNotEmpty() && placer.getEngine() != null) { markers = new KMap<>(); + var list = StreamSupport.stream(blocks.keys().spliterator(), false) + .collect(KList.collector()); + for (IrisObjectMarker j : config.getMarkers()) { IrisMarker marker = getLoader().getMarkerLoader().load(j.getMarker()); @@ -874,13 +872,12 @@ public class IrisObject extends IrisRegistrant { } int max = j.getMaximumMarkers(); - - for (Vector3i i : getBlocks().k().shuffle()) { + for (BlockVector i : list.shuffle()) { if (max <= 0) { break; } - BlockData data = getBlocks().get(i); + BlockData data = blocks.get(i); for (BlockData k : j.getMark(rdata)) { if (max <= 0) { @@ -888,8 +885,8 @@ public class IrisObject extends IrisRegistrant { } if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) { - boolean a = !blocks.containsKey((Vector3i) i.clone().add(new BlockVector(0, 1, 0))); - boolean fff = !blocks.containsKey((Vector3i) i.clone().add(new BlockVector(0, 2, 0))); + boolean a = !blocks.containsKey((BlockVector) i.clone().add(new BlockVector(0, 1, 0))); + boolean fff = !blocks.containsKey((BlockVector) i.clone().add(new BlockVector(0, 2, 0))); if (!marker.isEmptyAbove() || (a && fff)) { markers.put(i, j.getMarker()); @@ -901,14 +898,14 @@ public class IrisObject extends IrisRegistrant { } } - for (var entry : getBlocks().entrySet()) { + for (var entry : blocks) { var g = entry.getKey(); BlockData d; TileData tile = null; try { d = entry.getValue(); - tile = getStates().get(g); + tile = states.get(g); } catch (Throwable e) { Iris.reportError(e); Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (cme)"); @@ -1026,12 +1023,12 @@ public class IrisObject extends IrisRegistrant { if (stilting) { readLock.lock(); IrisStiltSettings settings = config.getStiltSettings(); - for (Vector3i g : getBlocks().keySet()) { + for (BlockVector g : blocks.keys()) { BlockData d; if (settings == null || settings.getPalette() == null) { try { - d = getBlocks().get(g); + d = blocks.get(g); } catch (Throwable e) { Iris.reportError(e); Iris.warn("Failed to read block node " + g.getBlockX() + "," + g.getBlockY() + "," + g.getBlockZ() + " in object " + getLoadKey() + " (stilt cme)"); @@ -1138,58 +1135,60 @@ public class IrisObject extends IrisRegistrant { } public void rotate(IrisObjectRotation r, int spinx, int spiny, int spinz) { - KMap d = new KMap<>(); + writeLock.lock(); + VectorMap d = new VectorMap<>(); - for (Vector3i i : getBlocks().keySet()) { - d.put(new Vector3i(r.rotate(i, spinx, spiny, spinz)), r.rotate(getBlocks().get(i).clone(), spinx, spiny, spinz)); + for (var entry : blocks) { + d.put(r.rotate(entry.getKey(), spinx, spiny, spinz), r.rotate(entry.getValue(), spinx, spiny, spinz)); } - KMap dx = new KMap<>(); + VectorMap dx = new VectorMap<>(); - for (Vector3i i : getStates().keySet()) { - dx.put(new Vector3i(r.rotate(i, spinx, spiny, spinz)), getStates().get(i)); + for (var entry : states) { + dx.put(r.rotate(entry.getKey(), spinx, spiny, spinz), entry.getValue()); } blocks = d; states = dx; + writeLock.unlock(); shrinkwrap(); } public void place(Location at) { - for (Vector3i i : getBlocks().keySet()) { + readLock.lock(); + for (var entry : blocks) { + var i = entry.getKey(); Block b = at.clone().add(0, getCenter().getY(), 0).add(i).getBlock(); - b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); + b.setBlockData(Objects.requireNonNull(entry.getValue()), false); - if (getStates().containsKey(i)) { + if (states.containsKey(i)) { Iris.info(Objects.requireNonNull(states.get(i)).toString()); - Objects.requireNonNull(getStates().get(i)).toBukkitTry(b); + Objects.requireNonNull(states.get(i)).toBukkitTry(b); } } + readLock.unlock(); } public void placeCenterY(Location at) { - for (Vector3i i : getBlocks().keySet()) { + readLock.lock(); + for (var entry : blocks) { + var i = entry.getKey(); Block b = at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock(); - b.setBlockData(Objects.requireNonNull(getBlocks().get(i)), false); + b.setBlockData(Objects.requireNonNull(entry.getValue()), false); - if (getStates().containsKey(i)) { - Objects.requireNonNull(getStates().get(i)).toBukkitTry(b); + if (states.containsKey(i)) { + Objects.requireNonNull(states.get(i)).toBukkitTry(b); } } - } - - public synchronized KMap getBlocks() { - return blocks; - } - - public synchronized KMap getStates() { - return states; + readLock.unlock(); } public void unplaceCenterY(Location at) { - for (BlockVector i : getBlocks().keySet()) { + readLock.lock(); + for (BlockVector i : blocks.keys()) { at.clone().add(getCenter().getX(), getCenter().getY(), getCenter().getZ()).add(i).getBlock().setBlockData(AIR, false); } + readLock.unlock(); } public IrisObject scaled(double scale, IrisObjectPlacementScaleInterpolator interpolation) { @@ -1201,7 +1200,7 @@ public class IrisObject extends IrisRegistrant { IrisPosition l1 = getAABB().max(); IrisPosition l2 = getAABB().min(); - @SuppressWarnings({"unchecked", "rawtypes"}) HashMap placeBlock = new HashMap(); + VectorMap placeBlock = new VectorMap<>(); Vector center = getCenter(); if (getH() == 2) { @@ -1216,17 +1215,19 @@ public class IrisObject extends IrisRegistrant { IrisObject oo = new IrisObject((int) Math.ceil((w * scale) + (scale * 2)), (int) Math.ceil((h * scale) + (scale * 2)), (int) Math.ceil((d * scale) + (scale * 2))); - for (Map.Entry entry : blocks.entrySet()) { + readLock.lock(); + for (var entry : blocks) { BlockData bd = entry.getValue(); placeBlock.put(entry.getKey().clone().add(HALF).subtract(center) .multiply(scale).add(sm1).toBlockVector(), bd); } + readLock.unlock(); - for (Map.Entry entry : placeBlock.entrySet()) { + for (var entry : placeBlock) { BlockVector v = entry.getKey(); if (scale > 1) { for (BlockVector vec : blocksBetweenTwoPoints(v.clone().add(center), v.clone().add(center).add(sm1))) { - oo.getBlocks().put(new Vector3i(vec), entry.getValue()); + oo.blocks.put(vec, entry.getValue()); } } else { oo.setUnsigned(v.getBlockX(), v.getBlockY(), v.getBlockZ(), entry.getValue()); @@ -1245,8 +1246,9 @@ public class IrisObject extends IrisRegistrant { } public void trilinear(int rad) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); + writeLock.lock(); + VectorMap v = blocks; + VectorMap b = new VectorMap<>(); BlockVector min = getAABB().minbv(); BlockVector max = getAABB().maxbv(); @@ -1254,7 +1256,7 @@ public class IrisObject extends IrisRegistrant { for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { if (IrisInterpolation.getTrilinear(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new Vector3i((int) xx, (int) yy, (int) zz)); + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); if (data == null || data.getMaterial().isAir()) { return 0; @@ -1262,20 +1264,22 @@ public class IrisObject extends IrisRegistrant { return 1; }) >= 0.5) { - b.put(new Vector3i(x, y, z), nearestBlockData(x, y, z)); + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); } else { - b.put(new Vector3i(x, y, z), AIR); + b.put(new BlockVector(x, y, z), AIR); } } } } blocks = b; + writeLock.unlock(); } public void tricubic(int rad) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); + writeLock.lock(); + VectorMap v = blocks; + VectorMap b = new VectorMap<>(); BlockVector min = getAABB().minbv(); BlockVector max = getAABB().maxbv(); @@ -1283,7 +1287,7 @@ public class IrisObject extends IrisRegistrant { for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { if (IrisInterpolation.getTricubic(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new Vector3i((int) xx, (int) yy, (int) zz)); + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); if (data == null || data.getMaterial().isAir()) { return 0; @@ -1291,15 +1295,16 @@ public class IrisObject extends IrisRegistrant { return 1; }) >= 0.5) { - b.put(new Vector3i(x, y, z), nearestBlockData(x, y, z)); + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); } else { - b.put(new Vector3i(x, y, z), AIR); + b.put(new BlockVector(x, y, z), AIR); } } } } blocks = b; + writeLock.unlock(); } public void trihermite(int rad) { @@ -1307,8 +1312,9 @@ public class IrisObject extends IrisRegistrant { } public void trihermite(int rad, double tension, double bias) { - KMap v = getBlocks().copy(); - KMap b = new KMap<>(); + writeLock.lock(); + VectorMap v = blocks; + VectorMap b = new VectorMap<>(); BlockVector min = getAABB().minbv(); BlockVector max = getAABB().maxbv(); @@ -1316,7 +1322,7 @@ public class IrisObject extends IrisRegistrant { for (int y = min.getBlockY(); y <= max.getBlockY(); y++) { for (int z = min.getBlockZ(); z <= max.getBlockZ(); z++) { if (IrisInterpolation.getTrihermite(x, y, z, rad, (xx, yy, zz) -> { - BlockData data = v.get(new Vector3i((int) xx, (int) yy, (int) zz)); + BlockData data = v.get(new BlockVector((int) xx, (int) yy, (int) zz)); if (data == null || data.getMaterial().isAir()) { return 0; @@ -1324,20 +1330,22 @@ public class IrisObject extends IrisRegistrant { return 1; }, tension, bias) >= 0.5) { - b.put(new Vector3i(x, y, z), nearestBlockData(x, y, z)); + b.put(new BlockVector(x, y, z), nearestBlockData(x, y, z)); } else { - b.put(new Vector3i(x, y, z), AIR); + b.put(new BlockVector(x, y, z), AIR); } } } } blocks = b; + writeLock.unlock(); } private BlockData nearestBlockData(int x, int y, int z) { BlockVector vv = new BlockVector(x, y, z); - BlockData r = getBlocks().get(vv); + readLock.lock(); + BlockData r = blocks.get(vv); if (r != null && !r.getMaterial().isAir()) { return r; @@ -1345,7 +1353,7 @@ public class IrisObject extends IrisRegistrant { double d = Double.MAX_VALUE; - for (Map.Entry entry : blocks.entrySet()) { + for (var entry : blocks) { BlockData dat = entry.getValue(); if (dat.getMaterial().isAir()) { @@ -1359,6 +1367,7 @@ public class IrisObject extends IrisRegistrant { r = dat; } } + readLock.unlock(); return r; } diff --git a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java index c16bebc3a..54bfefd5a 100644 --- a/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/com/volmit/iris/engine/platform/BukkitChunkGenerator.java @@ -166,6 +166,8 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun return targetCache.aquire(() -> { IrisData data = IrisData.get(dataLocation); + data.dump(); + data.clearLists(); IrisDimension dimension = data.getDimensionLoader().load(dimensionKey); if (dimension == null) { @@ -205,7 +207,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun Chunk c = PaperLib.getChunkAtAsync(world, x, z) .thenApply(d -> { - d.addPluginChunkTicket(Iris.instance); + Iris.tickets.addTicket(d); for (Entity ee : d.getEntities()) { if (ee instanceof Player) { @@ -215,7 +217,6 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun ee.remove(); } - engine.getWorldManager().onChunkLoad(d, false); return d; }).get(); @@ -241,7 +242,7 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) .thenRunAsync(() -> { - c.removePluginChunkTicket(Iris.instance); + Iris.tickets.removeTicket(c); engine.getWorldManager().onChunkLoad(c, true); }, syncExecutor) .get(); diff --git a/core/src/main/java/com/volmit/iris/util/cache/ChunkCache2D.java b/core/src/main/java/com/volmit/iris/util/cache/ChunkCache2D.java index f048926c3..f68dff1bf 100644 --- a/core/src/main/java/com/volmit/iris/util/cache/ChunkCache2D.java +++ b/core/src/main/java/com/volmit/iris/util/cache/ChunkCache2D.java @@ -2,24 +2,55 @@ package com.volmit.iris.util.cache; import com.volmit.iris.util.function.Function2; -import java.util.concurrent.atomic.AtomicReferenceArray; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.VarHandle; public class ChunkCache2D { - private final AtomicReferenceArray cache; + private static final boolean FAST = Boolean.getBoolean("iris.cache.fast"); + private static final boolean DYNAMIC = Boolean.getBoolean("iris.cache.dynamic"); + private static final VarHandle AA = MethodHandles.arrayElementVarHandle(Entry[].class); + private final Entry[] cache; + + @SuppressWarnings({"unchecked"}) public ChunkCache2D() { - this.cache = new AtomicReferenceArray<>(256); + this.cache = new Entry[256]; + if (DYNAMIC) return; + for (int i = 0; i < cache.length; i++) { + cache[i] = FAST ? new FastEntry<>() : new Entry<>(); + } } + @SuppressWarnings({"unchecked"}) public T get(int x, int z, Function2 resolver) { int key = ((z & 15) * 16) + (x & 15); - T t = cache.get(key); - - if (t == null) { - t = resolver.apply(x, z); - cache.set(key, t); + var entry = cache[key]; + if (entry == null) { + entry = FAST ? new FastEntry<>() : new Entry<>(); + if (!AA.compareAndSet(cache, key, null, entry)) { + entry = (Entry) AA.getVolatile(cache, key); + } } + return entry.compute(x, z, resolver); + } - return t; + private static class Entry { + protected volatile T t; + + protected T compute(int x, int z, Function2 resolver) { + if (t != null) return t; + synchronized (this) { + if (t == null) t = resolver.apply(x, z); + return t; + } + } + } + + private static class FastEntry extends Entry { + @Override + protected T compute(int x, int z, Function2 resolver) { + if (t != null) return t; + return t = resolver.apply(x, z); + } } } diff --git a/core/src/main/java/com/volmit/iris/util/cache/WorldCache2D.java b/core/src/main/java/com/volmit/iris/util/cache/WorldCache2D.java index e79b87aeb..1d0ea3f29 100644 --- a/core/src/main/java/com/volmit/iris/util/cache/WorldCache2D.java +++ b/core/src/main/java/com/volmit/iris/util/cache/WorldCache2D.java @@ -1,24 +1,32 @@ package com.volmit.iris.util.cache; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; import com.volmit.iris.engine.data.cache.Cache; -import com.volmit.iris.util.data.KCache; import com.volmit.iris.util.function.Function2; public class WorldCache2D { - private final KCache> chunks; + private final ConcurrentLinkedHashMap> chunks; private final Function2 resolver; - public WorldCache2D(Function2 resolver) { + public WorldCache2D(Function2 resolver, int size) { this.resolver = resolver; - chunks = new KCache<>((x) -> new ChunkCache2D<>(), 1024); + chunks = new ConcurrentLinkedHashMap.Builder>() + .initialCapacity(size) + .maximumWeightedCapacity(size) + .concurrencyLevel(Math.max(32, Runtime.getRuntime().availableProcessors() * 4)) + .build(); } public T get(int x, int z) { - ChunkCache2D chunk = chunks.get(Cache.key(x >> 4, z >> 4)); + ChunkCache2D chunk = chunks.computeIfAbsent(Cache.key(x >> 4, z >> 4), $ -> new ChunkCache2D<>()); return chunk.get(x, z, resolver); } public long getSize() { - return chunks.getSize() * 256L; + return chunks.size() * 256L; + } + + public long getMaxSize() { + return chunks.capacity() * 256L; } } diff --git a/core/src/main/java/com/volmit/iris/util/collection/KMap.java b/core/src/main/java/com/volmit/iris/util/collection/KMap.java index 4a7f938e0..4657b431f 100644 --- a/core/src/main/java/com/volmit/iris/util/collection/KMap.java +++ b/core/src/main/java/com/volmit/iris/util/collection/KMap.java @@ -33,7 +33,15 @@ public class KMap extends ConcurrentHashMap { private static final long serialVersionUID = 7288942695300448163L; public KMap() { - super(); + this(16); + } + + public KMap(int initialCapacity) { + this(initialCapacity, 0.75f, 1); + } + + public KMap(int initialCapacity, float loadFactor, int concurrencyLevel) { + super(initialCapacity, loadFactor, concurrencyLevel); } public KMap(Map gMap) { diff --git a/core/src/main/java/com/volmit/iris/util/context/ChunkContext.java b/core/src/main/java/com/volmit/iris/util/context/ChunkContext.java deleted file mode 100644 index b017d49e1..000000000 --- a/core/src/main/java/com/volmit/iris/util/context/ChunkContext.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.volmit.iris.util.context; - -import com.volmit.iris.engine.IrisComplex; -import com.volmit.iris.engine.object.IrisBiome; -import com.volmit.iris.engine.object.IrisRegion; -import com.volmit.iris.util.documentation.BlockCoordinates; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.parallel.MultiBurst; -import lombok.Data; -import org.bukkit.block.data.BlockData; - -@Data -public class ChunkContext { - private final int x; - private final int z; - private ChunkedDataCache height; - private ChunkedDataCache biome; - private ChunkedDataCache cave; - private ChunkedDataCache rock; - private ChunkedDataCache fluid; - private ChunkedDataCache region; - - @BlockCoordinates - public ChunkContext(int x, int z, IrisComplex c) { - this(x, z, c, true); - } - - @BlockCoordinates - public ChunkContext(int x, int z, IrisComplex c, boolean cache) { - this.x = x; - this.z = z; - - if (cache) { - BurstExecutor b = MultiBurst.burst.burst(); - height = new ChunkedDataCache<>(b, c.getHeightStream(), x, z); - biome = new ChunkedDataCache<>(b, c.getTrueBiomeStream(), x, z); - cave = new ChunkedDataCache<>(b, c.getCaveBiomeStream(), x, z); - rock = new ChunkedDataCache<>(b, c.getRockStream(), x, z); - fluid = new ChunkedDataCache<>(b, c.getFluidStream(), x, z); - region = new ChunkedDataCache<>(b, c.getRegionStream(), x, z); - b.complete(); - } else { - height = new ChunkedDataCache<>(null, c.getHeightStream(), x, z, false); - biome = new ChunkedDataCache<>(null, c.getTrueBiomeStream(), x, z, false); - cave = new ChunkedDataCache<>(null, c.getCaveBiomeStream(), x, z, false); - rock = new ChunkedDataCache<>(null, c.getRockStream(), x, z, false); - fluid = new ChunkedDataCache<>(null, c.getFluidStream(), x, z, false); - region = new ChunkedDataCache<>(null, c.getRegionStream(), x, z, false); - } - } -} diff --git a/core/src/main/java/com/volmit/iris/util/context/ChunkedDataCache.java b/core/src/main/java/com/volmit/iris/util/context/ChunkedDataCache.java deleted file mode 100644 index 8c030c2cc..000000000 --- a/core/src/main/java/com/volmit/iris/util/context/ChunkedDataCache.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.volmit.iris.util.context; - -import com.volmit.iris.util.collection.KSet; -import com.volmit.iris.util.documentation.BlockCoordinates; -import com.volmit.iris.util.parallel.BurstExecutor; -import com.volmit.iris.util.stream.ProceduralStream; -import lombok.Data; - -@Data -public class ChunkedDataCache { - private final int x; - private final int z; - private final KSet uniques; - private final Object[] data; - private final boolean cache; - private final ProceduralStream stream; - - @BlockCoordinates - public ChunkedDataCache(BurstExecutor burst, ProceduralStream stream, int x, int z) { - this(burst, stream, x, z, true); - } - - @BlockCoordinates - public ChunkedDataCache(BurstExecutor burst, ProceduralStream stream, int x, int z, boolean cache) { - this.stream = stream; - this.cache = cache; - this.x = x; - this.z = z; - this.uniques = cache ? new KSet<>() : null; - if (cache) { - data = new Object[256]; - int i, j; - - for (i = 0; i < 16; i++) { - int finalI = i; - for (j = 0; j < 16; j++) { - int finalJ = j; - burst.queue(() -> { - T t = stream.get(x + finalI, z + finalJ); - data[(finalJ * 16) + finalI] = t; - uniques.add(t); - }); - } - } - } else { - data = new Object[0]; - } - } - - @SuppressWarnings("unchecked") - @BlockCoordinates - public T get(int x, int z) { - if (!cache) { - return stream.get(this.x + x, this.z + z); - } - - T t = (T) data[(z * 16) + x]; - return t == null ? stream.get(this.x + x, this.z + z) : t; - } -} diff --git a/core/src/main/java/com/volmit/iris/util/data/IrisCustomData.java b/core/src/main/java/com/volmit/iris/util/data/IrisCustomData.java index c02ea2879..da60f704c 100644 --- a/core/src/main/java/com/volmit/iris/util/data/IrisCustomData.java +++ b/core/src/main/java/com/volmit/iris/util/data/IrisCustomData.java @@ -1,127 +1,77 @@ package com.volmit.iris.util.data; import com.volmit.iris.core.link.Identifier; -import lombok.Data; +import com.volmit.iris.util.collection.KMap; import lombok.NonNull; -import org.bukkit.Location; -import org.bukkit.Material; -import org.bukkit.SoundGroup; -import org.bukkit.block.*; import org.bukkit.block.data.BlockData; -import org.bukkit.block.structure.Mirror; -import org.bukkit.block.structure.StructureRotation; -import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -@Data -public class IrisCustomData implements BlockData { - private final @NonNull BlockData base; - private final @NotNull Identifier custom; +import java.lang.reflect.Proxy; +import java.util.*; - @NotNull - @Override - public Material getMaterial() { - return base.getMaterial(); +public interface IrisCustomData extends BlockData { + @NonNull BlockData getBase(); + @NonNull Identifier getCustom(); + + static IrisCustomData of(@NotNull BlockData base, @NotNull Identifier custom) { + var clazz = base.getClass(); + var loader = clazz.getClassLoader(); + return (IrisCustomData) Proxy.newProxyInstance(loader, Internal.getInterfaces(loader, clazz), (proxy, method, args) -> + switch (method.getName()) { + case "getBase" -> base; + case "getCustom" -> custom; + case "merge" -> of(base.merge((BlockData) args[0]), custom); + case "clone" -> of(base.clone(), custom); + case "hashCode" -> Objects.hash(base, custom); + case "matches" -> { + if (!(args[0] instanceof IrisCustomData store)) + yield false; + yield base.matches(store.getBase()) && custom.equals(store.getCustom()); + } + case "equals" -> { + if (!(args[0] instanceof IrisCustomData store)) + yield false; + yield store.getBase().equals(base) && store.getCustom().equals(custom); + } + default -> method.invoke(base, args); + }); } - @NotNull - @Override - public String getAsString() { - return base.getAsString(); - } + @ApiStatus.Internal + abstract class Internal { + private static final KMap, Class[]> cache = new KMap<>(); - @NotNull - @Override - public String getAsString(boolean b) { - return base.getAsString(b); - } + private static Class[] getInterfaces(ClassLoader loader, Class base) { + return cache.computeIfAbsent(base, k -> { + Queue> queue = new LinkedList<>(); + Set> set = new HashSet<>(); - @NotNull - @Override - public BlockData merge(@NotNull BlockData blockData) { - return new IrisCustomData(base.merge(blockData), custom); - } + queue.add(k); + while (!queue.isEmpty()) { + Class i = queue.poll(); - @Override - public boolean matches(@Nullable BlockData blockData) { - if (blockData instanceof IrisCustomData b) - return custom.equals(b.custom) && base.matches(b.base); - return base.matches(blockData); - } + if (!BlockData.class.isAssignableFrom(i)) + continue; - @NotNull - @Override - public BlockData clone() { - return new IrisCustomData(base.clone(), custom); - } + for (Class j : i.getInterfaces()) { + if (j.isSealed() || j.isHidden()) + continue; - @NotNull - @Override - public SoundGroup getSoundGroup() { - return base.getSoundGroup(); - } + try { + Class.forName(j.getName(), false, loader); + set.add(j); + } catch (ClassNotFoundException ignored) {} + } - @Override - public int getLightEmission() { - return base.getLightEmission(); - } + var parent = i.getSuperclass(); + if (parent != null) + queue.add(parent); + } - @Override - public boolean isOccluding() { - return base.isOccluding(); - } - - @Override - public boolean requiresCorrectToolForDrops() { - return base.requiresCorrectToolForDrops(); - } - - @Override - public boolean isPreferredTool(@NotNull ItemStack itemStack) { - return base.isPreferredTool(itemStack); - } - - @NotNull - @Override - public PistonMoveReaction getPistonMoveReaction() { - return base.getPistonMoveReaction(); - } - - @Override - public boolean isSupported(@NotNull Block block) { - return base.isSupported(block); - } - - @Override - public boolean isSupported(@NotNull Location location) { - return base.isSupported(location); - } - - @Override - public boolean isFaceSturdy(@NotNull BlockFace blockFace, @NotNull BlockSupport blockSupport) { - return base.isFaceSturdy(blockFace, blockSupport); - } - - @NotNull - @Override - public Material getPlacementMaterial() { - return base.getPlacementMaterial(); - } - - @Override - public void rotate(@NotNull StructureRotation structureRotation) { - base.rotate(structureRotation); - } - - @Override - public void mirror(@NotNull Mirror mirror) { - base.mirror(mirror); - } - - @NotNull - @Override - public BlockState createBlockState() { - return base.createBlockState(); + set.add(IrisCustomData.class); + return set.toArray(Class[]::new); + }); + } } } diff --git a/core/src/main/java/com/volmit/iris/util/data/VectorMap.java b/core/src/main/java/com/volmit/iris/util/data/VectorMap.java new file mode 100644 index 000000000..0b3108252 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/data/VectorMap.java @@ -0,0 +1,220 @@ +package com.volmit.iris.util.data; + +import com.volmit.iris.util.collection.KMap; +import lombok.NonNull; +import org.bukkit.util.BlockVector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Iterator; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class VectorMap implements Iterable> { + private final Map> map = new KMap<>(); + + public int size() { + return map.values().stream().mapToInt(Map::size).sum(); + } + + public boolean isEmpty() { + return map.values().stream().allMatch(Map::isEmpty); + } + + public boolean containsKey(@NonNull BlockVector vector) { + var chunk = map.get(chunk(vector)); + return chunk != null && chunk.containsKey(relative(vector)); + } + + public boolean containsValue(@NonNull T value) { + return map.values().stream().anyMatch(m -> m.containsValue(value)); + } + + public @Nullable T get(@NonNull BlockVector vector) { + var chunk = map.get(chunk(vector)); + return chunk == null ? null : chunk.get(relative(vector)); + } + + public @Nullable T put(@NonNull BlockVector vector, @NonNull T value) { + return map.computeIfAbsent(chunk(vector), k -> new KMap<>()) + .put(relative(vector), value); + } + + public @Nullable T computeIfAbsent(@NonNull BlockVector vector, @NonNull Function<@NonNull BlockVector, @NonNull T> mappingFunction) { + return map.computeIfAbsent(chunk(vector), k -> new KMap<>()) + .computeIfAbsent(relative(vector), $ -> mappingFunction.apply(vector)); + } + + public @Nullable T remove(@NonNull BlockVector vector) { + var chunk = map.get(chunk(vector)); + return chunk == null ? null : chunk.remove(relative(vector)); + } + + public void putAll(@NonNull VectorMap map) { + map.forEach(this::put); + } + + public void clear() { + map.clear(); + } + + public void forEach(@NonNull BiConsumer<@NonNull BlockVector, @NonNull T> consumer) { + map.forEach((chunk, values) -> { + int rX = chunk.x << 10; + int rY = chunk.y << 10; + int rZ = chunk.z << 10; + + values.forEach((relative, value) -> consumer.accept( + relative.resolve(rX, rY, rZ), + value + )); + }); + } + + private static Key chunk(BlockVector vector) { + return new Key(vector.getBlockX() >> 10, vector.getBlockY() >> 10, vector.getBlockZ() >> 10); + } + + private static Key relative(BlockVector vector) { + return new Key(vector.getBlockX() & 0x3FF, vector.getBlockY() & 0x3FF, vector.getBlockZ() & 0x3FF); + } + + @Override + public @NotNull EntryIterator iterator() { + return new EntryIterator(); + } + + public @NotNull KeyIterator keys() { + return new KeyIterator(); + } + + public @NotNull ValueIterator values() { + return new ValueIterator(); + } + + public class EntryIterator implements Iterator> { + private final Iterator>> chunkIterator = map.entrySet().iterator(); + private Iterator> relativeIterator; + private int rX, rY, rZ; + + @Override + public boolean hasNext() { + return relativeIterator != null && relativeIterator.hasNext() || chunkIterator.hasNext(); + } + + @Override + public Map.Entry next() { + if (relativeIterator == null || !relativeIterator.hasNext()) { + if (!chunkIterator.hasNext()) throw new IllegalStateException("No more elements"); + var chunk = chunkIterator.next(); + rX = chunk.getKey().x << 10; + rY = chunk.getKey().y << 10; + rZ = chunk.getKey().z << 10; + relativeIterator = chunk.getValue().entrySet().iterator(); + } + + var entry = relativeIterator.next(); + return Map.entry(entry.getKey().resolve(rX, rY, rZ), entry.getValue()); + } + + @Override + public void remove() { + if (relativeIterator == null) throw new IllegalStateException("No element to remove"); + relativeIterator.remove(); + } + } + + public class KeyIterator implements Iterator, Iterable { + private final Iterator>> chunkIterator = map.entrySet().iterator(); + private Iterator relativeIterator; + private int rX, rY, rZ; + + @Override + public boolean hasNext() { + return relativeIterator != null && relativeIterator.hasNext() || chunkIterator.hasNext(); + } + + @Override + public BlockVector next() { + if (relativeIterator == null || !relativeIterator.hasNext()) { + var chunk = chunkIterator.next(); + rX = chunk.getKey().x << 10; + rY = chunk.getKey().y << 10; + rZ = chunk.getKey().z << 10; + relativeIterator = chunk.getValue().keySet().iterator(); + } + + return relativeIterator.next().resolve(rX, rY, rZ); + } + + @Override + public void remove() { + if (relativeIterator == null) throw new IllegalStateException("No element to remove"); + relativeIterator.remove(); + } + + @Override + public @NotNull Iterator iterator() { + return this; + } + } + + public class ValueIterator implements Iterator, Iterable { + private final Iterator> chunkIterator = map.values().iterator(); + private Iterator relativeIterator; + + @Override + public boolean hasNext() { + return relativeIterator != null && relativeIterator.hasNext() || chunkIterator.hasNext(); + } + + @Override + public T next() { + if (relativeIterator == null || !relativeIterator.hasNext()) { + relativeIterator = chunkIterator.next().values().iterator(); + } + return relativeIterator.next(); + } + + @Override + public void remove() { + if (relativeIterator == null) throw new IllegalStateException("No element to remove"); + relativeIterator.remove(); + } + + @Override + public @NotNull Iterator iterator() { + return this; + } + } + + private static final class Key { + private final int x; + private final int y; + private final int z; + private final int hashCode; + + private Key(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + this.hashCode = (x << 20) | (y << 10) | z; + } + + private BlockVector resolve(int rX, int rY, int rZ) { + return new BlockVector(rX + x, rY + y, rZ + z); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Key key)) return false; + return x == key.x && y == key.y && z == key.z; + } + } +} diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java index ab672ca76..2d02875fa 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/DataContainer.java @@ -19,21 +19,30 @@ package com.volmit.iris.util.hunk.bits; import com.volmit.iris.util.data.Varint; -import lombok.Synchronized; +import it.unimi.dsi.fastutil.ints.*; import java.io.*; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; public class DataContainer { + private static final boolean TRIM = Boolean.getBoolean("iris.trim-palette"); protected static final int INITIAL_BITS = 3; protected static final int LINEAR_BITS_LIMIT = 4; - protected static final int LINEAR_INITIAL_LENGTH = (int) Math.pow(2, LINEAR_BITS_LIMIT) + 1; + protected static final int LINEAR_INITIAL_LENGTH = (int) Math.pow(2, LINEAR_BITS_LIMIT) + 2; protected static final int[] BIT = computeBitLimits(); + private final Lock read, write; + private volatile Palette palette; private volatile DataBits data; private final int length; private final Writable writer; public DataContainer(Writable writer, int length) { + var lock = new ReentrantReadWriteLock(); + this.read = lock.readLock(); + this.write = lock.writeLock(); + this.writer = writer; this.length = length; this.data = new DataBits(INITIAL_BITS, length); @@ -41,10 +50,15 @@ public class DataContainer { } public DataContainer(DataInputStream din, Writable writer) throws IOException { + var lock = new ReentrantReadWriteLock(); + this.read = lock.readLock(); + this.write = lock.writeLock(); + this.writer = writer; this.length = Varint.readUnsignedVarInt(din); this.palette = newPalette(din); this.data = new DataBits(palette.bits(), length, din); + trim(); } private static int[] computeBitLimits() { @@ -86,13 +100,18 @@ public class DataContainer { writeDos(new DataOutputStream(out)); } - @Synchronized public void writeDos(DataOutputStream dos) throws IOException { - Varint.writeUnsignedVarInt(length, dos); - Varint.writeUnsignedVarInt(palette.size(), dos); - palette.iterateIO((data, __) -> writer.writeNodeData(dos, data)); - data.write(dos); - dos.flush(); + write.lock(); + try { + trim(); + Varint.writeUnsignedVarInt(length, dos); + Varint.writeUnsignedVarInt(palette.size(), dos); + palette.iterateIO((data, __) -> writer.writeNodeData(dos, data)); + data.write(dos); + dos.flush(); + } finally { + write.unlock(); + } } private Palette newPalette(DataInputStream din) throws IOException { @@ -110,25 +129,38 @@ public class DataContainer { return new HashPalette<>(); } - @Synchronized public void set(int position, T t) { - int id = palette.id(t); + int id; - if (id == -1) { - id = palette.add(t); - updateBits(); + read.lock(); + try { + id = palette.id(t); + if (id == -1) { + id = palette.add(t); + if (palette.bits() == data.getBits()) { + data.set(position, id); + return; + } + } + } finally { + read.unlock(); } - data.set(position, id); + write.lock(); + try { + updateBits(); + data.set(position, id); + } finally { + write.unlock(); + } } - @Synchronized @SuppressWarnings("NonAtomicOperationOnVolatileField") private void updateBits() { - if (palette.bits() == data.getBits()) + int bits = palette.bits(); + if (bits == data.getBits()) return; - int bits = palette.bits(); if (data.getBits() <= LINEAR_BITS_LIMIT != bits <= LINEAR_BITS_LIMIT) { palette = newPalette(bits).from(palette); } @@ -136,18 +168,44 @@ public class DataContainer { data = data.setBits(bits); } - @Synchronized public T get(int position) { - int id = data.get(position); + read.lock(); + try { + int id = data.get(position); - if (id <= 0) { - return null; + if (id <= 0) { + return null; + } + + return palette.get(id); + } finally { + read.unlock(); } - - return palette.get(id); } public int size() { return data.getSize(); } + + private void trim() { + var ints = new Int2IntRBTreeMap(); + for (int i = 0; i < length; i++) { + int x = data.get(i); + if (x <= 0) continue; + ints.put(x, x); + } + if (ints.size() == palette.size()) + return; + + int bits = bits(ints.size() + 1); + var trimmed = newPalette(bits); + ints.replaceAll((k, v) -> trimmed.add(palette.get(k))); + var tBits = new DataBits(bits, length); + for (int i = 0; i < length; i++) { + tBits.set(i, ints.get(data.get(i))); + } + + data = tBits; + palette = trimmed; + } } diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java index 134425f6f..cf31e6c37 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/HashPalette.java @@ -27,13 +27,14 @@ import java.util.LinkedHashMap; import java.util.concurrent.atomic.AtomicInteger; public class HashPalette implements Palette { - private final LinkedHashMap palette; + private final Object lock = new Object(); + private final KMap palette; private final KMap lookup; private final AtomicInteger size; public HashPalette() { this.size = new AtomicInteger(1); - this.palette = new LinkedHashMap<>(); + this.palette = new KMap<>(); this.lookup = new KMap<>(); } @@ -52,13 +53,13 @@ public class HashPalette implements Palette { return 0; } - synchronized (palette) { - return palette.computeIfAbsent(t, $ -> { + return palette.computeIfAbsent(t, $ -> { + synchronized (lock) { int index = size.getAndIncrement(); lookup.put(index, t); return index; - }); - } + } + }); } @Override @@ -78,7 +79,7 @@ public class HashPalette implements Palette { @Override public void iterate(Consumer2 c) { - synchronized (palette) { + synchronized (lock) { for (int i = 1; i < size.get(); i++) { c.accept(lookup.get(i), i); } diff --git a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java index 5e2c1d43e..a93b4887c 100644 --- a/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java +++ b/core/src/main/java/com/volmit/iris/util/hunk/bits/LinearPalette.java @@ -45,25 +45,23 @@ public class LinearPalette implements Palette { @Override public int add(T t) { - if (t == null) { - return 0; - } int index = size.getAndIncrement(); - grow(index + 1); + if (palette.length() <= index) + grow(index); palette.set(index, t); return index; } - private synchronized void grow(int newLength) { - if (newLength > palette.length()) { - AtomicReferenceArray a = new AtomicReferenceArray<>(newLength); + private synchronized void grow(int lastIndex) { + if (palette.length() > lastIndex) + return; - for (int i = 0; i < palette.length(); i++) { - a.set(i, palette.get(i)); - } - - palette = a; + AtomicReferenceArray a = new AtomicReferenceArray<>(lastIndex + 1); + for (int i = 0; i < palette.length(); i++) { + a.set(i, palette.get(i)); } + + palette = a; } @Override diff --git a/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java b/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java index b1ecd72ca..a2f1ef134 100644 --- a/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java +++ b/core/src/main/java/com/volmit/iris/util/interpolation/IrisInterpolation.java @@ -999,7 +999,7 @@ public class IrisInterpolation { public static double getNoise(InterpolationMethod method, int x, int z, double h, NoiseProvider noise) { HashMap cache = new HashMap<>(64); - NoiseProvider n = (x1, z1) -> cache.computeIfAbsent(new NoiseKey(x1, z1), k -> noise.noise(k.x, k.z)); + NoiseProvider n = (x1, z1) -> cache.computeIfAbsent(new NoiseKey(x1 - x, z1 - z), k -> noise.noise(x1, z1)); if (method.equals(InterpolationMethod.BILINEAR)) { return getBilinearNoise(x, z, h, n); diff --git a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java index 3867a2fc8..6f27e7c45 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/Mantle.java @@ -176,8 +176,8 @@ public class Mantle { * @return the writer */ @ChunkCoordinates - public MantleWriter write(EngineMantle engineMantle, int x, int z, int radius) { - return new MantleWriter(engineMantle, this, x, z, radius); + public MantleWriter write(EngineMantle engineMantle, int x, int z, int radius, boolean multicore) { + return new MantleWriter(engineMantle, this, x, z, radius, multicore); } /** @@ -406,18 +406,6 @@ public class Mantle { Iris.debug("The Mantle has Closed " + C.DARK_AQUA + dataFolder.getAbsolutePath()); } - /** - * Estimates the memory usage of the lastUse map. - * - * @return Estimated memory usage in bytes. - */ - - public long LastUseMapMemoryUsage() { - long numberOfEntries = lastUse.size(); - long bytesPerEntry = Long.BYTES * 2; - return numberOfEntries * bytesPerEntry; - } - /** * Save & unload regions that have not been used for more than the * specified amount of milliseconds @@ -635,7 +623,7 @@ public class Mantle { } public void deleteChunkSlice(int x, int z, Class c) { - if (!IrisToolbelt.toolbeltConfiguration.isEmpty() && IrisToolbelt.toolbeltConfiguration.getOrDefault("retain.mantle." + c.getCanonicalName(), false)) { + if (IrisToolbelt.isRetainingMantleDataForSlice(c.getCanonicalName())) { return; } diff --git a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java index c432b93f9..297140c5d 100644 --- a/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java +++ b/core/src/main/java/com/volmit/iris/util/mantle/MantleChunk.java @@ -19,16 +19,14 @@ package com.volmit.iris.util.mantle; import com.volmit.iris.Iris; -import com.volmit.iris.util.data.Varint; +import com.volmit.iris.core.tools.IrisToolbelt; import com.volmit.iris.util.documentation.ChunkCoordinates; import com.volmit.iris.util.documentation.ChunkRelativeBlockCoordinates; import com.volmit.iris.util.function.Consumer4; import com.volmit.iris.util.io.CountingDataInputStream; -import com.volmit.iris.util.mantle.flag.MantleFlag; import com.volmit.iris.util.matter.IrisMatter; import com.volmit.iris.util.matter.Matter; import com.volmit.iris.util.matter.MatterSlice; -import com.volmit.iris.util.parallel.AtomicBooleanArray; import lombok.Getter; import lombok.SneakyThrows; import org.jetbrains.annotations.Nullable; @@ -44,13 +42,11 @@ import java.util.concurrent.atomic.AtomicReferenceArray; * Represents a mantle chunk. Mantle chunks contain sections of matter (see matter api) * Mantle Chunks are fully atomic & thread safe */ -public class MantleChunk { +public class MantleChunk extends FlaggedChunk { @Getter private final int x; @Getter private final int z; - private final AtomicBooleanArray flags; - private final Object[] flagLocks; private final AtomicReferenceArray sections; private final Semaphore ref = new Semaphore(Integer.MAX_VALUE, true); private final AtomicBoolean closed = new AtomicBoolean(false); @@ -63,14 +59,8 @@ public class MantleChunk { @ChunkCoordinates public MantleChunk(int sectionHeight, int x, int z) { sections = new AtomicReferenceArray<>(sectionHeight); - flags = new AtomicBooleanArray(MantleFlag.MAX_ORDINAL + 1); - flagLocks = new Object[flags.length()]; this.x = x; this.z = z; - - for (int i = 0; i < flags.length(); i++) { - flagLocks[i] = new Object(); - } } /** @@ -84,20 +74,7 @@ public class MantleChunk { public MantleChunk(int version, int sectionHeight, CountingDataInputStream din) throws IOException { this(sectionHeight, din.readByte(), din.readByte()); int s = din.readByte(); - int l = version < 0 ? 16 : Varint.readUnsignedVarInt(din); - - if (version >= 1) { - for (int i = 0; i < l;) { - byte f = din.readByte(); - for (int j = 0; j < Byte.SIZE && i < flags.length(); j++, i++) { - flags.set(i, (f & (1 << j)) != 0); - } - } - } else { - for (int i = 0; i < flags.length() && i < l; i++) { - flags.set(i, din.readBoolean()); - } - } + readFlags(version, din); for (int i = 0; i < s; i++) { Iris.addPanic("read.section", "Section[" + i + "]"); @@ -156,52 +133,10 @@ public class MantleChunk { public void copyFlags(MantleChunk chunk) { use(); - for (int i = 0; i < flags.length(); i++) { - flags.set(i, chunk.flags.get(i)); - } + super.copyFlags(chunk); release(); } - public void flag(MantleFlag flag, boolean f) { - if (closed.get()) throw new IllegalStateException("Chunk is closed!"); - flags.set(flag.ordinal(), f); - } - - public void raiseFlag(MantleFlag flag, Runnable r) { - raiseFlag(null, flag, r); - } - - public void raiseFlag(@Nullable MantleFlag guard, MantleFlag flag, Runnable r) { - if (closed.get()) throw new IllegalStateException("Chunk is closed!"); - if (guard != null && isFlagged(guard)) return; - synchronized (flagLocks[flag.ordinal()]) { - if (flags.compareAndSet(flag.ordinal(), false, true)) { - try { - r.run(); - } catch (RuntimeException | Error e) { - flags.set(flag.ordinal(), false); - throw e; - } - } - } - } - - public void raiseFlagUnchecked(MantleFlag flag, Runnable r) { - if (closed.get()) throw new IllegalStateException("Chunk is closed!"); - if (flags.compareAndSet(flag.ordinal(), false, true)) { - try { - r.run(); - } catch (RuntimeException | Error e) { - flags.set(flag.ordinal(), false); - throw e; - } - } - } - - public boolean isFlagged(MantleFlag flag) { - return flags.get(flag.ordinal()); - } - /** * Check if a section exists (same as get(section) != null) * @@ -283,16 +218,7 @@ public class MantleChunk { dos.writeByte(x); dos.writeByte(z); dos.writeByte(sections.length()); - Varint.writeUnsignedVarInt(flags.length(), dos); - - int count = flags.length(); - for (int i = 0; i < count;) { - int f = 0; - for (int j = 0; j < Byte.SIZE && i < flags.length(); j++, i++) { - f |= flags.get(i) ? (1 << j) : 0; - } - dos.write(f); - } + writeFlags(dos); var bytes = new ByteArrayOutputStream(8192); var sub = new DataOutputStream(bytes); @@ -345,6 +271,8 @@ public class MantleChunk { } public void deleteSlices(Class c) { + if (IrisToolbelt.isRetainingMantleDataForSlice(c.getCanonicalName())) + return; for (int i = 0; i < sections.length(); i++) { Matter m = sections.get(i); if (m != null && m.hasSlice(c)) { @@ -360,4 +288,9 @@ public class MantleChunk { } } } + + @Override + public boolean isClosed() { + return closed.get(); + } } diff --git a/core/src/main/java/com/volmit/iris/util/math/Vector3i.java b/core/src/main/java/com/volmit/iris/util/math/Vector3i.java index 84d407f71..3aaf11650 100644 --- a/core/src/main/java/com/volmit/iris/util/math/Vector3i.java +++ b/core/src/main/java/com/volmit/iris/util/math/Vector3i.java @@ -21,6 +21,6 @@ public class Vector3i extends BlockVector { @Override public int hashCode() { - return (((int) x & 0x3FF) << 2) | (((int) y & 0x3FF) << 1) | ((int) z & 0x3FF); + return (int) x ^ ((int) z << 12) ^ ((int) y << 24); } } diff --git a/core/src/main/java/com/volmit/iris/util/matter/Matter.java b/core/src/main/java/com/volmit/iris/util/matter/Matter.java index 0fe0dca0c..384835bbf 100644 --- a/core/src/main/java/com/volmit/iris/util/matter/Matter.java +++ b/core/src/main/java/com/volmit/iris/util/matter/Matter.java @@ -85,13 +85,13 @@ public interface Matter { BlockVector min = new BlockVector(); Matter m = new IrisMatter(Math.max(object.getW(), 1) + 1, Math.max(object.getH(), 1) + 1, Math.max(object.getD(), 1) + 1); - for (BlockVector i : object.getBlocks().keySet()) { + for (BlockVector i : object.getBlocks().keys()) { min.setX(Math.min(min.getX(), i.getX())); min.setY(Math.min(min.getY(), i.getY())); min.setZ(Math.min(min.getZ(), i.getZ())); } - for (BlockVector i : object.getBlocks().keySet()) { + for (BlockVector i : object.getBlocks().keys()) { m.slice(BlockData.class).set(i.getBlockX() - min.getBlockX(), i.getBlockY() - min.getBlockY(), i.getBlockZ() - min.getBlockZ(), object.getBlocks().get(i)); } diff --git a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java index 32ab14103..8eb82b31f 100644 --- a/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java +++ b/core/src/main/java/com/volmit/iris/util/parallel/MultiBurst.java @@ -23,6 +23,8 @@ import com.volmit.iris.core.IrisSettings; import com.volmit.iris.util.collection.KList; import com.volmit.iris.util.math.M; import com.volmit.iris.util.scheduling.PrecisionStopwatch; +import kotlinx.coroutines.CoroutineDispatcher; +import kotlinx.coroutines.ExecutorsKt; import org.jetbrains.annotations.NotNull; import java.util.Collection; @@ -39,7 +41,9 @@ public class MultiBurst implements ExecutorService { private final String name; private final int priority; private final IntSupplier parallelism; - private ExecutorService service; + private final Object lock = new Object(); + private volatile ExecutorService service; + private volatile CoroutineDispatcher dispatcher; public MultiBurst() { this("Iris"); @@ -60,25 +64,36 @@ public class MultiBurst implements ExecutorService { last = new AtomicLong(M.ms()); } - private synchronized ExecutorService getService() { + private ExecutorService getService() { last.set(M.ms()); - if (service == null || service.isShutdown()) { + if (service != null && !service.isShutdown()) + return service; + + synchronized (lock) { + if (service != null && !service.isShutdown()) + return service; + service = new ForkJoinPool(IrisSettings.getThreadCount(parallelism.getAsInt()), - new ForkJoinPool.ForkJoinWorkerThreadFactory() { - int m = 0; + new ForkJoinPool.ForkJoinWorkerThreadFactory() { + int m = 0; - @Override - public ForkJoinWorkerThread newThread(ForkJoinPool pool) { - final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); - worker.setPriority(priority); - worker.setName(name + " " + ++m); - return worker; - } - }, - (t, e) -> e.printStackTrace(), true); + @Override + public ForkJoinWorkerThread newThread(ForkJoinPool pool) { + final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool); + worker.setPriority(priority); + worker.setName(name + " " + ++m); + return worker; + } + }, + (t, e) -> e.printStackTrace(), true); + dispatcher = ExecutorsKt.from(service); + return service; } + } - return service; + public CoroutineDispatcher getDispatcher() { + getService(); + return dispatcher; } public void burst(Runnable... r) { diff --git a/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java b/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java new file mode 100644 index 000000000..320c74436 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/plugin/chunk/ChunkTickets.java @@ -0,0 +1,57 @@ +package com.volmit.iris.util.plugin.chunk; + +import com.volmit.iris.Iris; +import lombok.NonNull; +import org.bukkit.Bukkit; +import org.bukkit.Chunk; +import org.bukkit.World; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.world.WorldLoadEvent; +import org.bukkit.event.world.WorldUnloadEvent; + +import java.util.HashMap; +import java.util.Map; + +public class ChunkTickets implements Listener { + private final Map holders = new HashMap<>(); + + public ChunkTickets() { + Iris.instance.registerListener(this); + Bukkit.getWorlds().forEach(w -> holders.put(w, new TicketHolder(w))); + } + + public TicketHolder getHolder(@NonNull World world) { + return holders.get(world); + } + + public void addTicket(@NonNull Chunk chunk) { + addTicket(chunk.getWorld(), chunk.getX(), chunk.getZ()); + } + + public void addTicket(@NonNull World world, int x, int z) { + var holder = getHolder(world); + if (holder != null) holder.addTicket(x, z); + } + + public boolean removeTicket(@NonNull Chunk chunk) { + return removeTicket(chunk.getWorld(), chunk.getX(), chunk.getZ()); + } + + public boolean removeTicket(@NonNull World world, int x, int z) { + var holder = getHolder(world); + if (holder != null) return holder.removeTicket(x, z); + return false; + } + + @EventHandler(priority = EventPriority.LOWEST) + public void on(@NonNull WorldLoadEvent event) { + holders.put(event.getWorld(), new TicketHolder(event.getWorld())); + } + + @EventHandler(priority = EventPriority.MONITOR) + public void on(@NonNull WorldUnloadEvent event) { + holders.remove(event.getWorld()); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java b/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java new file mode 100644 index 000000000..abf521c91 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/plugin/chunk/TicketHolder.java @@ -0,0 +1,48 @@ +package com.volmit.iris.util.plugin.chunk; + +import com.volmit.iris.Iris; +import com.volmit.iris.engine.data.cache.Cache; +import com.volmit.iris.util.collection.KMap; +import lombok.NonNull; +import org.bukkit.Chunk; +import org.bukkit.World; + +public class TicketHolder { + private final World world; + private final KMap tickets = new KMap<>(); + + public TicketHolder(@NonNull World world) { + this.world = world; + } + + public void addTicket(@NonNull Chunk chunk) { + if (chunk.getWorld() != world) return; + addTicket(chunk.getX(), chunk.getZ()); + } + + public void addTicket(int x, int z) { + tickets.compute(Cache.key(x, z), ($, ref) -> { + if (ref == null) { + world.addPluginChunkTicket(x, z, Iris.instance); + return 1L; + } + return ++ref; + }); + } + + public boolean removeTicket(@NonNull Chunk chunk) { + if (chunk.getWorld() != world) return false; + return removeTicket(chunk.getX(), chunk.getZ()); + } + + public boolean removeTicket(int x, int z) { + return tickets.compute(Cache.key(x, z), ($, ref) -> { + if (ref == null) return null; + if (--ref <= 0) { + world.removePluginChunkTicket(x, z, Iris.instance); + return null; + } + return ref; + }) == null; + } +} diff --git a/core/src/main/java/com/volmit/iris/util/scheduling/jobs/ParallelRadiusJob.java b/core/src/main/java/com/volmit/iris/util/scheduling/jobs/ParallelRadiusJob.java new file mode 100644 index 000000000..71df2a3f5 --- /dev/null +++ b/core/src/main/java/com/volmit/iris/util/scheduling/jobs/ParallelRadiusJob.java @@ -0,0 +1,86 @@ +package com.volmit.iris.util.scheduling.jobs; + +import com.volmit.iris.util.math.Spiraler; +import com.volmit.iris.util.parallel.MultiBurst; +import lombok.SneakyThrows; +import lombok.Synchronized; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Semaphore; +import java.util.concurrent.atomic.AtomicInteger; + +public abstract class ParallelRadiusJob implements Job { + private final ExecutorService service; + private final AtomicInteger completed; + private volatile int radiusX, radiusZ; + private volatile int offsetX, offsetZ; + private volatile int total; + private final Semaphore lock; + private final int lockSize; + + public ParallelRadiusJob(int concurrent) { + this(concurrent, MultiBurst.burst); + } + + public ParallelRadiusJob(int concurrent, ExecutorService service) { + this.service = service; + completed = new AtomicInteger(0); + lock = new Semaphore(concurrent); + lockSize = concurrent; + } + + public ParallelRadiusJob retarget(int radius, int offsetX, int offsetZ) { + return retarget(radius, radius, offsetX, offsetZ); + } + + @Synchronized + public ParallelRadiusJob retarget(int radiusX, int radiusZ, int offsetX, int offsetZ) { + completed.set(0); + this.radiusX = radiusX; + this.radiusZ = radiusZ; + this.offsetX = offsetX; + this.offsetZ = offsetZ; + total = (radiusX * 2 + 1) * (radiusZ * 2 + 1); + return this; + } + + @Override + @SneakyThrows + @Synchronized + public void execute() { + new Spiraler(radiusX * 2 + 3, radiusZ * 2 + 3, this::submit).drain(); + lock.acquire(lockSize); + lock.release(lockSize); + } + + @SneakyThrows + private void submit(int x, int z) { + if (Math.abs(x) > radiusX || Math.abs(z) > radiusZ) return; + lock.acquire(); + service.submit(() -> { + try { + execute(x + offsetX, z + offsetZ); + } finally { + completeWork(); + } + }); + } + + protected abstract void execute(int x, int z); + + @Override + public void completeWork() { + completed.incrementAndGet(); + lock.release(); + } + + @Override + public int getTotalWork() { + return total; + } + + @Override + public int getWorkCompleted() { + return completed.get(); + } +} diff --git a/core/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java b/core/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java index 10d687459..b5f3bedd2 100644 --- a/core/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java +++ b/core/src/main/java/com/volmit/iris/util/stream/utility/CachedStream2D.java @@ -37,7 +37,7 @@ public class CachedStream2D extends BasicStream implements ProceduralStrea super(); this.stream = stream; this.engine = engine; - cache = new WorldCache2D<>(stream::get); + cache = new WorldCache2D<>(stream::get, size); Iris.service(PreservationSVC.class).registerCache(this); } @@ -74,7 +74,7 @@ public class CachedStream2D extends BasicStream implements ProceduralStrea @Override public long getMaxSize() { - return 256 * 32; + return cache.getMaxSize(); } @Override diff --git a/core/src/main/kotlin/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.kt b/core/src/main/kotlin/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.kt new file mode 100644 index 000000000..ba8575102 --- /dev/null +++ b/core/src/main/kotlin/com/volmit/iris/core/pregenerator/cache/PregenCacheImpl.kt @@ -0,0 +1,232 @@ +package com.volmit.iris.core.pregenerator.cache + +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.io.IO +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import net.jpountz.lz4.LZ4BlockInputStream +import net.jpountz.lz4.LZ4BlockOutputStream +import java.io.* + +class PregenCacheImpl( + private val directory: File, + private val maxSize: Int +) : PregenCache { + private val cache = Object2ObjectLinkedOpenHashMap, Plate>() + + @ChunkCoordinates + override fun isChunkCached(x: Int, z: Int): Boolean { + return this[x shr 10, z shr 10].isCached( + (x shr 5) and 31, + (z shr 5) and 31 + ) { isCached(x and 31, z and 31) } + } + + @RegionCoordinates + override fun isRegionCached(x: Int, z: Int): Boolean { + return this[x shr 5, z shr 5].isCached( + x and 31, + z and 31, + Region::isCached + ) + } + + @ChunkCoordinates + override fun cacheChunk(x: Int, z: Int) { + this[x shr 10, z shr 10].cache( + (x shr 5) and 31, + (z shr 5) and 31 + ) { cache(x and 31, z and 31) } + } + + @RegionCoordinates + override fun cacheRegion(x: Int, z: Int) { + this[x shr 5, z shr 5].cache( + x and 31, + z and 31, + Region::cache + ) + } + + override fun write() { + if (cache.isEmpty()) return + runBlocking { + for (plate in cache.values) { + if (!plate.dirty) continue + launch(dispatcher) { + writePlate(plate) + } + } + } + } + + override fun trim(unloadDuration: Long) { + if (cache.isEmpty()) return + val threshold = System.currentTimeMillis() - unloadDuration + runBlocking { + val it = cache.values.iterator() + while (it.hasNext()) { + val plate = it.next() + if (plate.lastAccess < threshold) it.remove() + launch(dispatcher) { + writePlate(plate) + } + } + } + } + + private operator fun get(x: Int, z: Int): Plate { + val key = x to z + val plate = cache.getAndMoveToFirst(key) + if (plate != null) return plate + return readPlate(x, z).also { + cache.putAndMoveToFirst(key, it) + runBlocking { + while (cache.size > maxSize) { + val plate = cache.removeLast() + launch(dispatcher) { + writePlate(plate) + } + } + } + } + } + + private fun readPlate(x: Int, z: Int): Plate { + val file = fileForPlate(x, z) + if (!file.exists()) return Plate(x, z) + try { + DataInputStream(LZ4BlockInputStream(file.inputStream())).use { + return readPlate(x, z, it) + } + } catch (e: IOException) { + Iris.error("Failed to read pregen cache $file") + e.printStackTrace() + Iris.reportError(e) + } + return Plate(x, z) + } + + private fun writePlate(plate: Plate) { + if (!plate.dirty) return + val file = fileForPlate(plate.x, plate.z) + try { + IO.write(file, { DataOutputStream(LZ4BlockOutputStream(it)) }, plate::write) + plate.dirty = false + } catch (e: IOException) { + Iris.error("Failed to write preen cache $file") + e.printStackTrace() + Iris.reportError(e) + } + } + + private fun fileForPlate(x: Int, z: Int): File { + check(!(!directory.exists() && !directory.mkdirs())) { "Cannot create directory: " + directory.absolutePath } + return File(directory, "c.$x.$z.lz4b") + } + + private class Plate( + val x: Int, + val z: Int, + private var count: Short = 0, + private var regions: Array? = arrayOfNulls(1024) + ) { + var dirty: Boolean = false + var lastAccess: Long = System.currentTimeMillis() + + fun cache(x: Int, z: Int, predicate: Region.() -> Boolean): Boolean { + lastAccess = System.currentTimeMillis() + if (count == SIZE) return false + val region = regions!!.run { this[x * 32 + z] ?: Region().also { this[x * 32 + z] = it } } + if (!region.predicate()) return false + if (++count == SIZE) regions = null + dirty = true + return true + } + + fun isCached(x: Int, z: Int, predicate: Region.() -> Boolean): Boolean { + lastAccess = System.currentTimeMillis() + if (count == SIZE) return true + val region = regions!![x * 32 + z] ?: return false + return region.predicate() + } + + fun write(dos: DataOutput) { + Varint.writeSignedVarInt(count.toInt(), dos) + regions?.forEach { + dos.writeBoolean(it == null) + it?.write(dos) + } + } + } + + private class Region( + private var count: Short = 0, + private var words: LongArray? = LongArray(64) + ) { + fun cache(): Boolean { + if (count == SIZE) return false + count = SIZE + words = null + return true + } + + fun cache(x: Int, z: Int): Boolean { + if (count == SIZE) return false + val words = words ?: return false + val i = x * 32 + z + val w = i shr 6 + val b = 1L shl (i and 63) + + val cur = (words[w] and b) != 0L + if (cur) return false + + if (++count == SIZE) { + this.words = null + return true + } else { + words[w] = words[w] or b + return false + } + } + + fun isCached(): Boolean = count == SIZE + fun isCached(x: Int, z: Int): Boolean { + val i = x * 32 + z + return count == SIZE || (words!![i shr 6] and (1L shl (i and 63))) != 0L + } + + @Throws(IOException::class) + fun write(dos: DataOutput) { + Varint.writeSignedVarInt(count.toInt(), dos) + words?.forEach { Varint.writeUnsignedVarLong(it, dos) } + } + } + + companion object { + private val dispatcher = Dispatchers.IO.limitedParallelism(4) + private const val SIZE: Short = 1024 + + @Throws(IOException::class) + private fun readPlate(x: Int, z: Int, din: DataInput): Plate { + val count = Varint.readSignedVarInt(din) + if (count == 1024) return Plate(x, z, SIZE, null) + return Plate(x, z, count.toShort(), Array(1024) { + if (din.readBoolean()) null + else readRegion(din) + }) + } + + @Throws(IOException::class) + private fun readRegion(din: DataInput): Region { + val count = Varint.readSignedVarInt(din) + return if (count == 1024) Region(SIZE, null) + else Region(count.toShort(), LongArray(64) { Varint.readUnsignedVarLong(din) }) + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt index 768f2a9d3..8a091a50f 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/EngineScript.kt @@ -14,9 +14,9 @@ abstract class EngineScript object EngineScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { providedProperties( "engine" to Engine::class, - "complex" to IrisComplex::class, "seed" to Long::class, "dimension" to IrisDimension::class, + "complex" to IrisComplex::class, "biome" to BiomeLookup::class, ) }) { diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt index f594649e1..c8941bb8d 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/base/PreprocessorScript.kt @@ -1,6 +1,8 @@ package com.volmit.iris.core.scripting.kotlin.base import com.volmit.iris.core.loader.IrisRegistrant +import com.volmit.iris.engine.framework.Engine +import com.volmit.iris.engine.`object`.IrisDimension import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.ScriptCompilationConfiguration import kotlin.script.experimental.api.providedProperties @@ -8,8 +10,13 @@ import kotlin.script.experimental.api.providedProperties @KotlinScript(fileExtension = "proc.kts", compilationConfiguration = PreprocessorScriptDefinition::class) abstract class PreprocessorScript -object PreprocessorScriptDefinition : ScriptCompilationConfiguration(listOf(EngineScriptDefinition), { - providedProperties("object" to IrisRegistrant::class) +object PreprocessorScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { + providedProperties( + "engine" to Engine::class, + "seed" to Long::class, + "dimension" to IrisDimension::class, + "object" to IrisRegistrant::class + ) }) { private fun readResolve(): Any = PreprocessorScriptDefinition } \ No newline at end of file diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt index fc80c7738..2114f29d4 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt @@ -9,15 +9,20 @@ import com.volmit.iris.core.scripting.kotlin.base.EngineScript import com.volmit.iris.core.scripting.kotlin.base.MobSpawningScript import com.volmit.iris.core.scripting.kotlin.base.PostMobSpawningScript import com.volmit.iris.core.scripting.kotlin.base.PreprocessorScript +import com.volmit.iris.core.scripting.kotlin.environment.IrisSimpleExecutionEnvironment +import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.engine.framework.Engine import com.volmit.iris.util.mantle.MantleChunk import org.bukkit.Chunk import org.bukkit.Location import org.bukkit.entity.Entity +import java.io.File -data class IrisExecutionEnvironment( - private val engine: Engine -) : IrisPackExecutionEnvironment(engine.data), EngineEnvironment { +class IrisExecutionEnvironment internal constructor( + private val engine: Engine, + parent: ScriptRunner?, +) : IrisPackExecutionEnvironment(engine.data, parent), EngineEnvironment { + constructor(engine: Engine) : this(engine, null) override fun getEngine() = engine override fun execute(script: String) = @@ -33,18 +38,24 @@ data class IrisExecutionEnvironment( execute(script, PostMobSpawningScript::class.java, engine.parameters("location" to location, "entity" to mob)) override fun preprocessObject(script: String, `object`: IrisRegistrant) = - execute(script, PreprocessorScript::class.java, engine.parameters("object" to `object`)) + execute(script, PreprocessorScript::class.java, engine.limitedParameters("object" to `object`)) override fun updateChunk(script: String, mantleChunk: MantleChunk, chunk: Chunk, executor: UpdateExecutor) = execute(script, ChunkUpdateScript::class.java, engine.parameters("mantleChunk" to mantleChunk, "chunk" to chunk, "executor" to executor)) - private fun Engine.parameters(vararg values: Pair): Map { + private fun Engine.limitedParameters(vararg values: Pair): Map { return mapOf( "data" to data, "engine" to this, - "complex" to complex, "seed" to seedManager.seed, "dimension" to dimension, + *values, + ) + } + + private fun Engine.parameters(vararg values: Pair): Map { + return limitedParameters( + "complex" to complex, "biome" to BiomeLookup(::getSurfaceBiome), *values, ) diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt index 86832c963..412695f46 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt @@ -1,17 +1,22 @@ package com.volmit.iris.core.scripting.kotlin.environment import com.volmit.iris.core.loader.IrisData +import com.volmit.iris.core.scripting.environment.EngineEnvironment import com.volmit.iris.core.scripting.environment.PackEnvironment import com.volmit.iris.core.scripting.kotlin.base.DataScript import com.volmit.iris.core.scripting.kotlin.base.NoiseScript import com.volmit.iris.core.scripting.kotlin.runner.Script +import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.core.scripting.kotlin.runner.valueOrThrow +import com.volmit.iris.engine.framework.Engine import com.volmit.iris.util.math.RNG import kotlin.reflect.KClass -open class IrisPackExecutionEnvironment( - private val data: IrisData -) : IrisSimpleExecutionEnvironment(data.dataFolder), PackEnvironment { +open class IrisPackExecutionEnvironment internal constructor( + private val data: IrisData, + parent: ScriptRunner? +) : IrisSimpleExecutionEnvironment(data.dataFolder, parent), PackEnvironment { + constructor(data: IrisData) : this(data, null) override fun getData() = data @@ -31,6 +36,9 @@ open class IrisPackExecutionEnvironment( override fun createNoise(script: String, rng: RNG) = evaluate(script, NoiseScript::class.java, data.parameters("rng" to rng)) + override fun with(engine: Engine) = + IrisExecutionEnvironment(engine, runner) + private fun IrisData.parameters(vararg values: Pair): Map { return mapOf( "data" to this, diff --git a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt index 2896b5821..274807eee 100644 --- a/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ b/core/src/main/kotlin/com/volmit/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt @@ -4,10 +4,11 @@ import com.volmit.iris.Iris import com.volmit.iris.core.IrisSettings import com.volmit.iris.core.scripting.environment.SimpleEnvironment import com.volmit.iris.core.scripting.kotlin.base.* +import com.volmit.iris.core.scripting.kotlin.runner.FileComponents import com.volmit.iris.core.scripting.kotlin.runner.Script import com.volmit.iris.core.scripting.kotlin.runner.ScriptRunner import com.volmit.iris.core.scripting.kotlin.runner.classpath -import com.volmit.iris.core.scripting.kotlin.runner.valueOrNull +import com.volmit.iris.core.scripting.kotlin.runner.value import com.volmit.iris.core.scripting.kotlin.runner.valueOrThrow import com.volmit.iris.util.collection.KMap import com.volmit.iris.util.data.KCache @@ -18,11 +19,14 @@ import kotlin.script.experimental.annotations.KotlinScript import kotlin.script.experimental.api.ResultWithDiagnostics import kotlin.text.split -open class IrisSimpleExecutionEnvironment( - baseDir: File = File(".").absoluteFile +open class IrisSimpleExecutionEnvironment internal constructor( + baseDir: File, + parent: ScriptRunner? ) : SimpleEnvironment { - protected val compileCache = KCache, ResultWithDiagnostics