diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..3ffcddd8e --- /dev/null +++ b/build.gradle @@ -0,0 +1,268 @@ +import de.undercouch.gradle.tasks.download.Download +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.tasks.Jar +import org.gradle.jvm.toolchain.JavaLanguageVersion + +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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 . + */ + +buildscript { + repositories { + maven { + url = uri('https://jitpack.io') + } + } + dependencies { + classpath('com.github.VolmitSoftware:NMSTools:c88961416f') + } +} + +plugins { + id 'java' + id 'java-library' + alias(libs.plugins.download) +} + +group = 'art.arcane' +version = '4.0.0-1.21.11' +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') + .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .get() + +apply plugin: ApiGenerator + +// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED +// ======================== WINDOWS ============================= +registerCustomOutputTask('Cyberpwn', 'C://Users/cyberpwn/Documents/development/server/plugins') +registerCustomOutputTask('Psycho', 'C://Dan/MinecraftDevelopment/Server/plugins') +registerCustomOutputTask('ArcaneArts', 'C://Users/arcane/Documents/development/server/plugins') +registerCustomOutputTask('Coco', 'D://mcsm/plugins') +registerCustomOutputTask('Strange', 'D://Servers/1.17 Test Server/plugins') +registerCustomOutputTask('Vatuu', 'D://Minecraft/Servers/1.19.4/plugins') +registerCustomOutputTask('CrazyDev22', 'C://Users/Julian/Desktop/server/plugins') +registerCustomOutputTask('PixelFury', 'C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins') +registerCustomOutputTask('PixelFuryDev', 'C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins') +// ========================== UNIX ============================== +registerCustomOutputTaskUnix('CyberpwnLT', '/Users/danielmills/development/server/plugins') +registerCustomOutputTaskUnix('PsychoLT', '/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins') +registerCustomOutputTaskUnix('PixelMac', '/Users/test/Desktop/mcserver/plugins') +registerCustomOutputTaskUnix('CrazyDev22LT', '/home/julian/Desktop/server/plugins') +// ============================================================== + +def nmsBindings = [ + v1_21_R7: '1.21.11-R0.1-SNAPSHOT', +] +Class nmsTypeClass = Class.forName('NMSBinding$Type') +nmsBindings.each { key, value -> + project(":nms:${key}") { + apply plugin: JavaPlugin + + def nmsConfig = new Config() + nmsConfig.jvm = 21 + nmsConfig.version = value + nmsConfig.type = Enum.valueOf(nmsTypeClass, 'DIRECT') + extensions.extraProperties.set('nms', nmsConfig) + plugins.apply(NMSBinding) + + dependencies { + compileOnly(project(':core')) + compileOnly(volmLibCoordinate) { + changing = true + transitive = false + } + compileOnly(rootProject.libs.annotations) + compileOnly(rootProject.libs.byteBuddy.core) + } + } +} + +def included = configurations.create('included') +def jarJar = configurations.create('jarJar') +dependencies { + nmsBindings.keySet().each { key -> + add('included', project(path: ":nms:${key}", configuration: 'reobf')) + } + add('included', project(path: ':core', configuration: 'shadow')) + add('jarJar', project(':core:agent')) +} + +tasks.named('jar', Jar).configure { + inputs.files(included) + duplicatesStrategy = DuplicatesStrategy.EXCLUDE + from(jarJar, provider { included.resolve().collect { zipTree(it) } }) + archiveFileName.set("Iris-${project.version}.jar") +} + +tasks.register('iris', Copy) { + group = 'iris' + dependsOn('jar') + from(layout.buildDirectory.file("libs/Iris-${project.version}.jar")) + into(layout.buildDirectory) +} + +tasks.register('irisDev', Copy) { + group = 'iris' + from(project(':core').layout.buildDirectory.files('libs/core-javadoc.jar', 'libs/core-sources.jar')) + rename { String fileName -> fileName.replace('core', "Iris-${project.version}") } + into(layout.buildDirectory) + dependsOn(':core:sourcesJar') + dependsOn(':core:javadocJar') +} + +def cli = file('sentry-cli.exe') +tasks.register('downloadCli', Download) { + group = 'io.sentry' + src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty('os.name')}&package=sentry-cli") + dest(cli) + + doLast { + cli.setExecutable(true) + } +} + +tasks.register('release') { + group = 'io.sentry' + dependsOn('downloadCli') + doLast { + String url = 'http://sentry.volmit.com:8080' + def authToken = project.findProperty('sentry.auth.token') ?: System.getenv('SENTRY_AUTH_TOKEN') + String org = 'sentry' + String projectName = 'iris' + runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'new', '-o', org, '-p', projectName, version) + runCommand(cli, '--url', url, '--auth-token', authToken, 'releases', 'set-commits', '-o', org, '-p', projectName, version, '--auto', '--ignore-missing') + //exec(cli, "--url", url, "--auth-token", authToken, "releases", "finalize", "-o", org, "-p", projectName, version) + cli.delete() + } +} + +void runCommand(Object... command) { + Process process = new ProcessBuilder(command.collect { it.toString() }).start() + process.inputStream.readLines().each { println(it) } + process.errorStream.readLines().each { println(it) } + process.waitFor() +} + +configurations.configureEach { + resolutionStrategy.cacheChangingModulesFor(0, 'seconds') + resolutionStrategy.cacheDynamicVersionsFor(0, 'seconds') +} + +allprojects { + apply plugin: 'java' + + java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } + } + + repositories { + mavenCentral() + maven { url = uri('https://repo.papermc.io/repository/maven-public/') } + maven { url = uri('https://repo.codemc.org/repository/maven-public/') } + + maven { url = uri('https://jitpack.io') } // EcoItems, score + maven { url = uri('https://repo.nexomc.com/releases/') } // nexo + maven { url = uri('https://maven.devs.beer/') } // itemsadder + maven { url = uri('https://repo.extendedclip.com/releases/') } // placeholderapi + maven { url = uri('https://mvn.lumine.io/repository/maven-public/') } // mythic + maven { url = uri('https://nexus.phoenixdevt.fr/repository/maven-public/') } //MMOItems + maven { url = uri('https://repo.onarandombox.com/content/groups/public/') } //Multiverse Core + } + + dependencies { + // Provided or Classpath + compileOnly(rootProject.libs.lombok) + annotationProcessor(rootProject.libs.lombok) + } + + /** + * We need parameter meta for the decree command system + */ + tasks.named('compileJava', JavaCompile).configure { + options.compilerArgs.add('-parameters') + options.encoding = 'UTF-8' + options.debugOptions.debugLevel = 'none' + } + + tasks.named('javadoc').configure { + options.encoding = 'UTF-8' + options.quiet() + //options.addStringOption("Xdoclint:none") // TODO: Re-enable this + } + + tasks.register('sourcesJar', Jar) { + archiveClassifier.set('sources') + from(sourceSets.main.allSource) + } + + tasks.register('javadocJar', Jar) { + archiveClassifier.set('javadoc') + from(tasks.named('javadoc').map { it.destinationDir }) + } +} + +if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) { + System.err.println() + System.err.println('=========================================================================================================') + System.err.println('You must run gradle on Java 21 or newer. You are using ' + JavaVersion.current()) + System.err.println() + System.err.println('=== For IDEs ===') + System.err.println('1. Configure the project for Java 21 toolchain') + System.err.println('2. Configure the bundled gradle to use Java 21+ in settings') + System.err.println() + System.err.println('=== For Command Line (gradlew) ===') + System.err.println('1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html') + System.err.println('2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4') + System.err.println('3. Open a new command prompt window to get the new environment variables if need be.') + System.err.println('=========================================================================================================') + System.err.println() + System.exit(69) +} + +void registerCustomOutputTask(String name, String path) { + if (!System.getProperty('os.name').toLowerCase().contains('windows')) { + return + } + + tasks.register("build${name}", Copy) { + group = 'development' + outputs.upToDateWhen { false } + dependsOn('iris') + from(layout.buildDirectory.file("Iris-${project.version}.jar")) + into(file(path)) + rename { String ignored -> 'Iris.jar' } + } +} + +void registerCustomOutputTaskUnix(String name, String path) { + if (System.getProperty('os.name').toLowerCase().contains('windows')) { + return + } + + tasks.register("build${name}", Copy) { + group = 'development' + outputs.upToDateWhen { false } + dependsOn('iris') + from(layout.buildDirectory.file("Iris-${project.version}.jar")) + into(file(path)) + rename { String ignored -> 'Iris.jar' } + } +} diff --git a/build.gradle.kts b/build.gradle.kts deleted file mode 100644 index beb8f6c2e..000000000 --- a/build.gradle.kts +++ /dev/null @@ -1,263 +0,0 @@ -import de.undercouch.gradle.tasks.download.Download -import org.gradle.jvm.toolchain.JavaLanguageVersion -import kotlin.system.exitProcess - -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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 . - */ - -buildscript { - repositories.maven("https://jitpack.io") - dependencies.classpath("com.github.VolmitSoftware:NMSTools:c88961416f") -} - -plugins { - java - `java-library` - alias(libs.plugins.download) -} - -group = "art.arcane" -version = "4.0.0-1.21.11" -val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate") - .orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT") - .get() - -apply() - -// ADD YOURSELF AS A NEW LINE IF YOU WANT YOUR OWN BUILD TASK GENERATED -// ======================== WINDOWS ============================= -registerCustomOutputTask("Cyberpwn", "C://Users/cyberpwn/Documents/development/server/plugins") -registerCustomOutputTask("Psycho", "C://Dan/MinecraftDevelopment/Server/plugins") -registerCustomOutputTask("ArcaneArts", "C://Users/arcane/Documents/development/server/plugins") -registerCustomOutputTask("Coco", "D://mcsm/plugins") -registerCustomOutputTask("Strange", "D://Servers/1.17 Test Server/plugins") -registerCustomOutputTask("Vatuu", "D://Minecraft/Servers/1.19.4/plugins") -registerCustomOutputTask("CrazyDev22", "C://Users/Julian/Desktop/server/plugins") -registerCustomOutputTask("PixelFury", "C://Users/repix/workplace/Iris/1.21.3 - Development-Public-v3/plugins") -registerCustomOutputTask("PixelFuryDev", "C://Users/repix/workplace/Iris/1.21 - Development-v3/plugins") -// ========================== UNIX ============================== -registerCustomOutputTaskUnix("CyberpwnLT", "/Users/danielmills/development/server/plugins") -registerCustomOutputTaskUnix("PsychoLT", "/Users/brianfopiano/Developer/RemoteGit/[Minecraft Server]/consumers/plugin-consumers/dropins/plugins") -registerCustomOutputTaskUnix("PixelMac", "/Users/test/Desktop/mcserver/plugins") -registerCustomOutputTaskUnix("CrazyDev22LT", "/home/julian/Desktop/server/plugins") -// ============================================================== - -val nmsBindings = mapOf( - "v1_21_R7" to "1.21.11-R0.1-SNAPSHOT", -) -nmsBindings.forEach { (key, value) -> - project(":nms:$key") { - apply() - - nmsBinding { - jvm = 21 - version = value - type = NMSBinding.Type.DIRECT - } - - dependencies { - compileOnly(project(":core")) - compileOnly(volmLibCoordinate) { - isChanging = true - isTransitive = false - } - compileOnly(rootProject.libs.annotations) - compileOnly(rootProject.libs.byteBuddy.core) - } - } -} - -val included: Configuration by configurations.creating -val jarJar: Configuration by configurations.creating -dependencies { - for (key in nmsBindings.keys) { - included(project(":nms:$key", "reobf")) - } - included(project(":core", "shadow")) - jarJar(project(":core:agent")) -} - -tasks { - jar { - inputs.files(included) - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - from(jarJar, provider { included.resolve().map(::zipTree) }) - archiveFileName.set("Iris-${project.version}.jar") - } - - register("iris") { - group = "iris" - dependsOn("jar") - from(layout.buildDirectory.file("libs/Iris-${project.version}.jar")) - into(layout.buildDirectory) - } - - register("irisDev") { - group = "iris" - from(project(":core").layout.buildDirectory.files("libs/core-javadoc.jar", "libs/core-sources.jar")) - rename { it.replace("core", "Iris-${project.version}") } - into(layout.buildDirectory) - dependsOn(":core:sourcesJar") - dependsOn(":core:javadocJar") - } - - val cli = file("sentry-cli.exe") - register("downloadCli") { - group = "io.sentry" - src("https://release-registry.services.sentry.io/apps/sentry-cli/latest?response=download&arch=x86_64&platform=${System.getProperty("os.name")}&package=sentry-cli") - dest(cli) - - doLast { - cli.setExecutable(true) - } - } - - register("release") { - group = "io.sentry" - dependsOn("downloadCli") - doLast { - val url = "http://sentry.volmit.com:8080" - val authToken = project.findProperty("sentry.auth.token") ?: System.getenv("SENTRY_AUTH_TOKEN") - val org = "sentry" - val projectName = "iris" - exec(cli, "--url", url , "--auth-token", authToken, "releases", "new", "-o", org, "-p", projectName, version) - exec(cli, "--url", url , "--auth-token", authToken, "releases", "set-commits", "-o", org, "-p", projectName, version, "--auto", "--ignore-missing") - //exec(cli, "--url", url, "--auth-token", authToken, "releases", "finalize", "-o", org, "-p", projectName, version) - cli.delete() - } - } -} - -fun exec(vararg command: Any) { - val p = ProcessBuilder(command.map { it.toString() }) - .start() - p.inputStream.reader().useLines { it.forEach(::println) } - p.errorStream.reader().useLines { it.forEach(::println) } - p.waitFor() -} - -configurations.configureEach { - resolutionStrategy.cacheChangingModulesFor(0, "seconds") - resolutionStrategy.cacheDynamicVersionsFor(0, "seconds") -} - -allprojects { - apply() - - java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } - } - - repositories { - mavenCentral() - maven("https://repo.papermc.io/repository/maven-public/") - maven("https://repo.codemc.org/repository/maven-public/") - - maven("https://jitpack.io") // EcoItems, score - maven("https://repo.nexomc.com/releases/") // nexo - maven("https://maven.devs.beer/") // itemsadder - maven("https://repo.extendedclip.com/releases/") // placeholderapi - maven("https://mvn.lumine.io/repository/maven-public/") // mythic - maven("https://nexus.phoenixdevt.fr/repository/maven-public/") //MMOItems - maven("https://repo.onarandombox.com/content/groups/public/") //Multiverse Core - } - - dependencies { - // Provided or Classpath - compileOnly(rootProject.libs.lombok) - annotationProcessor(rootProject.libs.lombok) - } - - /** - * We need parameter meta for the decree command system - */ - tasks { - compileJava { - options.compilerArgs.add("-parameters") - options.encoding = "UTF-8" - options.debugOptions.debugLevel = "none" - } - - javadoc { - options.encoding = "UTF-8" - options.quiet() - //options.addStringOption("Xdoclint:none") // TODO: Re-enable this - } - - register("sourcesJar") { - archiveClassifier.set("sources") - from(sourceSets.main.map { it.allSource }) - } - - register("javadocJar") { - archiveClassifier.set("javadoc") - from(javadoc.map { it.destinationDir!! }) - } - } -} - -if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_21)) { - System.err.println() - System.err.println("=========================================================================================================") - System.err.println("You must run gradle on Java 21 or newer. You are using " + JavaVersion.current()) - System.err.println() - System.err.println("=== For IDEs ===") - System.err.println("1. Configure the project for Java 21 toolchain") - System.err.println("2. Configure the bundled gradle to use Java 21+ in settings") - System.err.println() - System.err.println("=== For Command Line (gradlew) ===") - System.err.println("1. Install JDK 21 from https://www.oracle.com/java/technologies/javase/jdk21-archive-downloads.html") - System.err.println("2. Set JAVA_HOME environment variable to the new jdk installation folder such as C:\\Program Files\\Java\\jdk-21.0.4") - System.err.println("3. Open a new command prompt window to get the new environment variables if need be.") - System.err.println("=========================================================================================================") - System.err.println() - exitProcess(69) -} - - -fun registerCustomOutputTask(name: String, path: String) { - if (!System.getProperty("os.name").lowercase().contains("windows")) { - return - } - - tasks.register("build$name") { - group = "development" - outputs.upToDateWhen { false } - dependsOn("iris") - from(layout.buildDirectory.file("Iris-${project.version}.jar")) - into(file(path)) - rename { "Iris.jar" } - } -} - -fun registerCustomOutputTaskUnix(name: String, path: String) { - if (System.getProperty("os.name").lowercase().contains("windows")) { - return - } - - tasks.register("build$name") { - group = "development" - outputs.upToDateWhen { false } - dependsOn("iris") - from(layout.buildDirectory.file("Iris-${project.version}.jar")) - into(file(path)) - rename { "Iris.jar" } - } -} diff --git a/buildSrc/build.gradle b/buildSrc/build.gradle new file mode 100644 index 000000000..add9425c9 --- /dev/null +++ b/buildSrc/build.gradle @@ -0,0 +1,25 @@ +import org.gradle.jvm.toolchain.JavaLanguageVersion + +plugins { + id 'java' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +repositories { + mavenCentral() + gradlePluginPortal() + maven { + url = uri('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') +} diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts deleted file mode 100644 index 983ee94c1..000000000 --- a/buildSrc/build.gradle.kts +++ /dev/null @@ -1,32 +0,0 @@ -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.jetbrains.kotlin.gradle.dsl.JvmTarget - -plugins { - kotlin("jvm") version embeddedKotlinVersion -} - -java { - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - -kotlin { - jvmToolchain(21) - compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) - } -} - -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") -} diff --git a/buildSrc/src/main/java/ApiGenerator.java b/buildSrc/src/main/java/ApiGenerator.java new file mode 100644 index 000000000..4a90df4e7 --- /dev/null +++ b/buildSrc/src/main/java/ApiGenerator.java @@ -0,0 +1,162 @@ +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +import org.gradle.api.publish.PublishingExtension; +import org.gradle.api.publish.maven.MavenPublication; +import org.gradle.api.tasks.InputFile; +import org.gradle.api.tasks.OutputFile; +import org.gradle.api.tasks.TaskAction; +import org.gradle.api.tasks.TaskProvider; +import org.gradle.jvm.tasks.Jar; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarOutputStream; + +public class ApiGenerator implements Plugin { + @Override + public void apply(Project target) { + target.getPlugins().apply("maven-publish"); + TaskProvider task = target.getTasks().register("irisApi", GenerateApiTask.class); + + PublishingExtension publishing = target.getExtensions().findByType(PublishingExtension.class); + if (publishing == null) { + throw new GradleException("Publishing extension not found"); + } + + publishing.getRepositories().maven(repository -> { + repository.setName("deployDir"); + repository.setUrl(targetDirectory(target).toURI()); + }); + + publishing.getPublications().create("maven", MavenPublication.class, publication -> { + publication.setGroupId(target.getName()); + publication.setVersion(target.getVersion().toString()); + publication.artifact(task); + }); + } + + public static File targetDirectory(Project project) { + String dir = System.getenv("DEPLOY_DIR"); + if (dir == null) { + return project.getLayout().getBuildDirectory().dir("api").get().getAsFile(); + } + return new File(dir); + } +} + +abstract class GenerateApiTask extends DefaultTask { + private final File inputFile; + private final File outputFile; + + public GenerateApiTask() { + setGroup("iris"); + dependsOn("jar"); + finalizedBy("publishMavenPublicationToDeployDirRepository"); + doLast(task -> getLogger().lifecycle("The API is located at " + getOutputFile().getAbsolutePath())); + + TaskProvider jarTask = getProject().getTasks().named("jar", Jar.class); + this.inputFile = jarTask.get().getArchiveFile().get().getAsFile(); + this.outputFile = ApiGenerator.targetDirectory(getProject()).toPath().resolve(this.inputFile.getName()).toFile(); + } + + @InputFile + public File getInputFile() { + return inputFile; + } + + @OutputFile + public File getOutputFile() { + return outputFile; + } + + @TaskAction + public void generate() throws IOException { + File parent = outputFile.getParentFile(); + if (parent != null) { + parent.mkdirs(); + } + + try (JarFile jar = new JarFile(inputFile); + JarOutputStream out = new JarOutputStream(new FileOutputStream(outputFile))) { + jar.stream() + .parallel() + .filter(entry -> !entry.isDirectory()) + .filter(entry -> entry.getName().endsWith(".class")) + .forEach(entry -> writeStrippedClass(jar, out, entry)); + } + } + + private static void writeStrippedClass(JarFile jar, JarOutputStream out, JarEntry entry) { + byte[] bytes; + try (InputStream input = jar.getInputStream(entry)) { + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS); + ClassVisitor visitor = new MethodClearingVisitor(writer); + ClassReader reader = new ClassReader(input); + reader.accept(visitor, 0); + bytes = writer.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + synchronized (out) { + try { + JarEntry outputEntry = new JarEntry(entry.getName()); + out.putNextEntry(outputEntry); + out.write(bytes); + out.closeEntry(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } +} + +class MethodClearingVisitor extends ClassVisitor { + public MethodClearingVisitor(ClassVisitor cv) { + super(Opcodes.ASM9, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { + return new ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions)); + } +} + +class ExceptionThrowingMethodVisitor extends MethodVisitor { + public ExceptionThrowingMethodVisitor(MethodVisitor mv) { + super(Opcodes.ASM9, mv); + } + + @Override + public void visitCode() { + if (mv == null) { + return; + } + + mv.visitCode(); + mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException"); + mv.visitInsn(Opcodes.DUP); + mv.visitLdcInsn("Only API"); + mv.visitMethodInsn( + Opcodes.INVOKESPECIAL, + "java/lang/IllegalStateException", + "", + "(Ljava/lang/String;)V", + false + ); + mv.visitInsn(Opcodes.ATHROW); + mv.visitMaxs(0, 0); + mv.visitEnd(); + } +} diff --git a/buildSrc/src/main/java/Config.java b/buildSrc/src/main/java/Config.java new file mode 100644 index 000000000..92d71b086 --- /dev/null +++ b/buildSrc/src/main/java/Config.java @@ -0,0 +1,5 @@ +public class Config { + public int jvm = 21; + public NMSBinding.Type type = NMSBinding.Type.DIRECT; + public String version; +} diff --git a/buildSrc/src/main/java/NMSBinding.java b/buildSrc/src/main/java/NMSBinding.java new file mode 100644 index 000000000..61016f589 --- /dev/null +++ b/buildSrc/src/main/java/NMSBinding.java @@ -0,0 +1,274 @@ +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 org.gradle.api.Action; +import org.gradle.api.DefaultTask; +import org.gradle.api.GradleException; +import org.gradle.api.Named; +import org.gradle.api.Plugin; +import org.gradle.api.Project; +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.file.FileTree; +import org.gradle.api.model.ObjectFactory; +import org.gradle.api.plugins.JavaPluginExtension; +import org.gradle.api.plugins.ExtraPropertiesExtension; +import org.gradle.api.provider.Provider; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskAction; +import org.gradle.jvm.toolchain.JavaLanguageVersion; +import org.gradle.jvm.toolchain.JavaToolchainService; +import org.gradle.work.DisableCachingByDefault; + +import javax.inject.Inject; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static io.papermc.paperweight.util.constants.ConstantsKt.REOBF_CONFIG; + +public class NMSBinding implements Plugin { + private static final String NEW_LINE = System.lineSeparator(); + private static final byte[] NEW_LINE_BYTES = NEW_LINE.getBytes(StandardCharsets.UTF_8); + + @Override + public void apply(Project target) { + ExtraPropertiesExtension extra = target.getExtensions().getExtraProperties(); + Object configValue = extra.has("nms") ? extra.get("nms") : null; + if (!(configValue instanceof Config)) { + throw new GradleException("No NMS binding configuration found"); + } + + Config config = (Config) configValue; + int jvm = config.jvm; + Type type = config.type; + + if (type == Type.USER_DEV) { + target.getPlugins().apply(PaperweightUser.class); + + PaperweightUserDependenciesExtension dependenciesExtension = + target.getDependencies().getExtensions().findByType(PaperweightUserDependenciesExtension.class); + if (dependenciesExtension != null) { + dependenciesExtension.paperDevBundle(config.version); + } + + JavaPluginExtension java = target.getExtensions().findByType(JavaPluginExtension.class); + if (java == null) { + throw new GradleException("Java plugin not found"); + } + + java.getToolchain().getLanguageVersion().set(JavaLanguageVersion.of(jvm)); + JavaToolchainService javaToolchains = target.getExtensions().getByType(JavaToolchainService.class); + target.getExtensions().configure(PaperweightUserExtension.class, + extension -> extension.getJavaLauncher().set(javaToolchains.launcherFor(java.getToolchain()))); + } else { + extra.set("nmsTools.useBuildTools", type == Type.BUILD_TOOLS); + target.getPlugins().apply(NMSToolsPlugin.class); + target.getExtensions().configure(NMSToolsExtension.class, extension -> { + extension.getJvm().set(jvm); + extension.getVersion().set(config.version); + }); + + ObjectFactory objects = target.getObjects(); + target.getConfigurations().register(REOBF_CONFIG, configuration -> { + configuration.setCanBeConsumed(true); + configuration.setCanBeResolved(false); + configuration.getAttributes().attribute(Usage.USAGE_ATTRIBUTE, named(objects, Usage.class, Usage.JAVA_RUNTIME)); + configuration.getAttributes().attribute(Category.CATEGORY_ATTRIBUTE, named(objects, Category.class, Category.LIBRARY)); + configuration.getAttributes().attribute(LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, named(objects, LibraryElements.class, LibraryElements.JAR)); + configuration.getAttributes().attribute(Bundling.BUNDLING_ATTRIBUTE, named(objects, Bundling.class, Bundling.EXTERNAL)); + configuration.getAttributes().attribute(Obfuscation.Companion.getOBFUSCATION_ATTRIBUTE(), named(objects, Obfuscation.class, Obfuscation.OBFUSCATED)); + configuration.getOutgoing().artifact(target.getTasks().named("remap")); + }); + } + + int[] version = parseVersion(config.version); + int major = version[0]; + int minor = version[1]; + if (major <= 20 && minor <= 4) { + return; + } + + target.getTasks().register("convert", ConversionTask.class, type); + target.getTasks().named("compileJava").configure(task -> task.dependsOn("convert")); + target.getRootProject().getTasks() + .matching(task -> task.getName().equals("prepareKotlinBuildScriptModel")) + .configureEach(task -> task.dependsOn(target.getPath() + ":convert")); + } + + public static void nmsBinding(Project project, Action action) { + Config config = new Config(); + action.execute(config); + project.getExtensions().getExtraProperties().set("nms", config); + project.getPlugins().apply(NMSBinding.class); + } + + private static int[] parseVersion(String version) { + String trimmed = version; + int suffix = trimmed.indexOf('-'); + if (suffix >= 0) { + trimmed = trimmed.substring(0, suffix); + } + + String[] parts = trimmed.split("\\."); + return new int[]{Integer.parseInt(parts[1]), Integer.parseInt(parts[2])}; + } + + private static T named(ObjectFactory objects, Class type, String name) { + return objects.named(type, name); + } + + @DisableCachingByDefault + public abstract static class ConversionTask extends DefaultTask { + private final Pattern pattern; + private final String replacement; + + @Inject + public ConversionTask(Type type) { + setGroup("nms"); + getInputs().property("type", type); + + JavaPluginExtension java = getProject().getExtensions().findByType(JavaPluginExtension.class); + if (java == null) { + throw new GradleException("Java plugin not found"); + } + + Provider source = java.getSourceSets().named(SourceSet.MAIN_SOURCE_SET_NAME).map(SourceSet::getAllJava); + getInputs().files(source); + getOutputs().files(source); + + if (type == Type.USER_DEV) { + this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\." + getProject().getName()); + this.replacement = "org.bukkit.craftbukkit"; + } else { + this.pattern = Pattern.compile("org\\.bukkit\\.craftbukkit\\.(?!" + getProject().getName() + ")"); + this.replacement = "org.bukkit.craftbukkit." + getProject().getName() + "."; + } + } + + @TaskAction + public void process() { + ExecutorService executor = Executors.newFixedThreadPool(16); + try { + Set files = getInputs().getFiles().getFiles(); + List> futures = new ArrayList<>(files.size()); + for (File file : files) { + if (!file.getName().endsWith(".java")) { + continue; + } + futures.add(executor.submit(() -> processFile(file))); + } + + for (Future future : futures) { + future.get(); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } catch (ExecutionException e) { + throw new RuntimeException(e.getCause()); + } finally { + executor.shutdown(); + } + } + + private void processFile(File file) { + List output = new ArrayList<>(); + boolean changed = false; + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (line.startsWith("package") || line.isBlank()) { + output.add(line); + continue; + } + + if (!line.startsWith("import")) { + if (!changed) { + return; + } + + output.add(line); + continue; + } + + Matcher matcher = pattern.matcher(line); + if (!matcher.find()) { + output.add(line); + continue; + } + + output.add(matcher.replaceAll(replacement)); + changed = true; + } + } catch (IOException e) { + throw new RuntimeException(e); + } + + if (!changed) { + return; + } + + try { + if (hasTrailingNewLine(file)) { + output.add(""); + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (int i = 0; i < output.size(); i++) { + writer.append(output.get(i)); + if (i + 1 < output.size()) { + writer.append(NEW_LINE); + } + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private boolean hasTrailingNewLine(File file) throws IOException { + if (NEW_LINE_BYTES.length == 0) { + return false; + } + + try (RandomAccessFile raf = new RandomAccessFile(file, "r")) { + if (raf.length() < NEW_LINE_BYTES.length) { + return false; + } + + byte[] bytes = new byte[NEW_LINE_BYTES.length]; + raf.seek(raf.length() - bytes.length); + raf.readFully(bytes); + return Arrays.equals(bytes, NEW_LINE_BYTES); + } + } + } + + public enum Type { + USER_DEV, + BUILD_TOOLS, + DIRECT + } +} diff --git a/buildSrc/src/main/kotlin/ApiGenerator.kt b/buildSrc/src/main/kotlin/ApiGenerator.kt deleted file mode 100644 index 530d4c6eb..000000000 --- a/buildSrc/src/main/kotlin/ApiGenerator.kt +++ /dev/null @@ -1,121 +0,0 @@ -import org.gradle.api.DefaultTask -import org.gradle.api.Plugin -import org.gradle.api.Project -import org.gradle.api.publish.PublishingExtension -import org.gradle.api.publish.maven.MavenPublication -import org.gradle.api.tasks.InputFile -import org.gradle.api.tasks.OutputFile -import org.gradle.api.tasks.TaskAction -import org.gradle.jvm.tasks.Jar -import org.objectweb.asm.* -import java.io.File -import java.util.jar.JarFile -import java.util.jar.JarOutputStream - -class ApiGenerator : Plugin { - override fun apply(target: Project): Unit = with(target) { - plugins.apply("maven-publish") - val task = tasks.register("irisApi", GenerateApiTask::class.java) - extensions.findByType(PublishingExtension::class.java)!!.apply { - repositories.maven { - it.name = "deployDir" - it.url = targetDirectory.toURI() - } - - publications.create("maven", MavenPublication::class.java) { - it.groupId = name - it.version = version.toString() - it.artifact(task) - } - } - } -} - -abstract class GenerateApiTask : DefaultTask() { - init { - group = "iris" - dependsOn("jar") - finalizedBy("publishMavenPublicationToDeployDirRepository") - doLast { - logger.lifecycle("The API is located at ${outputFile.absolutePath}") - } - } - - @InputFile - val inputFile: File = project.tasks - .named("jar", Jar::class.java) - .get() - .archiveFile - .get() - .asFile - - @OutputFile - val outputFile: File = project.targetDirectory.resolve(inputFile.name) - - @TaskAction - fun generate() { - JarFile(inputFile).use { jar -> - JarOutputStream(outputFile.apply { parentFile?.mkdirs() }.outputStream()).use { out -> - jar.stream() - .parallel() - .filter { !it.isDirectory } - .filter { it.name.endsWith(".class") } - .forEach { - val bytes = jar.getInputStream(it).use { input -> - val writer = ClassWriter(ClassWriter.COMPUTE_MAXS) - val visitor = MethodClearingVisitor(writer) - ClassReader(input).accept(visitor, 0) - writer.toByteArray() - } - - synchronized(out) { - out.putNextEntry(it) - out.write(bytes) - out.closeEntry() - } - } - } - } - } -} - -val Project.targetDirectory: File get() { - val dir = System.getenv("DEPLOY_DIR") ?: return project.layout.buildDirectory.dir("api").get().asFile - return File(dir) -} - -private class MethodClearingVisitor( - cv: ClassVisitor -) : ClassVisitor(Opcodes.ASM9, cv) { - - override fun visitMethod( - access: Int, - name: String?, - descriptor: String?, - signature: String?, - exceptions: Array? - ) = ExceptionThrowingMethodVisitor(super.visitMethod(access, name, descriptor, signature, exceptions)) -} - -private class ExceptionThrowingMethodVisitor( - mv: MethodVisitor -) : MethodVisitor(Opcodes.ASM9, mv) { - - override fun visitCode() { - if (mv == null) return - mv.visitCode() - - mv.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException") - mv.visitInsn(Opcodes.DUP) - mv.visitLdcInsn("Only API") - mv.visitMethodInsn( - Opcodes.INVOKESPECIAL, - "java/lang/IllegalStateException", - "", "(Ljava/lang/String;)V", false - ) - mv.visitInsn(Opcodes.ATHROW) - - mv.visitMaxs(0, 0) - mv.visitEnd() - } -} \ No newline at end of file diff --git a/buildSrc/src/main/kotlin/NMSBinding.kt b/buildSrc/src/main/kotlin/NMSBinding.kt deleted file mode 100644 index 14a7c3cca..000000000 --- a/buildSrc/src/main/kotlin/NMSBinding.kt +++ /dev/null @@ -1,182 +0,0 @@ -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) diff --git a/core/agent/build.gradle b/core/agent/build.gradle new file mode 100644 index 000000000..baff85547 --- /dev/null +++ b/core/agent/build.gradle @@ -0,0 +1,14 @@ +import org.gradle.jvm.tasks.Jar + +plugins { + id 'java' +} + +tasks.named('jar', Jar).configure { + manifest.attributes( + 'Agent-Class': 'art.arcane.iris.util.project.agent.Installer', + 'Premain-Class': 'art.arcane.iris.util.project.agent.Installer', + 'Can-Redefine-Classes': true, + 'Can-Retransform-Classes': true + ) +} diff --git a/core/agent/build.gradle.kts b/core/agent/build.gradle.kts deleted file mode 100644 index 0421e533b..000000000 --- a/core/agent/build.gradle.kts +++ /dev/null @@ -1,12 +0,0 @@ -plugins { - java -} - -tasks.jar { - manifest.attributes( - "Agent-Class" to "art.arcane.iris.util.project.agent.Installer", - "Premain-Class" to "art.arcane.iris.util.project.agent.Installer", - "Can-Redefine-Classes" to true, - "Can-Retransform-Classes" to true - ) -} \ No newline at end of file diff --git a/core/build.gradle b/core/build.gradle new file mode 100644 index 000000000..47b946d91 --- /dev/null +++ b/core/build.gradle @@ -0,0 +1,257 @@ +import io.github.slimjar.resolver.data.Mirror +import org.ajoberstar.grgit.Grgit +import org.gradle.api.tasks.Copy +import org.gradle.api.tasks.compile.JavaCompile +import org.gradle.jvm.tasks.Jar +import org.gradle.jvm.toolchain.JavaLanguageVersion +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +import java.net.URI + +/* + * Iris is a World Generator for Minecraft Bukkit Servers + * Copyright (c) 2021 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 . + */ + +plugins { + id 'java' + id 'java-library' + alias(libs.plugins.shadow) + alias(libs.plugins.sentry) + alias(libs.plugins.slimjar) + alias(libs.plugins.grgit) + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.kotlin.lombok) +} + +def apiVersion = '1.21' +def mainClass = 'art.arcane.iris.Iris' +def lib = 'art.arcane.iris.util' +String volmLibCoordinate = providers.gradleProperty('volmLibCoordinate') + .orElse('com.github.VolmitSoftware:VolmLib:master-SNAPSHOT') + .get() + +/** + * Dependencies. + * + * Provided or classpath dependencies are not shaded and are available on the runtime classpath + * + * Shaded dependencies are not available at runtime, nor are they available on mvn central so they + * need to be shaded into the jar (increasing binary size) + * + * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the + * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare + * these dependencies if they are available on mvn central. + */ +dependencies { + // Provided or Classpath + compileOnly(libs.spigot) + compileOnly(libs.log4j.api) + compileOnly(libs.log4j.core) + + // Third Party Integrations + compileOnly(libs.nexo) + compileOnly(libs.itemsadder) + compileOnly(libs.placeholderApi) + compileOnly(libs.score) + compileOnly(libs.mmoitems) + compileOnly(libs.ecoitems) + compileOnly(libs.mythic) + compileOnly(libs.mythicChrucible) + compileOnly(libs.kgenerators) { + transitive = false + } + compileOnly(libs.multiverseCore) + + // Shaded + implementation('de.crazydev22.slimjar.helper:spigot:2.1.5') + implementation(volmLibCoordinate) { + changing = true + transitive = false + } + + // Dynamically Loaded + slim(libs.paralithic) + slim(libs.paperlib) + slim(libs.adventure.api) + slim(libs.adventure.minimessage) + slim(libs.adventure.platform) + slim(libs.bstats) + slim(libs.sentry) + + slim(libs.commons.io) + slim(libs.commons.lang) + slim(libs.commons.lang3) + slim(libs.commons.math3) + slim(libs.oshi) + slim(libs.lz4) + slim(libs.fastutil) + slim(libs.lru) + slim(libs.zip) + slim(libs.gson) + slim(libs.asm) + slim(libs.caffeine) + slim(libs.byteBuddy.core) + slim(libs.byteBuddy.agent) + slim(libs.dom4j) + slim(libs.jaxen) + + // Script Engine + slim(libs.kotlin.stdlib) + slim(libs.kotlin.coroutines) +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } +} + +kotlin { + jvmToolchain(21) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_21) + } +} + +sentry { + url = 'http://sentry.volmit.com:8080' + autoInstallation.enabled = false + includeSourceContext = true + + org = 'sentry' + projectName = 'iris' + authToken = findProperty('sentry.auth.token') as String ?: System.getenv('SENTRY_AUTH_TOKEN') +} + +slimJar { + mirrors = [ + new Mirror( + URI.create('https://maven-central.storage-download.googleapis.com/maven2').toURL(), + URI.create('https://repo.maven.apache.org/maven2/').toURL() + ) + ] + + relocate('com.dfsek.paralithic', "${lib}.paralithic") + relocate('io.papermc.lib', "${lib}.paper") + relocate('net.kyori', "${lib}.kyori") + relocate('org.bstats', "${lib}.metrics") + relocate('io.sentry', "${lib}.sentry") + relocate('org.apache.maven', "${lib}.maven") + relocate('org.codehaus.plexus', "${lib}.plexus") + relocate('org.eclipse.sisu', "${lib}.sisu") + relocate('org.eclipse.aether', "${lib}.aether") + relocate('com.google.inject', "${lib}.guice") + relocate('org.dom4j', "${lib}.dom4j") + relocate('org.jaxen', "${lib}.jaxen") + relocate('com.github.benmanes.caffeine', "${lib}.caffeine") +} + +def embeddedAgentJar = project(':core:agent').tasks.named('jar', Jar) +def templateSource = file('src/main/templates') +def templateDest = layout.buildDirectory.dir('generated/sources/templates') +def generateTemplates = tasks.register('generateTemplates', Copy) { + inputs.properties([ + environment: providers.provider { + if (project.hasProperty('release')) { + return 'production' + } + if (project.hasProperty('argghh')) { + return 'Argghh!' + } + return 'development' + }, + commit: providers.provider { + String commitId = null + Exception failure = null + try { + commitId = project.extensions.getByType(Grgit).head().id + } catch (Exception ex) { + failure = ex + } + + if (commitId != null && commitId.length() == 40) { + return commitId + } + + logger.error('Git commit hash not found', failure) + return 'unknown' + }, + ]) + + from(templateSource) + into(templateDest) + rename { String fileName -> "art/arcane/iris/${fileName}" } + expand(inputs.properties) +} + +tasks.named('compileJava', JavaCompile).configure { + /** + * We need parameter meta for the decree command system + */ + options.compilerArgs.add('-parameters') + options.encoding = 'UTF-8' + options.debugOptions.debugLevel = 'none' +} + +tasks.named('processResources').configure { + /** + * Expand properties into plugin yml + */ + def pluginProperties = [ + name : rootProject.name, + version : rootProject.version, + apiVersion: apiVersion, + main : mainClass, + ] + inputs.properties(pluginProperties) + filesMatching('**/plugin.yml') { + expand(pluginProperties) + } +} + +tasks.named('shadowJar', com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar).configure { + dependsOn(embeddedAgentJar) + mergeServiceFiles() + //minimize() + relocate('io.github.slimjar', "${lib}.slimjar") + exclude('modules/loader-agent.isolated-jar') + from(embeddedAgentJar.map { it.archiveFile }) { + rename { String ignored -> 'agent.jar' } + } +} + +tasks.named('sentryCollectSourcesJava').configure { + dependsOn(generateTemplates) +} + +tasks.named('generateSentryBundleIdJava').configure { + dependsOn(generateTemplates) +} + +rootProject.tasks.matching { + it.name == 'prepareKotlinBuildScriptModel' +}.configureEach { + dependsOn(generateTemplates) +} + +sourceSets { + main { + java { + srcDir(generateTemplates.map { it.outputs }) + } + } +} diff --git a/core/build.gradle.kts b/core/build.gradle.kts deleted file mode 100644 index e38516e62..000000000 --- a/core/build.gradle.kts +++ /dev/null @@ -1,246 +0,0 @@ -import io.github.slimjar.func.slimjarHelper -import io.github.slimjar.resolver.data.Mirror -import org.ajoberstar.grgit.Grgit -import org.gradle.jvm.toolchain.JavaLanguageVersion -import org.gradle.jvm.tasks.Jar -import org.jetbrains.kotlin.gradle.dsl.JvmTarget -import java.net.URI - -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2021 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 . - */ - -plugins { - java - `java-library` - alias(libs.plugins.shadow) - alias(libs.plugins.sentry) - alias(libs.plugins.slimjar) - alias(libs.plugins.grgit) - alias(libs.plugins.kotlin.jvm) - alias(libs.plugins.kotlin.lombok) -} - -val apiVersion = "1.21" -val main = "art.arcane.iris.Iris" -val lib = "art.arcane.iris.util" -val volmLibCoordinate: String = providers.gradleProperty("volmLibCoordinate") - .orElse("com.github.VolmitSoftware:VolmLib:master-SNAPSHOT") - .get() - -/** - * Dependencies. - * - * Provided or classpath dependencies are not shaded and are available on the runtime classpath - * - * Shaded dependencies are not available at runtime, nor are they available on mvn central so they - * need to be shaded into the jar (increasing binary size) - * - * Dynamically loaded dependencies are defined in the plugin.yml (updating these must be updated in the - * plugin.yml also, otherwise they wont be available). These do not increase binary size). Only declare - * these dependencies if they are available on mvn central. - */ -dependencies { - // Provided or Classpath - compileOnly(libs.spigot) - compileOnly(libs.log4j.api) - compileOnly(libs.log4j.core) - - // Third Party Integrations - compileOnly(libs.nexo) - compileOnly(libs.itemsadder) - compileOnly(libs.placeholderApi) - compileOnly(libs.score) - compileOnly(libs.mmoitems) - compileOnly(libs.ecoitems) - compileOnly(libs.mythic) - compileOnly(libs.mythicChrucible) - compileOnly(libs.kgenerators) { - isTransitive = false - } - compileOnly(libs.multiverseCore) - - // Shaded - implementation(slimjarHelper("spigot")) - implementation(volmLibCoordinate) { - isChanging = true - isTransitive = false - } - - // Dynamically Loaded - slim(libs.paralithic) - slim(libs.paperlib) - slim(libs.adventure.api) - slim(libs.adventure.minimessage) - slim(libs.adventure.platform) - slim(libs.bstats) - slim(libs.sentry) - - slim(libs.commons.io) - slim(libs.commons.lang) - slim(libs.commons.lang3) - slim(libs.commons.math3) - slim(libs.oshi) - slim(libs.lz4) - slim(libs.fastutil) - slim(libs.lru) - slim(libs.zip) - slim(libs.gson) - slim(libs.asm) - slim(libs.caffeine) - slim(libs.byteBuddy.core) - slim(libs.byteBuddy.agent) - slim(libs.dom4j) - slim(libs.jaxen) - - // Script Engine - slim(libs.kotlin.stdlib) - slim(libs.kotlin.coroutines) - slim(libs.kotlin.scripting.common) - slim(libs.kotlin.scripting.jvm) - slim(libs.kotlin.scripting.jvm.host) - slim(libs.kotlin.scripting.dependencies.maven) { - constraints { - slim(libs.mavenCore) - } - } -} - -java { - disableAutoTargetJvm() - toolchain { - languageVersion.set(JavaLanguageVersion.of(21)) - } -} - -kotlin { - jvmToolchain(21) - compilerOptions { - jvmTarget.set(JvmTarget.JVM_21) - } -} - -sentry { - url = "http://sentry.volmit.com:8080" - autoInstallation.enabled = false - includeSourceContext = true - - org = "sentry" - projectName = "iris" - authToken = findProperty("sentry.auth.token") as String? ?: System.getenv("SENTRY_AUTH_TOKEN") -} - -slimJar { - mirrors = listOf(Mirror( - URI.create("https://maven-central.storage-download.googleapis.com/maven2").toURL(), - URI.create("https://repo.maven.apache.org/maven2/").toURL() - )) - - relocate("com.dfsek.paralithic", "$lib.paralithic") - relocate("io.papermc.lib", "$lib.paper") - relocate("net.kyori", "$lib.kyori") - relocate("org.bstats", "$lib.metrics") - relocate("io.sentry", "$lib.sentry") - relocate("org.apache.maven", "$lib.maven") - relocate("org.codehaus.plexus", "$lib.plexus") - relocate("org.eclipse.sisu", "$lib.sisu") - relocate("org.eclipse.aether", "$lib.aether") - relocate("com.google.inject", "$lib.guice") - relocate("org.dom4j", "$lib.dom4j") - relocate("org.jaxen", "$lib.jaxen") - relocate("com.github.benmanes.caffeine", "$lib.caffeine") -} - -val embeddedAgentJar = project(":core:agent").tasks.named("jar") - -tasks { - /** - * We need parameter meta for the decree command system - */ - compileJava { - options.compilerArgs.add("-parameters") - options.encoding = "UTF-8" - options.debugOptions.debugLevel = "none" - } - - /** - * Expand properties into plugin yml - */ - processResources { - inputs.properties( - "name" to rootProject.name, - "version" to rootProject.version, - "apiVersion" to apiVersion, - "main" to main, - ) - filesMatching("**/plugin.yml") { - expand(inputs.properties) - } - } - - shadowJar { - dependsOn(embeddedAgentJar) - mergeServiceFiles() - //minimize() - relocate("io.github.slimjar", "$lib.slimjar") - exclude("modules/loader-agent.isolated-jar") - from(embeddedAgentJar.map { it.archiveFile }) { - rename { "agent.jar" } - } - } - - sentryCollectSourcesJava { - dependsOn(generateTemplates) - } -} - -val templateSource = file("src/main/templates") -val templateDest = layout.buildDirectory.dir("generated/sources/templates")!! -val generateTemplates = tasks.register("generateTemplates") { - inputs.properties( - "environment" to when { - project.hasProperty("release") -> "production" - project.hasProperty("argghh") -> "Argghh!" - else -> "development" - }, - "commit" to provider { - val res = runCatching { project.extensions.getByType().head().id } - res.getOrDefault("") - .takeIf { it.length == 40 } ?: run { - this.logger.error("Git commit hash not found", res.exceptionOrNull()) - "unknown" - } - }, - ) - - from(templateSource) - into(templateDest) - rename { "art/arcane/iris/$it" } - expand(inputs.properties) -} - -tasks.generateSentryBundleIdJava { - dependsOn(generateTemplates) -} - -rootProject.tasks.named("prepareKotlinBuildScriptModel") { - dependsOn(generateTemplates) -} - -sourceSets.main { - java.srcDir(generateTemplates.map { it.outputs }) -} diff --git a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java index f707afbc9..753c167c9 100644 --- a/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java +++ b/core/src/main/java/art/arcane/iris/core/ExternalDataPackPipeline.java @@ -12,6 +12,7 @@ import art.arcane.iris.engine.object.IrisExternalDatapackReplaceTargets; import art.arcane.iris.engine.object.IrisExternalDatapackStructureAlias; import art.arcane.iris.engine.object.IrisExternalDatapackStructureSetAlias; import art.arcane.iris.engine.object.IrisExternalDatapackStructurePatch; +import art.arcane.iris.engine.object.IrisExternalDatapackTemplateAlias; import art.arcane.iris.engine.object.TileData; import art.arcane.iris.util.common.data.B; import art.arcane.iris.util.common.math.Vector3i; @@ -436,6 +437,9 @@ public final class ExternalDataPackPipeline { + ", installedDatapacks=" + projectionResult.installedDatapacks() + ", installedAssets=" + projectionResult.installedAssets() + ", syntheticStructureSets=" + projectionResult.syntheticStructureSets() + + ", templateAliasesApplied=" + projectionResult.templateAliasesApplied() + + ", emptyElementConversions=" + projectionResult.emptyElementConversions() + + ", unresolvedTemplateRefs=" + projectionResult.unresolvedTemplateRefs() + ", success=true"); } else { Iris.warn("External datapack projection: id=" + request.id() @@ -445,6 +449,9 @@ public final class ExternalDataPackPipeline { + ", installedDatapacks=" + projectionResult.installedDatapacks() + ", installedAssets=" + projectionResult.installedAssets() + ", syntheticStructureSets=" + projectionResult.syntheticStructureSets() + + ", templateAliasesApplied=" + projectionResult.templateAliasesApplied() + + ", emptyElementConversions=" + projectionResult.emptyElementConversions() + + ", unresolvedTemplateRefs=" + projectionResult.unresolvedTemplateRefs() + ", success=false" + ", reason=" + projectionResult.error()); } @@ -1184,7 +1191,7 @@ public final class ExternalDataPackPipeline { if (request.required()) { return ProjectionResult.failure(managedName, "no target world datapack folder is available for required external datapack request"); } - return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0, Set.of()); + return ProjectionResult.success(managedName, 0, 0, Set.copyOf(request.resolvedLocateStructures()), 0, Set.of(), 0, 0, 0); } ProjectionAssetSummary projectionAssetSummary; @@ -1203,7 +1210,10 @@ public final class ExternalDataPackPipeline { 0, projectionAssetSummary.resolvedLocateStructures(), projectionAssetSummary.syntheticStructureSets(), - projectionAssetSummary.projectedStructureKeys() + projectionAssetSummary.projectedStructureKeys(), + projectionAssetSummary.templateAliasesApplied(), + projectionAssetSummary.emptyElementConversions(), + projectionAssetSummary.unresolvedTemplateRefs() ); } @@ -1245,7 +1255,10 @@ public final class ExternalDataPackPipeline { installedAssets, projectionAssetSummary.resolvedLocateStructures(), projectionAssetSummary.syntheticStructureSets(), - projectionAssetSummary.projectedStructureKeys() + projectionAssetSummary.projectedStructureKeys(), + projectionAssetSummary.templateAliasesApplied(), + projectionAssetSummary.emptyElementConversions(), + projectionAssetSummary.unresolvedTemplateRefs() ); } @@ -1257,7 +1270,7 @@ public final class ExternalDataPackPipeline { List inputAssets = projectionSelection.assets(); if (inputAssets.isEmpty()) { - return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0, Set.of()); + return new ProjectionAssetSummary(List.of(), Set.copyOf(request.resolvedLocateStructures()), 0, Set.of(), 0, 0, 0); } int selectedStructureNbtCount = 0; for (ProjectionInputAsset inputAsset : inputAssets) { @@ -1290,6 +1303,9 @@ public final class ExternalDataPackPipeline { LinkedHashSet remappedStructureKeys = new LinkedHashSet<>(); LinkedHashSet projectedStructureKeys = new LinkedHashSet<>(); LinkedHashSet structureSetReferences = new LinkedHashSet<>(); + int templateAliasesApplied = 0; + int emptyElementConversions = 0; + LinkedHashSet unresolvedTemplateRefs = new LinkedHashSet<>(); LinkedHashSet writtenPaths = new LinkedHashSet<>(); ArrayList outputAssets = new ArrayList<>(); int projectedCanonicalStructureNbtCount = 0; @@ -1318,6 +1334,13 @@ public final class ExternalDataPackPipeline { JSONObject root = new JSONObject(new String(outputBytes, StandardCharsets.UTF_8)); rewriteJsonValues(root, remapStringValues); + if (projectedEntry.type() == ProjectedEntryType.TEMPLATE_POOL && !request.templateAliases().isEmpty()) { + TemplateAliasRewriteResult templateAliasRewriteResult = applyTemplateAliasesToTemplatePool(root, request.templateAliases()); + templateAliasesApplied += templateAliasRewriteResult.appliedCount(); + emptyElementConversions += templateAliasRewriteResult.emptyConversions(); + unresolvedTemplateRefs.addAll(templateAliasRewriteResult.unresolvedReferences()); + } + if (projectedEntry.type() == ProjectedEntryType.STRUCTURE) { Integer startHeightAbsolute = getPatchedStartHeightAbsolute(projectedEntry, request); if (startHeightAbsolute == null && remappedKey != null) { @@ -1395,17 +1418,26 @@ public final class ExternalDataPackPipeline { throw new IOException("Required external datapack projection produced no canonical structure template outputs (data/*/structure/*.nbt)."); } + if (request.required() && !unresolvedTemplateRefs.isEmpty()) { + throw new IOException("Required external datapack template alias rewrite unresolved reference(s): " + + summarizeMissingSeededTargets(unresolvedTemplateRefs)); + } + if (request.replaceVanilla() && !request.alongsideMode()) { int directTargetCount = projectionSelection.directResolvedTargets().size(); int aliasTargetCount = projectionSelection.aliasResolvedTargets().size(); int projectedStructureCount = projectedStructureKeys.size(); int projectedTemplateCount = projectedCanonicalStructureNbtCount; + int unresolvedTemplateRefCount = unresolvedTemplateRefs.size(); if (request.required()) { Iris.info("External datapack strict replace validation: id=" + request.id() + ", directTargets=" + directTargetCount + ", aliasTargets=" + aliasTargetCount + ", projectedStructures=" + projectedStructureCount + ", templates=" + projectedTemplateCount + + ", templateAliasesApplied=" + templateAliasesApplied + + ", emptyElementConversions=" + emptyElementConversions + + ", unresolvedTemplateRefs=" + unresolvedTemplateRefCount + ", missingTargets=0"); } else { Iris.verbose("External datapack strict replace validation: id=" + request.id() @@ -1413,11 +1445,22 @@ public final class ExternalDataPackPipeline { + ", aliasTargets=" + aliasTargetCount + ", projectedStructures=" + projectedStructureCount + ", templates=" + projectedTemplateCount + + ", templateAliasesApplied=" + templateAliasesApplied + + ", emptyElementConversions=" + emptyElementConversions + + ", unresolvedTemplateRefs=" + unresolvedTemplateRefCount + ", missingTargets=0"); } } - return new ProjectionAssetSummary(outputAssets, Set.copyOf(resolvedLocateStructures), syntheticStructureSets, Set.copyOf(projectedStructureKeys)); + return new ProjectionAssetSummary( + outputAssets, + Set.copyOf(resolvedLocateStructures), + syntheticStructureSets, + Set.copyOf(projectedStructureKeys), + templateAliasesApplied, + emptyElementConversions, + unresolvedTemplateRefs.size() + ); } private static ProjectionSelection readProjectedEntries(File source, DatapackRequest request) throws IOException { @@ -2492,6 +2535,75 @@ public final class ExternalDataPackPipeline { return value; } + private static TemplateAliasRewriteResult applyTemplateAliasesToTemplatePool(JSONObject root, Map templateAliases) { + if (root == null || templateAliases == null || templateAliases.isEmpty()) { + return TemplateAliasRewriteResult.empty(); + } + + JSONArray elements = root.optJSONArray("elements"); + if (elements == null || elements.length() <= 0) { + return TemplateAliasRewriteResult.empty(); + } + + LinkedHashSet referenced = new LinkedHashSet<>(); + LinkedHashSet rewritten = new LinkedHashSet<>(); + LinkedHashSet unresolved = new LinkedHashSet<>(); + int applied = 0; + int emptyConversions = 0; + + for (int i = 0; i < elements.length(); i++) { + JSONObject poolElement = elements.optJSONObject(i); + if (poolElement == null) { + continue; + } + + JSONObject elementData = poolElement.optJSONObject("element"); + if (elementData == null) { + continue; + } + + String location = elementData.optString("location", ""); + if (location.isBlank()) { + continue; + } + + String normalizedLocation = normalizeResourceKey("minecraft", location, "structure/", "structures/"); + if (normalizedLocation == null || normalizedLocation.isBlank()) { + continue; + } + + String aliasTarget = templateAliases.get(normalizedLocation); + if (aliasTarget == null || aliasTarget.isBlank()) { + continue; + } + + referenced.add(normalizedLocation); + try { + if ("minecraft:empty".equalsIgnoreCase(aliasTarget)) { + JSONObject emptyElement = new JSONObject(); + emptyElement.put("element_type", "minecraft:empty_pool_element"); + poolElement.put("element", emptyElement); + emptyConversions++; + } else { + elementData.put("location", aliasTarget); + poolElement.put("element", elementData); + } + applied++; + rewritten.add(normalizedLocation); + } catch (Throwable ignored) { + unresolved.add(normalizedLocation); + } + } + + for (String reference : referenced) { + if (!rewritten.contains(reference)) { + unresolved.add(reference); + } + } + + return new TemplateAliasRewriteResult(applied, emptyConversions, unresolved); + } + private static Set readStructureSetReferences(JSONObject root) { LinkedHashSet references = new LinkedHashSet<>(); if (root == null) { @@ -4002,6 +4114,7 @@ public final class ExternalDataPackPipeline { Set structureSets, Map> structureAliases, Map structureSetAliases, + Map templateAliases, Set configuredFeatures, Set placedFeatures, Set templatePools, @@ -4024,6 +4137,7 @@ public final class ExternalDataPackPipeline { IrisExternalDatapackReplaceTargets replaceTargets, KList structureAliases, KList structureSetAliases, + KList templateAliases, KList structurePatches ) { this( @@ -4037,6 +4151,7 @@ public final class ExternalDataPackPipeline { replaceTargets, structureAliases, structureSetAliases, + templateAliases, structurePatches, Set.of(), "dimension-root", @@ -4056,6 +4171,7 @@ public final class ExternalDataPackPipeline { IrisExternalDatapackReplaceTargets replaceTargets, KList structureAliases, KList structureSetAliases, + KList templateAliases, KList structurePatches, Set forcedBiomeKeys, String scopeKey, @@ -4074,6 +4190,7 @@ public final class ExternalDataPackPipeline { normalizeTargets(replaceTargets == null ? null : replaceTargets.getStructureSets(), "worldgen/structure_set/"), normalizeStructureAliases(structureAliases), normalizeStructureSetAliases(structureSetAliases), + normalizeTemplateAliases(templateAliases), normalizeTargets(replaceTargets == null ? null : replaceTargets.getConfiguredFeatures(), "worldgen/configured_feature/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getPlacedFeatures(), "worldgen/placed_feature/"), normalizeTargets(replaceTargets == null ? null : replaceTargets.getTemplatePools(), "worldgen/template_pool/"), @@ -4099,6 +4216,7 @@ public final class ExternalDataPackPipeline { structureSets = immutableSet(structureSets); structureAliases = immutableStructureAliasMap(structureAliases); structureSetAliases = immutableAliasMap(structureSetAliases); + templateAliases = immutableAliasMap(templateAliases); configuredFeatures = immutableSet(configuredFeatures); placedFeatures = immutableSet(placedFeatures); templatePools = immutableSet(templatePools); @@ -4145,6 +4263,7 @@ public final class ExternalDataPackPipeline { union(structureSets, other.structureSets), unionStructureAliases(structureAliases, other.structureAliases), unionAliases(structureSetAliases, other.structureSetAliases), + unionAliases(templateAliases, other.templateAliases), union(configuredFeatures, other.configuredFeatures), union(placedFeatures, other.placedFeatures), union(templatePools, other.templatePools), @@ -4274,6 +4393,40 @@ public final class ExternalDataPackPipeline { return normalized; } + private static Map normalizeTemplateAliases(KList aliases) { + LinkedHashMap normalized = new LinkedHashMap<>(); + if (aliases == null) { + return normalized; + } + + for (IrisExternalDatapackTemplateAlias alias : aliases) { + if (alias == null || !alias.isEnabled()) { + continue; + } + + String from = normalizeResourceKey("minecraft", alias.getFrom(), "structure/", "structures/"); + if (from == null || from.isBlank()) { + continue; + } + + String toRaw = alias.getTo() == null ? "" : alias.getTo().trim(); + String to; + if ("minecraft:empty".equalsIgnoreCase(toRaw)) { + to = "minecraft:empty"; + } else { + to = normalizeResourceKey("minecraft", toRaw, "structure/", "structures/"); + } + + if (to == null || to.isBlank()) { + continue; + } + + normalized.put(from, to); + } + + return normalized; + } + private static Set immutableSet(Set values) { LinkedHashSet copy = new LinkedHashSet<>(); if (values != null) { @@ -4666,6 +4819,9 @@ public final class ExternalDataPackPipeline { Set resolvedLocateStructures, int syntheticStructureSets, Set projectedStructureKeys, + int templateAliasesApplied, + int emptyElementConversions, + int unresolvedTemplateRefs, String managedName, String error ) { @@ -4691,6 +4847,9 @@ public final class ExternalDataPackPipeline { } projectedStructureKeys = Set.copyOf(normalizedProjected); syntheticStructureSets = Math.max(0, syntheticStructureSets); + templateAliasesApplied = Math.max(0, templateAliasesApplied); + emptyElementConversions = Math.max(0, emptyElementConversions); + unresolvedTemplateRefs = Math.max(0, unresolvedTemplateRefs); if (error == null) { error = ""; } @@ -4702,14 +4861,29 @@ public final class ExternalDataPackPipeline { int installedAssets, Set resolvedLocateStructures, int syntheticStructureSets, - Set projectedStructureKeys + Set projectedStructureKeys, + int templateAliasesApplied, + int emptyElementConversions, + int unresolvedTemplateRefs ) { - return new ProjectionResult(true, installedDatapacks, installedAssets, resolvedLocateStructures, syntheticStructureSets, projectedStructureKeys, managedName, ""); + return new ProjectionResult( + true, + installedDatapacks, + installedAssets, + resolvedLocateStructures, + syntheticStructureSets, + projectedStructureKeys, + templateAliasesApplied, + emptyElementConversions, + unresolvedTemplateRefs, + managedName, + "" + ); } private static ProjectionResult failure(String managedName, String error) { String message = error == null || error.isBlank() ? "projection failed" : error; - return new ProjectionResult(false, 0, 0, Set.of(), 0, Set.of(), managedName, message); + return new ProjectionResult(false, 0, 0, Set.of(), 0, Set.of(), 0, 0, 0, managedName, message); } } @@ -4725,11 +4899,30 @@ public final class ExternalDataPackPipeline { } } + private record TemplateAliasRewriteResult( + int appliedCount, + int emptyConversions, + Set unresolvedReferences + ) { + private TemplateAliasRewriteResult { + appliedCount = Math.max(0, appliedCount); + emptyConversions = Math.max(0, emptyConversions); + unresolvedReferences = unresolvedReferences == null ? Set.of() : Set.copyOf(unresolvedReferences); + } + + private static TemplateAliasRewriteResult empty() { + return new TemplateAliasRewriteResult(0, 0, Set.of()); + } + } + private record ProjectionAssetSummary( List assets, Set resolvedLocateStructures, int syntheticStructureSets, - Set projectedStructureKeys + Set projectedStructureKeys, + int templateAliasesApplied, + int emptyElementConversions, + int unresolvedTemplateRefs ) { private ProjectionAssetSummary { assets = assets == null ? List.of() : List.copyOf(assets); @@ -4754,6 +4947,9 @@ public final class ExternalDataPackPipeline { } projectedStructureKeys = Set.copyOf(normalizedProjected); syntheticStructureSets = Math.max(0, syntheticStructureSets); + templateAliasesApplied = Math.max(0, templateAliasesApplied); + emptyElementConversions = Math.max(0, emptyElementConversions); + unresolvedTemplateRefs = Math.max(0, unresolvedTemplateRefs); } } diff --git a/core/src/main/java/art/arcane/iris/core/IrisSettings.java b/core/src/main/java/art/arcane/iris/core/IrisSettings.java index bbb9fd6bb..307b9522e 100644 --- a/core/src/main/java/art/arcane/iris/core/IrisSettings.java +++ b/core/src/main/java/art/arcane/iris/core/IrisSettings.java @@ -167,7 +167,6 @@ public class IrisSettings { public int noiseCacheSize = 1_024; public int resourceLoaderCacheSize = 1_024; public int objectLoaderCacheSize = 4_096; - public int scriptLoaderCacheSize = 512; public int tectonicPlateSize = -1; public int mantleCleanupDelay = 200; diff --git a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java index 9b6164058..5912ed131 100644 --- a/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java +++ b/core/src/main/java/art/arcane/iris/core/ServerConfigurator.java @@ -343,6 +343,7 @@ public class ServerConfigurator { definition.getReplaceTargets(), definition.getStructureAliases(), definition.getStructureSetAliases(), + definition.getTemplateAliases(), definition.getStructurePatches(), Set.of(), scopeKey, @@ -374,6 +375,7 @@ public class ServerConfigurator { definition.getReplaceTargets(), definition.getStructureAliases(), definition.getStructureSetAliases(), + definition.getTemplateAliases(), definition.getStructurePatches(), group.forcedBiomeKeys(), group.scopeKey(), diff --git a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java index 9ee64970c..0eb013dfc 100644 --- a/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java +++ b/core/src/main/java/art/arcane/iris/core/commands/CommandStudio.java @@ -277,14 +277,6 @@ public class CommandStudio implements DirectorExecutor { } } - @Director(description = "Execute a script", aliases = "run", origin = DirectorOrigin.PLAYER) - public void execute( - @Param(description = "The script to run") - IrisScript script - ) { - engine().getExecution().execute(script.getLoadKey()); - } - @Director(description = "Open the noise explorer (External GUI)", aliases = {"nmap", "n"}) public void noise() { if (noGUI()) return; diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java index 12afee310..d777d9429 100644 --- a/core/src/main/java/art/arcane/iris/core/loader/IrisData.java +++ b/core/src/main/java/art/arcane/iris/core/loader/IrisData.java @@ -24,7 +24,6 @@ import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; import com.google.gson.stream.JsonWriter; import art.arcane.iris.Iris; -import art.arcane.iris.core.scripting.environment.PackEnvironment; import art.arcane.iris.engine.data.cache.AtomicCache; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.object.*; @@ -32,8 +31,6 @@ import art.arcane.iris.engine.object.annotations.Snippet; import art.arcane.iris.engine.object.matter.IrisMatterObject; import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.collection.KMap; -import art.arcane.iris.util.project.context.IrisContext; -import art.arcane.iris.util.common.format.C; import art.arcane.volmlib.util.mantle.flag.MantleFlagAdapter; import art.arcane.volmlib.util.mantle.flag.MantleFlag; import art.arcane.volmlib.util.math.RNG; @@ -58,7 +55,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private final File dataFolder; private final int id; private boolean closed = false; - private PackEnvironment environment; private ResourceLoader biomeLoader; private ResourceLoader lootLoader; private ResourceLoader regionLoader; @@ -73,7 +69,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { private ResourceLoader objectLoader; private ResourceLoader matterLoader; private ResourceLoader imageLoader; - private ResourceLoader scriptLoader; private ResourceLoader matterObjectLoader; private KMap> possibleSnippets; private Gson gson; @@ -152,10 +147,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { return loadAny(IrisSpawner.class, key, nearest); } - public static IrisScript loadAnyScript(String key, @Nullable IrisData nearest) { - return loadAny(IrisScript.class, key, nearest); - } - public static IrisRegion loadAnyRegion(String key, @Nullable IrisData nearest) { return loadAny(IrisRegion.class, key, nearest); } @@ -237,45 +228,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } public void preprocessObject(IrisRegistrant t) { - try { - IrisContext ctx = IrisContext.get(); - Engine engine = this.engine; - - if (engine == null && ctx != null && ctx.getEngine() != null) { - engine = ctx.getEngine(); - } - - if (engine == null && t.getPreprocessors().isNotEmpty()) { - Iris.error("Failed to preprocess object " + t.getLoadKey() + " because there is no engine context here. (See stack below)"); - try { - throw new RuntimeException(); - } catch (Throwable ex) { - ex.printStackTrace(); - } - } - - if (engine == null) return; - var global = engine.getDimension().getPreProcessors(t.getFolderName()); - var local = t.getPreprocessors(); - if ((global != null && global.isNotEmpty()) || local.isNotEmpty()) { - synchronized (this) { - if (global != null) { - for (String i : global) { - engine.getExecution().preprocessObject(i, t); - Iris.debug("Loader<" + C.GREEN + t.getTypeName() + C.LIGHT_PURPLE + "> iprocess " + C.YELLOW + t.getLoadKey() + C.LIGHT_PURPLE + " in " + i); - } - } - - for (String i : local) { - engine.getExecution().preprocessObject(i, t); - Iris.debug("Loader<" + C.GREEN + t.getTypeName() + C.LIGHT_PURPLE + "> iprocess " + C.YELLOW + t.getLoadKey() + C.LIGHT_PURPLE + " in " + i); - } - } - } - } catch (Throwable e) { - Iris.error("Failed to preprocess object!"); - e.printStackTrace(); - } } public void close() { @@ -298,9 +250,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { } else if (registrant.equals(IrisMatterObject.class)) { r = (ResourceLoader) new MatterObjectResourceLoader(dataFolder, this, rr.getFolderName(), rr.getTypeName()); - } else if (registrant.equals(IrisScript.class)) { - r = (ResourceLoader) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(), - rr.getTypeName()); } else if (registrant.equals(IrisImage.class)) { r = (ResourceLoader) new ImageResourceLoader(dataFolder, this, rr.getFolderName(), rr.getTypeName()); @@ -347,16 +296,10 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory { this.expressionLoader = registerLoader(IrisExpression.class); this.objectLoader = registerLoader(IrisObject.class); 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(); - dimensionLoader.streamAll() - .map(IrisDimension::getDataScripts) - .flatMap(KList::stream) - .forEach(environment::execute); if (engine != null) { engine.hotload(); diff --git a/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java b/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java index 63a689a9d..502e16a50 100644 --- a/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java +++ b/core/src/main/java/art/arcane/iris/core/loader/IrisRegistrant.java @@ -20,11 +20,6 @@ package art.arcane.iris.core.loader; import com.google.gson.GsonBuilder; import art.arcane.iris.Iris; -import art.arcane.iris.engine.object.IrisScript; -import art.arcane.iris.engine.object.annotations.ArrayType; -import art.arcane.iris.engine.object.annotations.Desc; -import art.arcane.iris.engine.object.annotations.RegistryListResource; -import art.arcane.volmlib.util.collection.KList; import art.arcane.volmlib.util.json.JSONObject; import art.arcane.iris.util.common.plugin.VolmitSender; import lombok.Data; @@ -35,11 +30,6 @@ 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: .proc.kts") - @RegistryListResource(IrisScript.class) - @ArrayType(min = 1, type = String.class) - private KList preprocessors = new KList<>(); - @EqualsAndHashCode.Exclude private transient IrisData loader; diff --git a/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java b/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java deleted file mode 100644 index efcfe2219..000000000 --- a/core/src/main/java/art/arcane/iris/core/loader/ScriptResourceLoader.java +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package art.arcane.iris.core.loader; - -import art.arcane.iris.Iris; -import art.arcane.iris.core.IrisSettings; -import art.arcane.iris.engine.object.IrisScript; -import art.arcane.volmlib.util.data.KCache; -import art.arcane.volmlib.util.io.IO; -import art.arcane.volmlib.util.scheduling.PrecisionStopwatch; - -import java.io.File; -import java.util.HashSet; -import java.util.Set; - -public class ScriptResourceLoader extends ResourceLoader { - public ScriptResourceLoader(File root, IrisData idm, String folderName, String resourceTypeName) { - super(root, idm, folderName, resourceTypeName, IrisScript.class); - loadCache = new KCache<>(this::loadRaw, IrisSettings.get().getPerformance().getScriptLoaderCacheSize()); - } - - public boolean supportsSchemas() { - return false; - } - - public long getSize() { - return loadCache.getSize(); - } - - protected IrisScript loadFile(File j, String name) { - try { - PrecisionStopwatch p = PrecisionStopwatch.start(); - IrisScript t = new IrisScript(IO.readAll(j)); - t.setLoadKey(name); - t.setLoader(manager); - t.setLoadFile(j); - logLoad(j, t); - tlt.addAndGet(p.getMilliseconds()); - return t; - } catch (Throwable e) { - Iris.reportError(e); - Iris.warn("Couldn't read " + resourceTypeName + " file: " + j.getPath() + ": " + e.getMessage()); - return null; - } - } - - - public String[] getPossibleKeys() { - if (possibleKeys != null) { - return possibleKeys; - } - - Iris.debug("Building " + resourceTypeName + " Possibility Lists"); - Set keys = new HashSet<>(); - - for (File i : getFolders()) { - if (i.isDirectory()) { - keys.addAll(getKeysInDirectory(i)); - } - } - - possibleKeys = keys.toArray(new String[0]); - return possibleKeys; - } - - private Set getKeysInDirectory(File directory) { - Set keys = new HashSet<>(); - for (File file : directory.listFiles()) { - if (file.isFile() && file.getName().endsWith(".kts")) { - keys.add(file.getName().replaceAll("\\Q.kts\\E", "")); - } else if (file.isDirectory()) { - keys.addAll(getKeysInDirectory(file)); - } - } - return keys; - } - -// public String[] getPossibleKeys() { -// if (possibleKeys != null) { -// return possibleKeys; -// } -// -// Iris.debug("Building " + resourceTypeName + " Possibility Lists"); -// KSet m = new KSet<>(); -// -// for (File i : getFolders()) { -// for (File j : i.listFiles()) { -// if (j.isFile() && j.getName().endsWith(".js")) { -// m.add(j.getName().replaceAll("\\Q.js\\E", "")); -// } else if (j.isDirectory()) { -// for (File k : j.listFiles()) { -// if (k.isFile() && k.getName().endsWith(".js")) { -// m.add(j.getName() + "/" + k.getName().replaceAll("\\Q.js\\E", "")); -// } else if (k.isDirectory()) { -// for (File l : k.listFiles()) { -// if (l.isFile() && l.getName().endsWith(".js")) { -// m.add(j.getName() + "/" + k.getName() + "/" + l.getName().replaceAll("\\Q.js\\E", "")); -// } -// } -// } -// } -// } -// } -// } -// -// KList v = new KList<>(m); -// possibleKeys = v.toArray(new String[0]); -// return possibleKeys; -// } - - public File findFile(String name) { - for (File i : getFolders(name)) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".kts") && j.getName().split("\\Q.\\E")[0].equals(name)) { - return j; - } - } - - File file = new File(i, name + ".kts"); - - if (file.exists()) { - return file; - } - } - - Iris.warn("Couldn't find " + resourceTypeName + ": " + name); - - return null; - } - - private IrisScript loadRaw(String name) { - for (File i : getFolders(name)) { - for (File j : i.listFiles()) { - if (j.isFile() && j.getName().endsWith(".kts") && j.getName().split("\\Q.\\E")[0].equals(name)) { - return loadFile(j, name); - } - } - - File file = new File(i, name + ".kts"); - - if (file.exists()) { - return loadFile(file, name); - } - } - - Iris.warn("Couldn't find " + resourceTypeName + ": " + name); - - return null; - } - - public IrisScript load(String name, boolean warn) { - return loadCache.get(name); - } -} diff --git a/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java new file mode 100644 index 000000000..f96d0e996 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java @@ -0,0 +1,369 @@ +package art.arcane.iris.core.pregenerator.cache; + +import art.arcane.iris.Iris; +import art.arcane.volmlib.util.data.Varint; +import art.arcane.volmlib.util.documentation.ChunkCoordinates; +import art.arcane.volmlib.util.documentation.RegionCoordinates; +import art.arcane.volmlib.util.io.IO; +import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; +import net.jpountz.lz4.LZ4BlockInputStream; +import net.jpountz.lz4.LZ4BlockOutputStream; + +import java.io.DataInput; +import java.io.DataInputStream; +import java.io.DataOutput; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class PregenCacheImpl implements PregenCache { + private static final ExecutorService DISPATCHER = Executors.newFixedThreadPool(4); + private static final short SIZE = 1024; + + private final File directory; + private final int maxSize; + private final Long2ObjectLinkedOpenHashMap cache = new Long2ObjectLinkedOpenHashMap<>(); + + public PregenCacheImpl(File directory, int maxSize) { + this.directory = directory; + this.maxSize = maxSize; + } + + @Override + @ChunkCoordinates + public boolean isChunkCached(int x, int z) { + return getPlate(x >> 10, z >> 10).isCached( + (x >> 5) & 31, + (z >> 5) & 31, + region -> region.isCached(x & 31, z & 31) + ); + } + + @Override + @RegionCoordinates + public boolean isRegionCached(int x, int z) { + return getPlate(x >> 5, z >> 5).isCached( + x & 31, + z & 31, + Region::isCached + ); + } + + @Override + @ChunkCoordinates + public void cacheChunk(int x, int z) { + getPlate(x >> 10, z >> 10).cache( + (x >> 5) & 31, + (z >> 5) & 31, + region -> region.cache(x & 31, z & 31) + ); + } + + @Override + @RegionCoordinates + public void cacheRegion(int x, int z) { + getPlate(x >> 5, z >> 5).cache( + x & 31, + z & 31, + Region::cache + ); + } + + @Override + public void write() { + if (cache.isEmpty()) { + return; + } + + List> futures = new ArrayList<>(cache.size()); + for (Plate plate : cache.values()) { + if (!plate.dirty) { + continue; + } + futures.add(CompletableFuture.runAsync(() -> writePlate(plate), DISPATCHER)); + } + + for (CompletableFuture future : futures) { + future.join(); + } + } + + @Override + public void trim(long unloadDuration) { + if (cache.isEmpty()) { + return; + } + + long threshold = System.currentTimeMillis() - unloadDuration; + List> futures = new ArrayList<>(cache.size()); + Iterator iterator = cache.values().iterator(); + while (iterator.hasNext()) { + Plate plate = iterator.next(); + if (plate.lastAccess < threshold) { + iterator.remove(); + } + futures.add(CompletableFuture.runAsync(() -> writePlate(plate), DISPATCHER)); + } + + for (CompletableFuture future : futures) { + future.join(); + } + } + + private Plate getPlate(int x, int z) { + long key = key(x, z); + Plate plate = cache.getAndMoveToFirst(key); + if (plate != null) { + return plate; + } + + Plate loaded = readPlate(x, z); + cache.putAndMoveToFirst(key, loaded); + + if (cache.size() > maxSize) { + List> futures = new ArrayList<>(cache.size() - maxSize); + while (cache.size() > maxSize) { + Plate evicted = cache.removeLast(); + futures.add(CompletableFuture.runAsync(() -> writePlate(evicted), DISPATCHER)); + } + for (CompletableFuture future : futures) { + future.join(); + } + } + + return loaded; + } + + private Plate readPlate(int x, int z) { + File file = fileForPlate(x, z); + if (!file.exists()) { + return new Plate(x, z); + } + + try (DataInputStream input = new DataInputStream(new LZ4BlockInputStream(new FileInputStream(file)))) { + return readPlate(x, z, input); + } catch (IOException e) { + Iris.error("Failed to read pregen cache " + file); + e.printStackTrace(); + Iris.reportError(e); + } + + return new Plate(x, z); + } + + private void writePlate(Plate plate) { + if (!plate.dirty) { + return; + } + + File file = fileForPlate(plate.x, plate.z); + try { + IO.write(file, output -> new DataOutputStream(new LZ4BlockOutputStream(output)), plate::write); + plate.dirty = false; + } catch (IOException e) { + Iris.error("Failed to write preen cache " + file); + e.printStackTrace(); + Iris.reportError(e); + } + } + + private File fileForPlate(int x, int z) { + if (!directory.exists() && !directory.mkdirs()) { + throw new IllegalStateException("Cannot create directory: " + directory.getAbsolutePath()); + } + return new File(directory, "c." + x + "." + z + ".lz4b"); + } + + private static long key(int x, int z) { + return (((long) x) << 32) ^ (z & 0xffffffffL); + } + + private interface RegionPredicate { + boolean test(Region region); + } + + private static class Plate { + private final int x; + private final int z; + private short count; + private Region[] regions; + private boolean dirty; + private long lastAccess; + + private Plate(int x, int z) { + this(x, z, (short) 0, new Region[1024]); + } + + private Plate(int x, int z, short count, Region[] regions) { + this.x = x; + this.z = z; + this.count = count; + this.regions = regions; + this.lastAccess = System.currentTimeMillis(); + } + + private boolean cache(int x, int z, RegionPredicate predicate) { + lastAccess = System.currentTimeMillis(); + if (count == SIZE) { + return false; + } + + int index = x * 32 + z; + Region region = regions[index]; + if (region == null) { + region = new Region(); + regions[index] = region; + } + + if (!predicate.test(region)) { + return false; + } + + count++; + if (count == SIZE) { + regions = null; + } + dirty = true; + return true; + } + + private boolean isCached(int x, int z, RegionPredicate predicate) { + lastAccess = System.currentTimeMillis(); + if (count == SIZE) { + return true; + } + + Region region = regions[x * 32 + z]; + if (region == null) { + return false; + } + return predicate.test(region); + } + + private void write(DataOutput output) throws IOException { + Varint.writeSignedVarInt(count, output); + if (regions == null) { + return; + } + + for (Region region : regions) { + output.writeBoolean(region == null); + if (region != null) { + region.write(output); + } + } + } + } + + private static class Region { + private short count; + private long[] words; + + private Region() { + this((short) 0, new long[64]); + } + + private Region(short count, long[] words) { + this.count = count; + this.words = words; + } + + private boolean cache() { + if (count == SIZE) { + return false; + } + count = SIZE; + words = null; + return true; + } + + private boolean cache(int x, int z) { + if (count == SIZE) { + return false; + } + + long[] value = words; + if (value == null) { + return false; + } + + int index = x * 32 + z; + int wordIndex = index >> 6; + long bit = 1L << (index & 63); + boolean current = (value[wordIndex] & bit) != 0L; + if (current) { + return false; + } + + count++; + if (count == SIZE) { + words = null; + return true; + } + + value[wordIndex] = value[wordIndex] | bit; + return false; + } + + private boolean isCached() { + return count == SIZE; + } + + private boolean isCached(int x, int z) { + int index = x * 32 + z; + if (count == SIZE) { + return true; + } + return (words[index >> 6] & (1L << (index & 63))) != 0L; + } + + private void write(DataOutput output) throws IOException { + Varint.writeSignedVarInt(count, output); + if (words == null) { + return; + } + + for (long word : words) { + Varint.writeUnsignedVarLong(word, output); + } + } + } + + private static Plate readPlate(int x, int z, DataInput input) throws IOException { + int count = Varint.readSignedVarInt(input); + if (count == 1024) { + return new Plate(x, z, SIZE, null); + } + + Region[] regions = new Region[1024]; + for (int i = 0; i < regions.length; i++) { + boolean isNull = input.readBoolean(); + if (!isNull) { + regions[i] = readRegion(input); + } + } + + return new Plate(x, z, (short) count, regions); + } + + private static Region readRegion(DataInput input) throws IOException { + int count = Varint.readSignedVarInt(input); + if (count == 1024) { + return new Region(SIZE, null); + } + + long[] words = new long[64]; + for (int i = 0; i < words.length; i++) { + words[i] = Varint.readUnsignedVarLong(input); + } + + return new Region((short) count, words); + } +} diff --git a/core/src/main/java/art/arcane/iris/core/project/Gradle.java b/core/src/main/java/art/arcane/iris/core/project/Gradle.java index 1d75418af..3940d9375 100644 --- a/core/src/main/java/art/arcane/iris/core/project/Gradle.java +++ b/core/src/main/java/art/arcane/iris/core/project/Gradle.java @@ -20,7 +20,7 @@ public class Gradle { public static synchronized void wrapper(File projectDir) { try { - File settings = new File(projectDir, "settings.gradle.kts"); + File settings = new File(projectDir, "settings.gradle"); if (!settings.exists()) settings.createNewFile(); runGradle(projectDir, "wrapper"); } catch (Throwable e) { diff --git a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java index 1437a82a3..82739ab1a 100644 --- a/core/src/main/java/art/arcane/iris/core/project/IrisProject.java +++ b/core/src/main/java/art/arcane/iris/core/project/IrisProject.java @@ -428,7 +428,6 @@ public class IrisProject { settings.put("json.schemas", schemas); ws.put("settings", settings); - dm.getEnvironment().configureProject(); File schemasFile = new File(path, ".idea" + File.separator + "jsonSchemas.xml"); Document doc = IO.read(schemasFile); Element mappings = (Element) doc.selectSingleNode("//component[@name='JsonSchemaMappingsProjectConfiguration']"); diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java b/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java new file mode 100644 index 000000000..77d7a96c4 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/IrisSafeguard.java @@ -0,0 +1,129 @@ +package art.arcane.iris.core.safeguard; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.safeguard.task.Diagnostic; +import art.arcane.iris.core.safeguard.task.Task; +import art.arcane.iris.core.safeguard.task.Tasks; +import art.arcane.iris.core.safeguard.task.ValueWithDiagnostics; +import art.arcane.iris.util.common.format.C; +import art.arcane.iris.util.common.scheduling.J; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public final class IrisSafeguard { + private static volatile boolean forceShutdown = false; + private static Map> results = Collections.emptyMap(); + private static Map context = Collections.emptyMap(); + private static Map> attachment = Collections.emptyMap(); + private static Mode mode = Mode.STABLE; + private static int count = 0; + + private IrisSafeguard() { + } + + public static void execute() { + List tasks = Tasks.getTasks(); + LinkedHashMap> resultValues = new LinkedHashMap<>(tasks.size()); + LinkedHashMap contextValues = new LinkedHashMap<>(tasks.size()); + LinkedHashMap> attachmentValues = new LinkedHashMap<>(tasks.size()); + Mode currentMode = Mode.STABLE; + int issueCount = 0; + + for (Task task : tasks) { + ValueWithDiagnostics result; + try { + result = task.run(); + } catch (Throwable e) { + Iris.reportError(e); + result = new ValueWithDiagnostics<>( + Mode.WARNING, + new Diagnostic(Diagnostic.Logger.ERROR, "Error while running task " + task.getId(), e) + ); + } + + currentMode = currentMode.highest(result.getValue()); + resultValues.put(task, result); + contextValues.put(task.getId(), result.getValue().getId()); + + List lines = new ArrayList<>(); + for (Diagnostic diagnostic : result.getDiagnostics()) { + String[] split = diagnostic.toString().split("\\n"); + Collections.addAll(lines, split); + } + attachmentValues.put(task.getId(), lines); + + if (result.getValue() != Mode.STABLE) { + issueCount++; + } + } + + results = Collections.unmodifiableMap(resultValues); + context = Collections.unmodifiableMap(contextValues); + attachment = Collections.unmodifiableMap(attachmentValues); + mode = currentMode; + count = issueCount; + } + + public static Mode mode() { + return mode; + } + + public static Map asContext() { + return context; + } + + public static Map> asAttachment() { + return attachment; + } + + public static void splash() { + Iris.instance.splash(); + printReports(); + printFooter(); + } + + public static void printReports() { + switch (mode) { + case STABLE -> Iris.info(C.BLUE + "0 Conflicts found"); + case WARNING -> Iris.warn(C.GOLD + "%s Issues found", count); + case UNSTABLE -> Iris.error(C.DARK_RED + "%s Issues found", count); + } + + for (ValueWithDiagnostics value : results.values()) { + value.log(true, true); + } + } + + public static void printFooter() { + switch (mode) { + case STABLE -> Iris.info(C.BLUE + "Iris is running Stable"); + case WARNING -> warning(); + case UNSTABLE -> unstable(); + } + } + + public static boolean isForceShutdown() { + return forceShutdown; + } + + private static void warning() { + Iris.warn(C.GOLD + "Iris is running in Warning Mode"); + Iris.warn(C.GRAY + "Some startup checks need attention. Review the messages above for tuning suggestions."); + Iris.warn(C.GRAY + "Iris will continue startup normally."); + Iris.warn(""); + } + + private static void unstable() { + Iris.error(C.DARK_RED + "Iris is running in Danger Mode"); + Iris.error(""); + Iris.error(C.DARK_GRAY + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--"); + Iris.error("Critical startup checks failed. Iris will continue startup in 10 seconds."); + Iris.error("Review and resolve the errors above as soon as possible."); + J.sleep(10000L); + Iris.info(""); + } +} diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java b/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java new file mode 100644 index 000000000..a7fad6cfe --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/Mode.java @@ -0,0 +1,143 @@ +package art.arcane.iris.core.safeguard; + +import art.arcane.iris.BuildConstants; +import art.arcane.iris.Iris; +import art.arcane.iris.core.IrisSettings; +import art.arcane.iris.util.common.format.C; +import art.arcane.volmlib.util.format.Form; +import org.bukkit.Bukkit; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.util.Locale; + +public enum Mode { + STABLE(C.IRIS), + WARNING(C.GOLD), + UNSTABLE(C.RED); + + private final C color; + private final String id; + + Mode(C color) { + this.color = color; + this.id = name().toLowerCase(Locale.ROOT); + } + + public String getId() { + return id; + } + + public Mode highest(Mode mode) { + if (mode.ordinal() > ordinal()) { + return mode; + } + return this; + } + + public String tag(String subTag) { + if (subTag == null || subTag.isBlank()) { + return wrap("Iris") + C.GRAY + ": "; + } + return wrap("Iris") + " " + wrap(subTag) + C.GRAY + ": "; + } + + public void trySplash() { + if (!IrisSettings.get().getGeneral().isSplashLogoStartup()) { + return; + } + splash(); + } + + public void splash() { + String padd = Form.repeat(" ", 8); + String padd2 = Form.repeat(" ", 4); + String version = Iris.instance.getDescription().getVersion(); + String releaseTrain = getReleaseTrain(version); + String serverVersion = getServerVersion(); + String startupDate = getStartupDate(); + int javaVersion = getJavaVersion(); + + String[] splash = new String[]{ + padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", + padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + color + " .(((()))). ", + padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + color + " .((((((())))))). ", + padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + color + " ((((((((())))))))) " + C.GRAY + " @", + padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + color + " ((((((((-))))))))) " + C.GRAY + " @@", + padd + C.GRAY + "@@@&&" + color + " ((((((({ })))))))) " + C.GRAY + " &&@@@", + padd + C.GRAY + "@@" + color + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", + padd + C.GRAY + "@" + color + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", + padd + C.GRAY + "" + color + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", + padd + C.GRAY + "" + color + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", + padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@" + }; + + String[] info = new String[]{ + "", + padd2 + color + " Iris, " + C.AQUA + "Dimension Engine " + C.RED + "[" + releaseTrain + " RELEASE]", + padd2 + C.GRAY + " Version: " + color + version, + padd2 + C.GRAY + " By: " + color + "Volmit Software (Arcane Arts)", + padd2 + C.GRAY + " Server: " + color + serverVersion, + padd2 + C.GRAY + " Java: " + color + javaVersion + C.GRAY + " | Date: " + color + startupDate, + padd2 + C.GRAY + " Commit: " + color + BuildConstants.COMMIT + C.GRAY + "/" + color + BuildConstants.ENVIRONMENT, + "", + "", + "", + "" + }; + + StringBuilder builder = new StringBuilder("\n\n"); + for (int i = 0; i < splash.length; i++) { + builder.append(splash[i]); + if (i < info.length) { + builder.append(info[i]); + } + builder.append("\n"); + } + + Iris.info(builder.toString()); + } + + private String wrap(String tag) { + return C.BOLD.toString() + C.DARK_GRAY + "[" + C.BOLD + color + tag + C.BOLD + C.DARK_GRAY + "]" + C.RESET; + } + + private String getServerVersion() { + String version = Bukkit.getVersion(); + int marker = version.indexOf(" (MC:"); + if (marker != -1) { + return version.substring(0, marker); + } + return version; + } + + private int getJavaVersion() { + String version = System.getProperty("java.version"); + if (version.startsWith("1.")) { + version = version.substring(2, 3); + } else { + int dot = version.indexOf('.'); + if (dot != -1) { + version = version.substring(0, dot); + } + } + return Integer.parseInt(version); + } + + private String getStartupDate() { + return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE); + } + + private String getReleaseTrain(String version) { + String value = version; + int suffixIndex = value.indexOf('-'); + if (suffixIndex >= 0) { + value = value.substring(0, suffixIndex); + } + String[] split = value.split("\\."); + if (split.length >= 2) { + return split[0] + "." + split[1]; + } + return value; + } +} diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java new file mode 100644 index 000000000..132a6310f --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Diagnostic.java @@ -0,0 +1,112 @@ +package art.arcane.iris.core.safeguard.task; + +import art.arcane.iris.Iris; +import art.arcane.iris.util.common.format.C; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.util.function.Consumer; + +public class Diagnostic { + private final Logger logger; + private final String message; + private final Throwable exception; + + public Diagnostic(String message) { + this(Logger.ERROR, message, null); + } + + public Diagnostic(Logger logger, String message) { + this(logger, message, null); + } + + public Diagnostic(Logger logger, String message, Throwable exception) { + this.logger = logger; + this.message = message; + this.exception = exception; + } + + public Logger getLogger() { + return logger; + } + + public String getMessage() { + return message; + } + + public Throwable getException() { + return exception; + } + + public void log() { + log(true, false); + } + + public void log(boolean withException) { + log(withException, false); + } + + public void log(boolean withException, boolean withStackTrace) { + logger.print(render(withException, withStackTrace)); + } + + public String render() { + return render(true, false); + } + + public String render(boolean withException) { + return render(withException, false); + } + + public String render(boolean withException, boolean withStackTrace) { + StringBuilder builder = new StringBuilder(); + builder.append(message); + if (withException && exception != null) { + builder.append(": "); + builder.append(exception); + if (withStackTrace) { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(os); + exception.printStackTrace(ps); + ps.flush(); + builder.append("\n"); + builder.append(os); + } + } + return builder.toString(); + } + + @Override + public String toString() { + return C.strip(render()); + } + + public enum Logger { + DEBUG(Iris::debug), + RAW(Iris::msg), + INFO(Iris::info), + WARN(Iris::warn), + ERROR(Iris::error); + + private final Consumer logger; + + Logger(Consumer logger) { + this.logger = logger; + } + + public void print(String message) { + String[] lines = message.split("\\n"); + for (String line : lines) { + logger.accept(line); + } + } + + public Diagnostic create(String message) { + return create(message, null); + } + + public Diagnostic create(String message, Throwable exception) { + return new Diagnostic(this, message, exception); + } + } +} diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java new file mode 100644 index 000000000..adfc428d3 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Task.java @@ -0,0 +1,49 @@ +package art.arcane.iris.core.safeguard.task; + +import art.arcane.iris.core.safeguard.Mode; +import art.arcane.volmlib.util.format.Form; + +import java.util.Locale; +import java.util.function.Supplier; + +public abstract class Task { + private final String id; + private final String name; + + public Task(String id) { + this(id, Form.capitalizeWords(id.replace(" ", "_").toLowerCase(Locale.ROOT))); + } + + public Task(String id, String name) { + this.id = id; + this.name = name; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public abstract ValueWithDiagnostics run(); + + public static Task of(String id, String name, Supplier> action) { + return new Task(id, name) { + @Override + public ValueWithDiagnostics run() { + return action.get(); + } + }; + } + + public static Task of(String id, Supplier> action) { + return new Task(id) { + @Override + public ValueWithDiagnostics run() { + return action.get(); + } + }; + } +} diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java new file mode 100644 index 000000000..5061ee3c4 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/Tasks.java @@ -0,0 +1,228 @@ +package art.arcane.iris.core.safeguard.task; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.IrisWorlds; +import art.arcane.iris.core.nms.INMS; +import art.arcane.iris.core.nms.v1X.NMSBinding1X; +import art.arcane.iris.core.safeguard.Mode; +import art.arcane.iris.engine.object.IrisDimension; +import art.arcane.iris.util.common.misc.getHardware; +import art.arcane.iris.util.project.agent.Agent; +import org.bukkit.Bukkit; +import org.bukkit.Server; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +public final class Tasks { + private static final Task MEMORY = Task.of("memory", () -> { + long mem = getHardware.getProcessMemory(); + if (mem >= 3072L) { + return withDiagnostics(Mode.STABLE); + } + + if (mem > 2048L) { + return withDiagnostics(Mode.STABLE, + Diagnostic.Logger.INFO.create("Memory Recommendation"), + Diagnostic.Logger.INFO.create("- 3GB+ process memory is recommended for Iris."), + Diagnostic.Logger.INFO.create("- Process Memory: " + mem + " MB")); + } + + return withDiagnostics(Mode.WARNING, + Diagnostic.Logger.WARN.create("Low Memory"), + Diagnostic.Logger.WARN.create("- Iris is running with 2GB or less process memory."), + Diagnostic.Logger.WARN.create("- 3GB+ process memory is recommended for Iris."), + Diagnostic.Logger.WARN.create("- Process Memory: " + mem + " MB")); + }); + + private static final Task INCOMPATIBILITIES = Task.of("incompatibilities", () -> { + Set plugins = new HashSet<>(Set.of("dynmap", "Stratos")); + plugins.removeIf(name -> server().getPluginManager().getPlugin(name) == null); + + if (plugins.isEmpty()) { + return withDiagnostics(Mode.STABLE); + } + + List diagnostics = new ArrayList<>(); + if (plugins.contains("dynmap")) { + addAllDiagnostics(diagnostics, + Diagnostic.Logger.ERROR.create("Dynmap"), + Diagnostic.Logger.ERROR.create("- The plugin Dynmap is not compatible with the server."), + Diagnostic.Logger.ERROR.create("- If you want to have a map plugin like Dynmap, consider Bluemap.")); + } + if (plugins.contains("Stratos")) { + addAllDiagnostics(diagnostics, + Diagnostic.Logger.ERROR.create("Stratos"), + Diagnostic.Logger.ERROR.create("- Iris is not compatible with other worldgen plugins.")); + } + return withDiagnostics(Mode.WARNING, diagnostics); + }); + + private static final Task SOFTWARE = Task.of("software", () -> { + Set supported = Set.of("canvas", "folia", "purpur", "pufferfish", "paper", "spigot", "bukkit"); + String serverName = server().getName().toLowerCase(Locale.ROOT); + boolean supportedServer = isCanvasServer(); + if (!supportedServer) { + for (String candidate : supported) { + if (serverName.contains(candidate)) { + supportedServer = true; + break; + } + } + } + + if (supportedServer) { + return withDiagnostics(Mode.STABLE); + } + + return withDiagnostics(Mode.WARNING, + Diagnostic.Logger.WARN.create("Unsupported Server Software"), + Diagnostic.Logger.WARN.create("- Please consider using Canvas, Folia, Paper, or Purpur instead.")); + }); + + private static final Task VERSION = Task.of("version", () -> { + String[] parts = Iris.instance.getDescription().getVersion().split("-"); + String supportedVersions; + if (parts.length >= 3) { + String minVersion = parts[1]; + String maxVersion = parts[2]; + supportedVersions = minVersion.equals(maxVersion) ? minVersion : minVersion + " - " + maxVersion; + } else if (parts.length >= 2) { + supportedVersions = parts[1]; + } else { + supportedVersions = "1.21.11"; + } + + if (!(INMS.get() instanceof NMSBinding1X)) { + return withDiagnostics(Mode.STABLE); + } + + return withDiagnostics(Mode.UNSTABLE, + Diagnostic.Logger.ERROR.create("Server Version"), + Diagnostic.Logger.ERROR.create("- Iris only supports " + supportedVersions)); + }); + + private static final Task INJECTION = Task.of("injection", () -> { + if (!isPaperPreferredServer() && !Agent.isInstalled()) { + return withDiagnostics(Mode.WARNING, + Diagnostic.Logger.WARN.create("Java Agent"), + Diagnostic.Logger.WARN.create("- Skipping dynamic Java agent attach on Spigot/Bukkit to avoid runtime agent warnings."), + Diagnostic.Logger.WARN.create("- For full runtime injection support, run with -javaagent:" + + Agent.AGENT_JAR.getPath() + " or use Canvas/Folia/Paper/Purpur.")); + } + + if (!Agent.install()) { + return withDiagnostics(Mode.UNSTABLE, + Diagnostic.Logger.ERROR.create("Java Agent"), + Diagnostic.Logger.ERROR.create("- Please enable dynamic agent loading by adding -XX:+EnableDynamicAgentLoading to your jvm arguments."), + Diagnostic.Logger.ERROR.create("- or add the jvm argument -javaagent:" + Agent.AGENT_JAR.getPath())); + } + + if (!INMS.get().injectBukkit()) { + return withDiagnostics(Mode.UNSTABLE, + Diagnostic.Logger.ERROR.create("Code Injection"), + Diagnostic.Logger.ERROR.create("- Failed to inject code. Please contact support")); + } + + return withDiagnostics(Mode.STABLE); + }); + + private static final Task DIMENSION_TYPES = Task.of("dimensionTypes", () -> { + Set keys = IrisWorlds.get().getDimensions().map(IrisDimension::getDimensionTypeKey).collect(Collectors.toSet()); + if (!INMS.get().missingDimensionTypes(keys.toArray(String[]::new))) { + return withDiagnostics(Mode.STABLE); + } + + return withDiagnostics(Mode.UNSTABLE, + Diagnostic.Logger.ERROR.create("Dimension Types"), + Diagnostic.Logger.ERROR.create("- Required Iris dimension types were not loaded."), + Diagnostic.Logger.ERROR.create("- If this still happens after a restart please contact support.")); + }); + + private static final Task DISK_SPACE = Task.of("diskSpace", () -> { + double freeGiB = server().getWorldContainer().getFreeSpace() / (double) 0x4000_0000; + if (freeGiB > 3.0) { + return withDiagnostics(Mode.STABLE); + } + + return withDiagnostics(Mode.WARNING, + Diagnostic.Logger.WARN.create("Insufficient Disk Space"), + Diagnostic.Logger.WARN.create("- 3GB of free space is required for Iris to function.")); + }); + + private static final Task JAVA = Task.of("java", () -> { + int version = Iris.getJavaVersion(); + if (version == 21) { + return withDiagnostics(Mode.STABLE); + } + + if (version > 21) { + return withDiagnostics(Mode.STABLE, + Diagnostic.Logger.INFO.create("Java Runtime"), + Diagnostic.Logger.INFO.create("- Running Java " + version + ". Iris is tested primarily on Java 21.")); + } + + return withDiagnostics(Mode.WARNING, + Diagnostic.Logger.WARN.create("Unsupported Java version"), + Diagnostic.Logger.WARN.create("- Java 21+ is recommended. Current runtime: Java " + version)); + }); + + private static final List TASKS = List.of( + MEMORY, + INCOMPATIBILITIES, + SOFTWARE, + VERSION, + INJECTION, + DIMENSION_TYPES, + DISK_SPACE, + JAVA + ); + + private Tasks() { + } + + public static List getTasks() { + return TASKS; + } + + private static Server server() { + return Bukkit.getServer(); + } + + private static boolean isPaperPreferredServer() { + String name = server().getName().toLowerCase(Locale.ROOT); + return isCanvasServer() + || name.contains("folia") + || name.contains("paper") + || name.contains("purpur") + || name.contains("pufferfish"); + } + + private static boolean isCanvasServer() { + ClassLoader loader = server().getClass().getClassLoader(); + try { + Class.forName("io.canvasmc.canvas.region.WorldRegionizer", false, loader); + return true; + } catch (Throwable ignored) { + return server().getName().toLowerCase(Locale.ROOT).contains("canvas"); + } + } + + private static void addAllDiagnostics(List diagnostics, Diagnostic... values) { + for (Diagnostic value : values) { + diagnostics.add(value); + } + } + + private static ValueWithDiagnostics withDiagnostics(Mode mode, Diagnostic... diagnostics) { + return new ValueWithDiagnostics<>(mode, diagnostics); + } + + private static ValueWithDiagnostics withDiagnostics(Mode mode, List diagnostics) { + return new ValueWithDiagnostics<>(mode, diagnostics); + } +} diff --git a/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java b/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java new file mode 100644 index 000000000..6650cc4a0 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.java @@ -0,0 +1,40 @@ +package art.arcane.iris.core.safeguard.task; + +import java.util.List; + +public class ValueWithDiagnostics { + private final T value; + private final List diagnostics; + + public ValueWithDiagnostics(T value, List diagnostics) { + this.value = value; + this.diagnostics = List.copyOf(diagnostics); + } + + public ValueWithDiagnostics(T value, Diagnostic... diagnostics) { + this.value = value; + this.diagnostics = List.of(diagnostics); + } + + public T getValue() { + return value; + } + + public List getDiagnostics() { + return diagnostics; + } + + public void log() { + log(true, false); + } + + public void log(boolean withException) { + log(withException, false); + } + + public void log(boolean withException, boolean withStackTrace) { + for (Diagnostic diagnostic : diagnostics) { + diagnostic.log(withException, withStackTrace); + } + } +} diff --git a/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java deleted file mode 100644 index 5682a89c6..000000000 --- a/core/src/main/java/art/arcane/iris/core/scripting/environment/EngineEnvironment.java +++ /dev/null @@ -1,30 +0,0 @@ -package art.arcane.iris.core.scripting.environment; - -import art.arcane.iris.core.loader.IrisRegistrant; -import art.arcane.iris.core.scripting.func.UpdateExecutor; -import art.arcane.iris.core.scripting.kotlin.environment.IrisExecutionEnvironment; -import art.arcane.iris.engine.framework.Engine; -import art.arcane.volmlib.util.mantle.runtime.MantleChunk; -import lombok.NonNull; -import org.bukkit.Chunk; -import org.bukkit.Location; -import org.bukkit.entity.Entity; -import org.jetbrains.annotations.Nullable; - -public interface EngineEnvironment extends PackEnvironment { - static EngineEnvironment create(@NonNull Engine engine) { - return new IrisExecutionEnvironment(engine); - } - - @NonNull - Engine getEngine(); - - @Nullable - Object spawnMob(@NonNull String script, @NonNull Location location); - - void postSpawnMob(@NonNull String script, @NonNull Location location, @NonNull Entity mob); - - void preprocessObject(@NonNull String script, @NonNull IrisRegistrant object); - - void updateChunk(@NonNull String script, @NonNull MantleChunk mantleChunk, @NonNull Chunk chunk, @NonNull UpdateExecutor executor); -} diff --git a/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java deleted file mode 100644 index ff159a715..000000000 --- a/core/src/main/java/art/arcane/iris/core/scripting/environment/PackEnvironment.java +++ /dev/null @@ -1,22 +0,0 @@ -package art.arcane.iris.core.scripting.environment; - -import art.arcane.iris.core.loader.IrisData; -import art.arcane.iris.core.scripting.kotlin.environment.IrisPackExecutionEnvironment; -import art.arcane.iris.engine.framework.Engine; -import art.arcane.volmlib.util.math.RNG; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -public interface PackEnvironment extends SimpleEnvironment { - static PackEnvironment create(@NonNull IrisData data) { - return new IrisPackExecutionEnvironment(data); - } - - @NonNull - IrisData getData(); - - @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/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java b/core/src/main/java/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java deleted file mode 100644 index 387b0ed2a..000000000 --- a/core/src/main/java/art/arcane/iris/core/scripting/environment/SimpleEnvironment.java +++ /dev/null @@ -1,30 +0,0 @@ -package art.arcane.iris.core.scripting.environment; - -import art.arcane.iris.core.scripting.kotlin.environment.IrisSimpleExecutionEnvironment; -import lombok.NonNull; -import org.jetbrains.annotations.Nullable; - -import java.io.File; -import java.util.Map; - -public interface SimpleEnvironment { - static SimpleEnvironment create() { - return new IrisSimpleExecutionEnvironment(); - } - - static SimpleEnvironment create(@NonNull File projectDir) { - return new IrisSimpleExecutionEnvironment(projectDir); - } - - void configureProject(); - - void execute(@NonNull String script); - - void execute(@NonNull String script, @NonNull Class type, @Nullable Map<@NonNull String, Object> vars); - - @Nullable - Object evaluate(@NonNull String script); - - @Nullable - Object evaluate(@NonNull String script, @NonNull Class type, @Nullable Map<@NonNull String, Object> vars); -} \ No newline at end of file diff --git a/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java b/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java deleted file mode 100644 index f7d306c24..000000000 --- a/core/src/main/java/art/arcane/iris/core/scripting/func/BiomeLookup.java +++ /dev/null @@ -1,10 +0,0 @@ -package art.arcane.iris.core.scripting.func; - -import art.arcane.iris.engine.object.IrisBiome; -import art.arcane.volmlib.util.documentation.BlockCoordinates; - -@FunctionalInterface -public interface BiomeLookup { - @BlockCoordinates - IrisBiome at(int x, int z); -} diff --git a/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java b/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java deleted file mode 100644 index 3796ceb5d..000000000 --- a/core/src/main/java/art/arcane/iris/core/scripting/func/UpdateExecutor.java +++ /dev/null @@ -1,22 +0,0 @@ -package art.arcane.iris.core.scripting.func; - -import org.jetbrains.annotations.NotNull; - -@FunctionalInterface -public interface UpdateExecutor { - - @NotNull Runnable wrap(int delay, @NotNull Runnable runnable); - - @NotNull - default Runnable wrap(@NotNull Runnable runnable) { - return wrap(1, runnable); - } - - default void execute(@NotNull Runnable runnable) { - execute(1, runnable); - } - - default void execute(int delay, @NotNull Runnable runnable) { - wrap(delay, runnable).run(); - } -} diff --git a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java index ac485857e..6629df689 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisEngine.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisEngine.java @@ -29,7 +29,6 @@ import art.arcane.iris.core.loader.ResourceLoader; import art.arcane.iris.core.nms.container.BlockPos; import art.arcane.iris.core.nms.container.Pair; import art.arcane.iris.core.project.IrisProject; -import art.arcane.iris.core.scripting.environment.EngineEnvironment; import art.arcane.iris.core.service.PreservationSVC; import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.engine.data.cache.AtomicCache; @@ -97,7 +96,6 @@ public class IrisEngine implements Engine { private CompletableFuture hash32; private EngineMode mode; private EngineEffects effects; - private EngineEnvironment execution; private EngineWorldManager worldManager; private volatile int parallelism; private boolean failing; @@ -131,7 +129,6 @@ public class IrisEngine implements Engine { cleaning = new AtomicBoolean(false); noisemapPrebakeRunning = new AtomicBoolean(false); modeFallbackLogged = new AtomicBoolean(false); - execution = getData().getEnvironment().with(this); if (studio) { getData().dump(); getData().clearLists(); @@ -190,7 +187,6 @@ public class IrisEngine implements Engine { if (currentMode != null) { currentMode.close(); } - execution = getData().getEnvironment().with(this); J.a(() -> new IrisProject(getData().getDataFolder()).updateWorkspace()); } @@ -207,7 +203,6 @@ public class IrisEngine implements Engine { IrisWorldManager manager = new IrisWorldManager(this); worldManager = manager; manager.startManager(); - getDimension().getEngineScripts().forEach(execution::execute); J.a(this::computeBiomeMaxes); J.a(() -> { File[] roots = getData().getLoaders() diff --git a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java index 629d94746..b9bb90a19 100644 --- a/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java +++ b/core/src/main/java/art/arcane/iris/engine/IrisNoisemapPrebakePipeline.java @@ -82,7 +82,6 @@ public final class IrisNoisemapPrebakePipeline { "ravines", "mods", "expressions", - "scripts", "images", "snippet" ); diff --git a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java index 778810b3b..e118be386 100644 --- a/core/src/main/java/art/arcane/iris/engine/framework/Engine.java +++ b/core/src/main/java/art/arcane/iris/engine/framework/Engine.java @@ -29,7 +29,6 @@ import art.arcane.iris.core.loader.IrisRegistrant; import art.arcane.iris.core.nms.container.BlockPos; import art.arcane.iris.core.nms.container.Pair; import art.arcane.iris.core.pregenerator.ChunkUpdater; -import art.arcane.iris.core.scripting.environment.EngineEnvironment; import art.arcane.iris.core.service.ExternalDataSVC; import art.arcane.iris.core.tools.IrisToolbelt; import art.arcane.iris.engine.IrisComplex; @@ -114,8 +113,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat IrisContext getContext(); - EngineEnvironment getExecution(); - double getMaxBiomeObjectDensity(); double getMaxBiomeDecoratorDensity(); @@ -390,16 +387,6 @@ public interface Engine extends DataProvider, Fallible, LootProvider, BlockUpdat }, RNG.r.i(1, 20))); //Why is there a random delay here? }); - chunk.raiseFlagUnchecked(MantleFlag.SCRIPT, () -> { - var scripts = getDimension().getChunkUpdateScripts(); - if (scripts == null || scripts.isEmpty()) - return; - - for (var script : scripts) { - getExecution().updateChunk(script, chunk, c, (delay, task) -> run(semaphore, c, task, delay)); - } - }); - try { semaphore.acquire(1024); } catch (InterruptedException ex) { diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java b/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java new file mode 100644 index 000000000..323dd159e --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/mantle/MatterGenerator.java @@ -0,0 +1,248 @@ +package art.arcane.iris.engine.mantle; + +import art.arcane.iris.Iris; +import art.arcane.iris.core.IrisSettings; +import art.arcane.iris.core.nms.container.Pair; +import art.arcane.iris.engine.framework.Engine; +import art.arcane.iris.util.common.misc.RegenRuntime; +import art.arcane.iris.util.common.parallel.MultiBurst; +import art.arcane.iris.util.project.context.ChunkContext; +import art.arcane.iris.util.project.matter.TileWrapper; +import art.arcane.volmlib.util.documentation.ChunkCoordinates; +import art.arcane.volmlib.util.mantle.flag.MantleFlag; +import art.arcane.volmlib.util.mantle.runtime.Mantle; +import art.arcane.volmlib.util.mantle.runtime.MantleChunk; +import art.arcane.volmlib.util.matter.Matter; +import org.bukkit.block.data.BlockData; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.stream.Collectors; + +public interface MatterGenerator { + long REGEN_PASS_CACHE_TTL_MS = 600000L; + Executor DISPATCHER = MultiBurst.burst; + ConcurrentHashMap> REGEN_GENERATED_CHUNKS_BY_PASS = new ConcurrentHashMap<>(); + ConcurrentHashMap> REGEN_CLEARED_CHUNKS_BY_PASS = new ConcurrentHashMap<>(); + ConcurrentHashMap> REGEN_PLANNED_CHUNKS_BY_PASS = new ConcurrentHashMap<>(); + ConcurrentHashMap REGEN_PASS_TOUCHED_MS = new ConcurrentHashMap<>(); + + Engine getEngine(); + + Mantle getMantle(); + + int getRadius(); + + int getRealRadius(); + + List, Integer>> getComponents(); + + @ChunkCoordinates + default void generateMatter(int x, int z, boolean multicore, ChunkContext context) { + if (!getEngine().getDimension().isUseMantle()) { + return; + } + + boolean useMulticore = multicore || IrisSettings.get().getGenerator().isUseMulticoreMantle(); + String threadName = Thread.currentThread().getName(); + boolean regenThread = threadName.startsWith("Iris-Regen-"); + boolean traceRegen = regenThread && IrisSettings.get().getGeneral().isDebug(); + boolean forceRegen = regenThread; + String regenPassKey = forceRegen ? resolveRegenPassKey(threadName) : null; + boolean optimizedRegen = forceRegen && !IrisSettings.get().getGeneral().isDebug() && regenPassKey != null; + int writeRadius = optimizedRegen ? Math.min(getRadius(), getRealRadius()) : getRadius(); + Set clearedChunks = optimizedRegen ? getRegenPassSet(REGEN_CLEARED_CHUNKS_BY_PASS, regenPassKey) : new HashSet<>(); + Set plannedChunks = optimizedRegen ? getRegenPassSet(REGEN_PLANNED_CHUNKS_BY_PASS, regenPassKey) : null; + + if (optimizedRegen) { + touchRegenPass(regenPassKey); + } + + if (traceRegen) { + Iris.info("Regen matter start: center=" + x + "," + z + + " radius=" + getRadius() + + " realRadius=" + getRealRadius() + + " writeRadius=" + writeRadius + + " multicore=" + useMulticore + + " components=" + getComponents().size() + + " optimized=" + optimizedRegen + + " passKey=" + (regenPassKey == null ? "none" : regenPassKey) + + " thread=" + threadName); + } + + try (MantleWriter writer = new MantleWriter(getEngine().getMantle(), getMantle(), x, z, writeRadius, useMulticore)) { + for (Pair, Integer> pair : getComponents()) { + int rawPassRadius = pair.getB(); + int passRadius = optimizedRegen ? Math.min(rawPassRadius, writeRadius) : rawPassRadius; + String passFlags = pair.getA().stream().map(component -> component.getFlag().toString()).collect(Collectors.joining(",")); + String passFlagKey = optimizedRegen ? regenPassKey + "|" + passFlags : null; + Set generatedChunks = passFlagKey == null ? null : getRegenPassSet(REGEN_GENERATED_CHUNKS_BY_PASS, passFlagKey); + int visitedChunks = 0; + int clearedCount = 0; + int plannedSkipped = 0; + int componentSkipped = 0; + int componentForcedReset = 0; + int launchedLayers = 0; + int dedupSkipped = 0; + List> launchedTasks = useMulticore ? new ArrayList<>() : null; + + if (passFlagKey != null) { + touchRegenPass(passFlagKey); + } + if (traceRegen) { + Iris.info("Regen matter pass start: center=" + x + "," + z + + " passRadius=" + passRadius + + " rawPassRadius=" + rawPassRadius + + " flags=[" + passFlags + "]"); + } + + for (int i = -passRadius; i <= passRadius; i++) { + for (int j = -passRadius; j <= passRadius; j++) { + int passX = x + i; + int passZ = z + j; + visitedChunks++; + long passKey = chunkKey(passX, passZ); + if (generatedChunks != null && !generatedChunks.add(passKey)) { + dedupSkipped++; + continue; + } + + MantleChunk chunk = writer.acquireChunk(passX, passZ); + if (forceRegen) { + if (clearedChunks.add(passKey)) { + chunk.deleteSlices(BlockData.class); + chunk.deleteSlices(String.class); + chunk.deleteSlices(TileWrapper.class); + chunk.flag(MantleFlag.PLANNED, false); + clearedCount++; + } + } + + if (!forceRegen && chunk.isFlagged(MantleFlag.PLANNED)) { + plannedSkipped++; + continue; + } + + for (MantleComponent component : pair.getA()) { + if (!forceRegen && chunk.isFlagged(component.getFlag())) { + componentSkipped++; + continue; + } + if (forceRegen && chunk.isFlagged(component.getFlag())) { + chunk.flag(component.getFlag(), false); + componentForcedReset++; + } + + launchedLayers++; + int finalPassX = passX; + int finalPassZ = passZ; + MantleChunk finalChunk = chunk; + MantleComponent finalComponent = component; + Runnable task = () -> finalChunk.raiseFlagUnchecked(finalComponent.getFlag(), + () -> finalComponent.generateLayer(writer, finalPassX, finalPassZ, context)); + + if (useMulticore) { + launchedTasks.add(CompletableFuture.runAsync(task, DISPATCHER)); + } else { + task.run(); + } + } + } + } + + if (useMulticore) { + for (CompletableFuture launchedTask : launchedTasks) { + launchedTask.join(); + } + } + + if (traceRegen) { + Iris.info("Regen matter pass done: center=" + x + "," + z + + " passRadius=" + passRadius + + " rawPassRadius=" + rawPassRadius + + " visited=" + visitedChunks + + " cleared=" + clearedCount + + " dedupSkipped=" + dedupSkipped + + " plannedSkipped=" + plannedSkipped + + " componentSkipped=" + componentSkipped + + " componentForcedReset=" + componentForcedReset + + " launchedLayers=" + launchedLayers + + " flags=[" + passFlags + "]"); + } + } + + for (int i = -getRealRadius(); i <= getRealRadius(); i++) { + for (int j = -getRealRadius(); j <= getRealRadius(); j++) { + int realX = x + i; + int realZ = z + j; + long realKey = chunkKey(realX, realZ); + if (plannedChunks != null && !plannedChunks.add(realKey)) { + continue; + } + writer.acquireChunk(realX, realZ).flag(MantleFlag.PLANNED, true); + } + } + } + + if (traceRegen) { + Iris.info("Regen matter done: center=" + x + "," + z + + " markedRealRadius=" + getRealRadius() + + " forceRegen=" + forceRegen); + } + } + + private static long chunkKey(int x, int z) { + return (((long) x) << 32) ^ (z & 0xffffffffL); + } + + private static Set getRegenPassSet(ConcurrentHashMap> store, String passKey) { + return store.computeIfAbsent(passKey, key -> ConcurrentHashMap.newKeySet()); + } + + private static String resolveRegenPassKey(String threadName) { + String runtimeKey = RegenRuntime.getRunId(); + if (runtimeKey != null && !runtimeKey.isBlank()) { + return runtimeKey; + } + + if (!threadName.startsWith("Iris-Regen-")) { + return null; + } + + String suffix = threadName.substring("Iris-Regen-".length()); + int lastDash = suffix.lastIndexOf('-'); + if (lastDash <= 0) { + return suffix; + } + return suffix.substring(0, lastDash); + } + + private static void touchRegenPass(String passKey) { + long now = System.currentTimeMillis(); + REGEN_PASS_TOUCHED_MS.put(passKey, now); + if (REGEN_PASS_TOUCHED_MS.size() <= 64) { + return; + } + + Iterator> iterator = REGEN_PASS_TOUCHED_MS.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + if (now - entry.getValue() <= REGEN_PASS_CACHE_TTL_MS) { + continue; + } + + String key = entry.getKey(); + iterator.remove(); + REGEN_GENERATED_CHUNKS_BY_PASS.remove(key); + REGEN_CLEARED_CHUNKS_BY_PASS.remove(key); + REGEN_PLANNED_CHUNKS_BY_PASS.remove(key); + } + } +} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java index 62cfefc0d..a368568bd 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimension.java @@ -78,7 +78,6 @@ public class IrisDimension extends IrisRegistrant { private final transient AtomicCache cosr = new AtomicCache<>(); private final transient AtomicCache rad = new AtomicCache<>(); private final transient AtomicCache featuresUsed = new AtomicCache<>(); - private final transient AtomicCache>> cachedPreProcessors = new AtomicCache<>(); private final transient AtomicCache> carvingEntryIndex = new AtomicCache<>(); @MinNumber(2) @Required @@ -248,25 +247,10 @@ public class IrisDimension extends IrisRegistrant { @MaxNumber(318) @Desc("The Subterrain Fluid Layer Height") private int caveLavaHeight = 8; - @RegistryListFunction(ComponentFlagFunction.class) - @ArrayType(type = String.class) - @Desc("Collection of disabled components") - private KList disabledComponents = new KList<>(); - @Desc("A list of globally applied pre-processors") - @ArrayType(type = IrisPreProcessors.class) - private KList globalPreProcessors = new KList<>(); - @Desc("A list of scripts executed on engine setup\nFile extension: .engine.kts") - @RegistryListResource(IrisScript.class) - @ArrayType(type = String.class, min = 1) - private KList engineScripts = new KList<>(); - @Desc("A list of scripts executed on data setup\nFile extension: .data.kts") - @RegistryListResource(IrisScript.class) - @ArrayType(type = String.class, min = 1) - private KList dataScripts = new KList<>(); - @Desc("A list of scripts executed on chunk update\nFile extension: .update.kts") - @RegistryListResource(IrisScript.class) - @ArrayType(type = String.class, min = 1) - private KList chunkUpdateScripts = new KList<>(); + @RegistryListFunction(ComponentFlagFunction.class) + @ArrayType(type = String.class) + @Desc("Collection of disabled components") + private KList disabledComponents = new KList<>(); public int getMaxHeight() { return (int) getDimensionHeight().getMax(); @@ -398,17 +382,6 @@ public class IrisDimension extends IrisRegistrant { return r; } - public KList getPreProcessors(String type) { - return cachedPreProcessors.aquire(() -> { - KMap> preProcessors = new KMap<>(); - for (var entry : globalPreProcessors) { - preProcessors.computeIfAbsent(entry.getType(), k -> new KList<>()) - .addAll(entry.getScripts()); - } - return preProcessors; - }).get(type); - } - public IrisGeneratorStyle getBiomeStyle(InferredType type) { switch (type) { case CAVE: diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionMode.java b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionMode.java index bf50d733c..42170e426 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionMode.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisDimensionMode.java @@ -21,7 +21,6 @@ package art.arcane.iris.engine.object; import art.arcane.iris.engine.framework.Engine; import art.arcane.iris.engine.framework.EngineMode; import art.arcane.iris.engine.object.annotations.Desc; -import art.arcane.iris.engine.object.annotations.RegistryListResource; import art.arcane.iris.engine.object.annotations.Snippet; import lombok.AllArgsConstructor; import lombok.Data; @@ -38,19 +37,7 @@ public class IrisDimensionMode { @Desc("The dimension type") private IrisDimensionModeType type = IrisDimensionModeType.OVERWORLD; - @RegistryListResource(IrisScript.class) - @Desc("The script to create the dimension mode instead of using provided types\nFile extension: .engine.kts") - private String script; - public EngineMode create(Engine engine) { - if (script == null) { - return type.create(engine); - } - Object result = engine.getExecution().evaluate(script); - if (result instanceof EngineMode) { - return (EngineMode) result; - } - - throw new IllegalStateException("The script '" + script + "' did not return an engine mode!"); + return type.create(engine); } } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java b/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java index b6d61d865..bb28f344c 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisEntity.java @@ -163,18 +163,9 @@ public class IrisEntity extends IrisRegistrant { @Desc("Create a mob from another plugin, such as Mythic Mobs. Should be in the format of a namespace of PluginName:MobName") private String specialType = ""; - @Desc("Set to true if you want to apply all of the settings here to the mob, even though an external plugin has already done so. Scripts are always applied.") + @Desc("Set to true if you want to apply all of the settings here to the mob, even though an external plugin has already done so.") private boolean applySettingsToCustomMobAnyways = false; - @Desc("Set the entity type to UNKNOWN, then define a script here which ends with the entity variable (the result). You can use location to find the target location. You can spawn any entity this way.\nFile extension: .spawn.kts") - @RegistryListResource(IrisScript.class) - private String spawnerScript = ""; - - @ArrayType(min = 1, type = String.class) - @Desc("Executed post spawn you can modify the entity however you want with it\nFile extension: .postspawn.kts") - @RegistryListResource(IrisScript.class) - private KList postSpawnScripts = new KList<>(); - @ArrayType(min = 1, type = IrisCommand.class) @Desc("Run raw commands when this entity is spawned. Use {x}, {y}, and {z} for location. /summon pig {x} {y} {z}") private KList rawCommands = new KList<>(); @@ -211,17 +202,6 @@ public class IrisEntity extends IrisRegistrant { return null; } - if (!spawnerScript.isEmpty() && ee == null) { - synchronized (this) { - try { - ee = (Entity) gen.getExecution().spawnMob(spawnerScript, at); - } catch (Throwable ex) { - Iris.error("You must return an Entity in your scripts to use entity scripts!"); - ex.printStackTrace(); - } - } - } - if (isSpecialType() && !applySettingsToCustomMobAnyways) { return ee; } @@ -356,14 +336,6 @@ public class IrisEntity extends IrisRegistrant { spawnEffect.apply(e); } - if (postSpawnScripts.isNotEmpty()) { - synchronized (this) { - for (String i : postSpawnScripts) { - gen.getExecution().postSpawnMob(i, at, ee); - } - } - } - if (rawCommands.isNotEmpty()) { final Location fat = at; rawCommands.forEach(r -> r.run(fat)); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java index 64e067ee2..e97df0eba 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapack.java @@ -43,6 +43,10 @@ public class IrisExternalDatapack { @Desc("Optional structure-set alias mappings used to synthesize vanilla structure_set replacements from non-minecraft source keys") private KList structureSetAliases = new KList<>(); + @ArrayType(type = IrisExternalDatapackTemplateAlias.class, min = 1) + @Desc("Optional template location alias mappings applied while projecting template pools") + private KList templateAliases = new KList<>(); + @ArrayType(type = IrisExternalDatapackStructurePatch.class, min = 1) @Desc("Structure placement patches applied when this external datapack is projected") private KList structurePatches = new KList<>(); diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapackTemplateAlias.java b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapackTemplateAlias.java new file mode 100644 index 000000000..959820809 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisExternalDatapackTemplateAlias.java @@ -0,0 +1,23 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.engine.object.annotations.Desc; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Accessors(chain = true) +@Desc("Maps missing template-pool element locations from an external datapack to replacement template locations") +public class IrisExternalDatapackTemplateAlias { + @Desc("Source template location to rewrite") + private String from = ""; + + @Desc("Target template location. Use minecraft:empty to convert the element to an empty pool element") + private String to = ""; + + @Desc("Enable or disable this alias entry") + private boolean enabled = true; +} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java b/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java index 17a82c0a7..21e88a0cc 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisGeneratorStyle.java @@ -26,7 +26,6 @@ import art.arcane.volmlib.util.math.RNG; import art.arcane.iris.util.project.noise.CNG; import art.arcane.iris.util.project.noise.ExpressionNoise; import art.arcane.iris.util.project.noise.ImageNoise; -import art.arcane.iris.util.project.noise.NoiseGenerator; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -59,9 +58,6 @@ public class IrisGeneratorStyle { private String expression = null; @Desc("Use an Image map instead of a generated value") private IrisImageMap imageMap = null; - @Desc("Instead of using the style property, use a custom noise generator to represent this style.\nFile extension: .noise.kts") - @RegistryListResource(IrisScript.class) - private String script = null; @MinNumber(0.00001) @Desc("The Output multiplier. Only used if parent is fracture.") private double multiplier = 1; @@ -111,7 +107,7 @@ public class IrisGeneratorStyle { } private int hash() { - return Objects.hash(expression, imageMapHash(), script, multiplier, axialFracturing, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style); + return Objects.hash(expression, imageMapHash(), multiplier, axialFracturing, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style); } public int prebakeSignature() { @@ -129,19 +125,6 @@ public class IrisGeneratorStyle { return cachePrefix(rng, effectiveCacheSize) + Long.toUnsignedString(sourceStamp); } - private long scriptStamp(IrisData data) { - if (getScript() == null) { - return 0L; - } - - File scriptFile = data.getScriptLoader().findFile(getScript()); - if (scriptFile == null) { - return Integer.toUnsignedLong(getScript().hashCode()); - } - - return Integer.toUnsignedLong(Objects.hash(getScript(), scriptFile.lastModified(), scriptFile.length())); - } - private void clearStaleCacheEntries(IrisData data, String prefix, String key) { File cacheFolder = new File(data.getDataFolder(), ".cache"); File[] files = cacheFolder.listFiles((dir, name) -> name.endsWith(".cnm") && name.startsWith(prefix)); @@ -178,13 +161,6 @@ public class IrisGeneratorStyle { } else if (getImageMap() != null) { cng = new CNG(rng, new ImageNoise(data, getImageMap()), 1D, 1).bake(); sourceStamp = Integer.toUnsignedLong(imageMapHash()); - } else if (getScript() != null) { - Object result = data.getEnvironment().createNoise(getScript(), rng); - if (result == null) Iris.warn("Failed to create noise from script: " + getScript()); - if (result instanceof NoiseGenerator generator) { - cng = new CNG(rng, generator, 1D, 1).bake(); - sourceStamp = scriptStamp(data); - } } if (cng == null) { diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisPreProcessors.java b/core/src/main/java/art/arcane/iris/engine/object/IrisPreProcessors.java deleted file mode 100644 index c63bc67e8..000000000 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisPreProcessors.java +++ /dev/null @@ -1,25 +0,0 @@ -package art.arcane.iris.engine.object; - -import art.arcane.iris.engine.object.annotations.*; -import art.arcane.iris.engine.object.annotations.functions.ResourceLoadersFunction; -import art.arcane.volmlib.util.collection.KList; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -@Desc("Represents global preprocessors") -public class IrisPreProcessors { - @Required - @Desc("The preprocessor type") - @RegistryListFunction(ResourceLoadersFunction.class) - private String type = "dimension"; - - @Required - @Desc("The preprocessor scripts\nFile extension: .proc.kts") - @RegistryListResource(IrisScript.class) - @ArrayType(type = String.class, min = 1) - private KList scripts = new KList<>(); -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisScript.java b/core/src/main/java/art/arcane/iris/engine/object/IrisScript.java deleted file mode 100644 index 026bee7c7..000000000 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisScript.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package art.arcane.iris.engine.object; - -import art.arcane.iris.core.loader.IrisRegistrant; -import art.arcane.volmlib.util.json.JSONObject; -import art.arcane.iris.util.common.plugin.VolmitSender; -import lombok.Data; -import lombok.EqualsAndHashCode; - -@EqualsAndHashCode(callSuper = true) -@Data -public class IrisScript extends IrisRegistrant { - private final String source; - - public IrisScript() { - this(""); - } - - public IrisScript(String source) { - this.source = source; - } - - @Override - public String getFolderName() { - return "scripts"; - } - - @Override - public String getTypeName() { - return "Script"; - } - - public String toString() { - return source; - } - - @Override - public void scanForErrors(JSONObject p, VolmitSender sender) { - - } -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisVanillaLootTable.java b/core/src/main/java/art/arcane/iris/engine/object/IrisVanillaLootTable.java index 172d8e0e9..9f64a1590 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisVanillaLootTable.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisVanillaLootTable.java @@ -72,9 +72,4 @@ public class IrisVanillaLootTable extends IrisLootTable { public IrisData getLoader() { throw new UnsupportedOperationException("VanillaLootTables do not have a loader"); } - - @Override - public KList getPreprocessors() { - return new KList<>(); - } } diff --git a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java index 0332a1d30..406a8a880 100644 --- a/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java +++ b/core/src/main/java/art/arcane/iris/engine/platform/BukkitChunkGenerator.java @@ -114,9 +114,9 @@ public class BukkitChunkGenerator extends ChunkGenerator implements PlatformChun this.folder = new ReactiveFolder( dataLocation, (_a, _b, _c) -> hotload(), - new KList<>(".iob", ".json", ".kts"), + new KList<>(".iob", ".json"), new KList<>(".iris"), - new KList<>(".gradle.kts") + new KList<>() ); Bukkit.getServer().getPluginManager().registerEvents(this, Iris.instance); } diff --git a/core/src/main/java/art/arcane/iris/util/common/director/handlers/ScriptHandler.java b/core/src/main/java/art/arcane/iris/util/common/director/handlers/ScriptHandler.java deleted file mode 100644 index 2b5e1655f..000000000 --- a/core/src/main/java/art/arcane/iris/util/common/director/handlers/ScriptHandler.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Iris is a World Generator for Minecraft Bukkit Servers - * Copyright (c) 2022 Arcane Arts (Volmit Software) - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -package art.arcane.iris.util.common.director.handlers; - -import art.arcane.iris.engine.object.IrisScript; -import art.arcane.iris.util.common.director.specialhandlers.RegistrantHandler; - -public class ScriptHandler extends RegistrantHandler { - public ScriptHandler() { - super(IrisScript.class, false); - } - - @Override - public String getRandomDefault() { - return "script"; - } -} diff --git a/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java b/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java new file mode 100644 index 000000000..9cc2cd2cc --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/context/ChunkContext.java @@ -0,0 +1,84 @@ +package art.arcane.iris.util.project.context; + +import art.arcane.iris.engine.IrisComplex; +import art.arcane.iris.engine.object.IrisBiome; +import art.arcane.iris.engine.object.IrisRegion; +import art.arcane.iris.util.common.parallel.MultiBurst; +import org.bukkit.block.data.BlockData; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; + +public class ChunkContext { + private final int x; + private final int z; + private final ChunkedDataCache height; + private final ChunkedDataCache biome; + private final ChunkedDataCache cave; + private final ChunkedDataCache rock; + private final ChunkedDataCache fluid; + private final ChunkedDataCache region; + + public ChunkContext(int x, int z, IrisComplex complex) { + this(x, z, complex, true); + } + + public ChunkContext(int x, int z, IrisComplex complex, boolean cache) { + this.x = x; + this.z = z; + this.height = new ChunkedDataCache<>(complex.getHeightStream(), x, z, cache); + this.biome = new ChunkedDataCache<>(complex.getTrueBiomeStream(), x, z, cache); + this.cave = new ChunkedDataCache<>(complex.getCaveBiomeStream(), x, z, cache); + this.rock = new ChunkedDataCache<>(complex.getRockStream(), x, z, cache); + this.fluid = new ChunkedDataCache<>(complex.getFluidStream(), x, z, cache); + this.region = new ChunkedDataCache<>(complex.getRegionStream(), x, z, cache); + + if (cache) { + Executor executor = MultiBurst.burst; + List> tasks = new ArrayList<>(6); + tasks.add(CompletableFuture.runAsync(() -> height.fill(executor), executor)); + tasks.add(CompletableFuture.runAsync(() -> biome.fill(executor), executor)); + tasks.add(CompletableFuture.runAsync(() -> cave.fill(executor), executor)); + tasks.add(CompletableFuture.runAsync(() -> rock.fill(executor), executor)); + tasks.add(CompletableFuture.runAsync(() -> fluid.fill(executor), executor)); + tasks.add(CompletableFuture.runAsync(() -> region.fill(executor), executor)); + for (CompletableFuture task : tasks) { + task.join(); + } + } + } + + public int getX() { + return x; + } + + public int getZ() { + return z; + } + + public ChunkedDataCache getHeight() { + return height; + } + + public ChunkedDataCache getBiome() { + return biome; + } + + public ChunkedDataCache getCave() { + return cave; + } + + public ChunkedDataCache getRock() { + return rock; + } + + public ChunkedDataCache getFluid() { + return fluid; + } + + public ChunkedDataCache getRegion() { + return region; + } +} diff --git a/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java new file mode 100644 index 000000000..f0f5472f2 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/util/project/context/ChunkedDataCache.java @@ -0,0 +1,73 @@ +package art.arcane.iris.util.project.context; + +import art.arcane.iris.util.project.stream.ProceduralStream; +import art.arcane.volmlib.util.documentation.BlockCoordinates; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.ForkJoinPool; + +public class ChunkedDataCache { + private final int x; + private final int z; + private final ProceduralStream stream; + private final boolean cache; + private final Object[] data; + + @BlockCoordinates + public ChunkedDataCache(ProceduralStream stream, int x, int z) { + this(stream, x, z, true); + } + + @BlockCoordinates + public ChunkedDataCache(ProceduralStream stream, int x, int z, boolean cache) { + this.x = x; + this.z = z; + this.stream = stream; + this.cache = cache; + this.data = new Object[cache ? 256 : 0]; + } + + public void fill() { + fill(ForkJoinPool.commonPool()); + } + + public void fill(Executor executor) { + if (!cache) { + return; + } + + List> tasks = new ArrayList<>(16); + for (int j = 0; j < 16; j++) { + int row = j; + tasks.add(CompletableFuture.runAsync(() -> { + int rowOffset = row * 16; + double zz = (z + row); + for (int i = 0; i < 16; i++) { + data[rowOffset + i] = stream.get(x + i, zz); + } + }, executor)); + } + + for (CompletableFuture task : tasks) { + task.join(); + } + } + + @BlockCoordinates + @SuppressWarnings("unchecked") + public T get(int x, int z) { + if (!cache) { + return stream.get(this.x + x, this.z + z); + } + + T value = (T) data[(z * 16) + x]; + if (value != null) { + return value; + } + + return stream.get(this.x + x, this.z + z); + } +} diff --git a/core/src/main/kotlin/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.kt b/core/src/main/kotlin/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.kt deleted file mode 100644 index aa05bee7a..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.kt +++ /dev/null @@ -1,232 +0,0 @@ -package art.arcane.iris.core.pregenerator.cache - -import art.arcane.iris.Iris -import art.arcane.volmlib.util.data.Varint -import art.arcane.volmlib.util.documentation.ChunkCoordinates -import art.arcane.volmlib.util.documentation.RegionCoordinates -import art.arcane.volmlib.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/art/arcane/iris/core/safeguard/IrisSafeguard.kt b/core/src/main/kotlin/art/arcane/iris/core/safeguard/IrisSafeguard.kt deleted file mode 100644 index 9b71a809b..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/safeguard/IrisSafeguard.kt +++ /dev/null @@ -1,108 +0,0 @@ -package art.arcane.iris.core.safeguard - -import art.arcane.iris.Iris -import art.arcane.iris.core.safeguard.task.Diagnostic -import art.arcane.iris.core.safeguard.task.Task -import art.arcane.iris.core.safeguard.task.ValueWithDiagnostics -import art.arcane.iris.core.safeguard.task.tasks -import art.arcane.iris.util.common.format.C -import art.arcane.iris.util.common.scheduling.J -import java.util.* - -object IrisSafeguard { - @Volatile - private var forceShutdown = false - private var results: Map> = emptyMap() - private var context: Map = emptyMap() - private var attachment: Map> = emptyMap() - private var mode = Mode.STABLE - private var count = 0 - - @JvmStatic - fun execute() { - val results = LinkedHashMap>(tasks.size) - val context = LinkedHashMap(tasks.size) - val attachment = LinkedHashMap>(tasks.size) - var mode = Mode.STABLE - var count = 0 - for (task in tasks) { - var result: ValueWithDiagnostics - try { - result = task.run() - } catch (e: Throwable) { - Iris.reportError(e) - result = ValueWithDiagnostics( - Mode.WARNING, - Diagnostic(Diagnostic.Logger.ERROR, "Error while running task ${task.id}", e) - ) - } - mode = mode.highest(result.value) - results[task] = result - context[task.id] = result.value.id - attachment[task.id] = result.diagnostics.flatMap { it.toString().split('\n') } - if (result.value != Mode.STABLE) count++ - } - - this.results = Collections.unmodifiableMap(results) - this.context = Collections.unmodifiableMap(context) - this.attachment = Collections.unmodifiableMap(attachment) - this.mode = mode - this.count = count - } - - @JvmStatic - fun mode() = mode - - @JvmStatic - fun asContext() = context - - @JvmStatic - fun asAttachment() = attachment - - @JvmStatic - fun splash() { - Iris.instance.splash() - printReports() - printFooter() - } - - @JvmStatic - fun printReports() { - when (mode) { - Mode.STABLE -> Iris.info(C.BLUE.toString() + "0 Conflicts found") - Mode.WARNING -> Iris.warn(C.GOLD.toString() + "%s Issues found", count) - Mode.UNSTABLE -> Iris.error(C.DARK_RED.toString() + "%s Issues found", count) - } - - results.values.forEach { it.log(withStackTrace = true) } - } - - @JvmStatic - fun printFooter() { - when (mode) { - Mode.STABLE -> Iris.info(C.BLUE.toString() + "Iris is running Stable") - Mode.WARNING -> warning() - Mode.UNSTABLE -> unstable() - } - } - - @JvmStatic - fun isForceShutdown() = forceShutdown - - private fun warning() { - Iris.warn(C.GOLD.toString() + "Iris is running in Warning Mode") - Iris.warn(C.GRAY.toString() + "Some startup checks need attention. Review the messages above for tuning suggestions.") - Iris.warn(C.GRAY.toString() + "Iris will continue startup normally.") - Iris.warn("") - } - - private fun unstable() { - Iris.error(C.DARK_RED.toString() + "Iris is running in Danger Mode") - Iris.error("") - Iris.error(C.DARK_GRAY.toString() + "--==<" + C.RED + " IMPORTANT " + C.DARK_GRAY + ">==--") - Iris.error("Critical startup checks failed. Iris will continue startup in 10 seconds.") - Iris.error("Review and resolve the errors above as soon as possible.") - J.sleep(10000L) - Iris.info("") - } -} diff --git a/core/src/main/kotlin/art/arcane/iris/core/safeguard/Mode.kt b/core/src/main/kotlin/art/arcane/iris/core/safeguard/Mode.kt deleted file mode 100644 index f3ec3c395..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/safeguard/Mode.kt +++ /dev/null @@ -1,125 +0,0 @@ -package art.arcane.iris.core.safeguard - -import art.arcane.iris.BuildConstants -import art.arcane.iris.Iris -import art.arcane.iris.core.IrisSettings -import art.arcane.iris.util.common.format.C -import art.arcane.volmlib.util.format.Form -import org.bukkit.Bukkit -import java.time.LocalDate -import java.time.format.DateTimeFormatter - -enum class Mode(private val color: C) { - STABLE(C.IRIS), - WARNING(C.GOLD), - UNSTABLE(C.RED); - - val id = name.lowercase() - - fun highest(m: Mode): Mode { - return if (m.ordinal > ordinal) m else this - } - - fun tag(subTag: String?): String { - if (subTag == null || subTag.isBlank()) return wrap("Iris") + C.GRAY + ": " - return wrap("Iris") + " " + wrap(subTag) + C.GRAY + ": " - } - - private fun wrap(tag: String?): String { - return C.BOLD.toString() + "" + C.DARK_GRAY + "[" + C.BOLD + color + tag + C.BOLD + C.DARK_GRAY + "]" + C.RESET - } - - fun trySplash() { - if (!IrisSettings.get().general.isSplashLogoStartup) return - splash() - } - - fun splash() { - val padd = Form.repeat(" ", 8) - val padd2 = Form.repeat(" ", 4) - val version = Iris.instance.description.version - val releaseTrain = getReleaseTrain(version) - val serverVersion = getServerVersion() - val startupDate = getStartupDate() - val javaVersion = getJavaVersion() - - val splash = arrayOf( - padd + C.GRAY + " @@@@@@@@@@@@@@" + C.DARK_GRAY + "@@@", - padd + C.GRAY + " @@&&&&&&&&&" + C.DARK_GRAY + "&&&&&&" + color + " .(((()))). ", - padd + C.GRAY + "@@@&&&&&&&&" + C.DARK_GRAY + "&&&&&" + color + " .((((((())))))). ", - padd + C.GRAY + "@@@&&&&&" + C.DARK_GRAY + "&&&&&&&" + color + " ((((((((())))))))) " + C.GRAY + " @", - padd + C.GRAY + "@@@&&&&" + C.DARK_GRAY + "@@@@@&" + color + " ((((((((-))))))))) " + C.GRAY + " @@", - padd + C.GRAY + "@@@&&" + color + " ((((((({ })))))))) " + C.GRAY + " &&@@@", - padd + C.GRAY + "@@" + color + " ((((((((-))))))))) " + C.DARK_GRAY + "&@@@@@" + C.GRAY + "&&&&@@@", - padd + C.GRAY + "@" + color + " ((((((((())))))))) " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&@@@", - padd + C.GRAY + "" + color + " '((((((()))))))' " + C.DARK_GRAY + "&&&&&" + C.GRAY + "&&&&&&&&@@@", - padd + C.GRAY + "" + color + " '(((())))' " + C.DARK_GRAY + "&&&&&&&&" + C.GRAY + "&&&&&&&@@", - padd + C.GRAY + " " + C.DARK_GRAY + "@@@" + C.GRAY + "@@@@@@@@@@@@@@", - ) - - val info = arrayOf( - "", - padd2 + color + " Iris, " + C.AQUA + "Dimension Engine " + C.RED + "[" + releaseTrain + " RELEASE]", - padd2 + C.GRAY + " Version: " + color + version, - padd2 + C.GRAY + " By: " + color + "Volmit Software (Arcane Arts)", - padd2 + C.GRAY + " Server: " + color + serverVersion, - padd2 + C.GRAY + " Java: " + color + javaVersion + C.GRAY + " | Date: " + color + startupDate, - padd2 + C.GRAY + " Commit: " + color + BuildConstants.COMMIT + C.GRAY + "/" + color + BuildConstants.ENVIRONMENT, - "", - "", - "", - "", - ) - - - val builder = StringBuilder("\n\n") - for (i in splash.indices) { - builder.append(splash[i]) - if (i < info.size) { - builder.append(info[i]) - } - builder.append("\n") - } - - Iris.info(builder.toString()) - } - - private fun getServerVersion(): String { - var version = Bukkit.getVersion() - val mcMarkerIndex = version.indexOf(" (MC:") - if (mcMarkerIndex != -1) { - version = version.substring(0, mcMarkerIndex) - } - return version - } - - private fun getJavaVersion(): Int { - var version = System.getProperty("java.version") - if (version.startsWith("1.")) { - version = version.substring(2, 3) - } else { - val dot = version.indexOf(".") - if (dot != -1) { - version = version.substring(0, dot) - } - } - return version.toInt() - } - - private fun getStartupDate(): String { - return LocalDate.now().format(DateTimeFormatter.ISO_LOCAL_DATE) - } - - private fun getReleaseTrain(version: String): String { - var value = version - val suffixIndex = value.indexOf("-") - if (suffixIndex >= 0) { - value = value.substring(0, suffixIndex) - } - val split = value.split('.') - if (split.size >= 2) { - return split[0] + "." + split[1] - } - return value - } -} diff --git a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Task.kt b/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Task.kt deleted file mode 100644 index 321474f9b..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Task.kt +++ /dev/null @@ -1,28 +0,0 @@ -package art.arcane.iris.core.safeguard.task - -import art.arcane.iris.core.safeguard.Mode -import art.arcane.volmlib.util.format.Form -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadOnlyProperty - -abstract class Task( - val id: String, - val name: String = Form.capitalizeWords(id.replace(" ", "_").lowercase()), -) { - - abstract fun run(): ValueWithDiagnostics - - companion object { - fun of(id: String, name: String = id, action: () -> ValueWithDiagnostics) = object : Task(id, name) { - override fun run() = action() - } - - fun of(id: String, action: () -> ValueWithDiagnostics) = object : Task(id) { - override fun run() = action() - } - - fun task(action: () -> ValueWithDiagnostics) = PropertyDelegateProvider> { _, _ -> - ReadOnlyProperty { _, property -> of(property.name, action) } - } - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Tasks.kt b/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Tasks.kt deleted file mode 100644 index 365f2ff09..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/Tasks.kt +++ /dev/null @@ -1,183 +0,0 @@ -package art.arcane.iris.core.safeguard.task - -import art.arcane.iris.Iris -import art.arcane.iris.core.IrisWorlds -import art.arcane.iris.core.nms.INMS -import art.arcane.iris.core.nms.v1X.NMSBinding1X -import art.arcane.iris.core.safeguard.Mode -import art.arcane.iris.core.safeguard.Mode.* -import art.arcane.iris.core.safeguard.task.Diagnostic.Logger.* -import art.arcane.iris.core.safeguard.task.Task.Companion.of -import art.arcane.iris.util.project.agent.Agent -import art.arcane.iris.util.common.misc.getHardware -import org.bukkit.Bukkit -import java.util.Locale -import java.util.stream.Collectors -import kotlin.properties.PropertyDelegateProvider -import kotlin.properties.ReadOnlyProperty - -private val memory by task { - val mem = getHardware.getProcessMemory() - when { - mem >= 3072 -> STABLE.withDiagnostics() - mem > 2048 -> STABLE.withDiagnostics( - INFO.create("Memory Recommendation"), - INFO.create("- 3GB+ process memory is recommended for Iris."), - INFO.create("- Process Memory: $mem MB") - ) - else -> WARNING.withDiagnostics( - WARN.create("Low Memory"), - WARN.create("- Iris is running with 2GB or less process memory."), - WARN.create("- 3GB+ process memory is recommended for Iris."), - WARN.create("- Process Memory: $mem MB") - ) - } -} - -private val incompatibilities by task { - val plugins = mutableSetOf("dynmap", "Stratos") - plugins.removeIf { server.pluginManager.getPlugin(it) == null } - - if (plugins.isEmpty()) STABLE.withDiagnostics() - else { - val diagnostics = mutableListOf() - if ("dynmap" in plugins) diagnostics.addAll( - ERROR.create("Dynmap"), - ERROR.create("- The plugin Dynmap is not compatible with the server."), - ERROR.create("- If you want to have a map plugin like Dynmap, consider Bluemap.") - ) - if ("Stratos" in plugins) diagnostics.addAll( - ERROR.create("Stratos"), - ERROR.create("- Iris is not compatible with other worldgen plugins.") - ) - WARNING.withDiagnostics(diagnostics) - } -} - -private val software by task { - val supported = setOf( - "canvas", - "folia", - "purpur", - "pufferfish", - "paper", - "spigot", - "bukkit" - ) - - if (isCanvasServer() || supported.any { server.name.contains(it, true) }) STABLE.withDiagnostics() - else WARNING.withDiagnostics( - WARN.create("Unsupported Server Software"), - WARN.create("- Please consider using Canvas, Folia, Paper, or Purpur instead.") - ) -} - -private val version by task { - val parts: List = Iris.instance.description.version.split('-') - val supportedVersions: String = when { - parts.size >= 3 -> { - val minVersion: String = parts[1] - val maxVersion: String = parts[2] - if (minVersion == maxVersion) minVersion else "$minVersion - $maxVersion" - } - parts.size >= 2 -> parts[1] - else -> "1.21.11" - } - - if (INMS.get() !is NMSBinding1X) STABLE.withDiagnostics() - else UNSTABLE.withDiagnostics( - ERROR.create("Server Version"), - ERROR.create("- Iris only supports $supportedVersions") - ) -} - -private val injection by task { - if (!isPaperPreferredServer() && !Agent.isInstalled()) { - WARNING.withDiagnostics( - WARN.create("Java Agent"), - WARN.create("- Skipping dynamic Java agent attach on Spigot/Bukkit to avoid runtime agent warnings."), - WARN.create("- For full runtime injection support, run with -javaagent:" + Agent.AGENT_JAR.path + " or use Canvas/Folia/Paper/Purpur.") - ) - } else if (!Agent.install()) UNSTABLE.withDiagnostics( - ERROR.create("Java Agent"), - ERROR.create("- Please enable dynamic agent loading by adding -XX:+EnableDynamicAgentLoading to your jvm arguments."), - ERROR.create("- or add the jvm argument -javaagent:" + Agent.AGENT_JAR.path) - ) - else if (!INMS.get().injectBukkit()) UNSTABLE.withDiagnostics( - ERROR.create("Code Injection"), - ERROR.create("- Failed to inject code. Please contact support") - ) - else STABLE.withDiagnostics() -} - -private val dimensionTypes by task { - val keys = IrisWorlds.get() - .dimensions - .map { it.dimensionTypeKey } - .collect(Collectors.toSet()) - - if (!INMS.get().missingDimensionTypes(*keys.toTypedArray())) STABLE.withDiagnostics() - else UNSTABLE.withDiagnostics( - ERROR.create("Dimension Types"), - ERROR.create("- Required Iris dimension types were not loaded."), - ERROR.create("- If this still happens after a restart please contact support.") - ) -} - -private val diskSpace by task { - if (server.worldContainer.freeSpace.toDouble().div(0x4000_0000) > 3) STABLE.withDiagnostics() - else WARNING.withDiagnostics( - WARN.create("Insufficient Disk Space"), - WARN.create("- 3GB of free space is required for Iris to function.") - ) -} - -private val java by task { - val version = Iris.getJavaVersion() - when { - version == 21 -> STABLE.withDiagnostics() - version > 21 -> STABLE.withDiagnostics( - INFO.create("Java Runtime"), - INFO.create("- Running Java $version. Iris is tested primarily on Java 21.") - ) - else -> WARNING.withDiagnostics( - WARN.create("Unsupported Java version"), - WARN.create("- Java 21+ is recommended. Current runtime: Java $version") - ) - } -} - - -val tasks = listOf( - memory, - incompatibilities, - software, - version, - injection, - dimensionTypes, - diskSpace, - java, -) - -private val server get() = Bukkit.getServer() -private fun isPaperPreferredServer(): Boolean { - val name = server.name.lowercase(Locale.ROOT) - return isCanvasServer() - || name.contains("folia") - || name.contains("paper") - || name.contains("purpur") - || name.contains("pufferfish") -} -private fun isCanvasServer(): Boolean { - val loader: ClassLoader? = server.javaClass.classLoader - return try { - Class.forName("io.canvasmc.canvas.region.WorldRegionizer", false, loader) - true - } catch (_: Throwable) { - server.name.contains("canvas", true) - } -} -private fun MutableList.addAll(vararg values: T) = values.forEach(this::add) -fun task(action: () -> ValueWithDiagnostics) = PropertyDelegateProvider> { _, _ -> - ReadOnlyProperty { _, property -> of(property.name, action) } -} diff --git a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.kt b/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.kt deleted file mode 100644 index ea5de3afd..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/safeguard/task/ValueWithDiagnostics.kt +++ /dev/null @@ -1,74 +0,0 @@ -package art.arcane.iris.core.safeguard.task - -import art.arcane.iris.Iris -import art.arcane.iris.util.common.format.C -import java.io.ByteArrayOutputStream -import java.io.PrintStream - -data class ValueWithDiagnostics( - val value: T, - val diagnostics: List -) { - constructor(value: T, vararg diagnostics: Diagnostic) : this(value, diagnostics.toList()) - - @JvmOverloads - fun log( - withException: Boolean = true, - withStackTrace: Boolean = false - ) { - diagnostics.forEach { it.log(withException, withStackTrace) } - } -} - -data class Diagnostic @JvmOverloads constructor( - val logger: Logger = Logger.ERROR, - val message: String, - val exception: Throwable? = null -) { - - enum class Logger( - private val logger: (String) -> Unit - ) { - DEBUG(Iris::debug), - RAW(Iris::msg), - INFO(Iris::info), - WARN(Iris::warn), - ERROR(Iris::error); - - fun print(message: String) = message.split('\n').forEach(logger) - fun create(message: String, exception: Throwable? = null) = Diagnostic(this, message, exception) - } - - @JvmOverloads - fun log( - withException: Boolean = true, - withStackTrace: Boolean = false - ) { - logger.print(render(withException, withStackTrace)) - } - - fun render( - withException: Boolean = true, - withStackTrace: Boolean = false - ): String = buildString { - append(message) - if (withException && exception != null) { - append(": ") - append(exception) - if (withStackTrace) { - ByteArrayOutputStream().use { os -> - val ps = PrintStream(os) - exception.printStackTrace(ps) - ps.flush() - append("\n") - append(os.toString()) - } - } - } - } - - override fun toString(): String = C.strip(render()) -} - -fun T.withDiagnostics(vararg diagnostics: Diagnostic) = ValueWithDiagnostics(this, diagnostics.toList()) -fun T.withDiagnostics(diagnostics: List) = ValueWithDiagnostics(this, diagnostics) \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/ChunkUpdateScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/ChunkUpdateScript.kt deleted file mode 100644 index b931275a9..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/ChunkUpdateScript.kt +++ /dev/null @@ -1,21 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.iris.core.scripting.func.UpdateExecutor -import art.arcane.volmlib.util.mantle.runtime.MantleChunk -import org.bukkit.Chunk -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "update.kts", compilationConfiguration = ChunkUpdateScriptDefinition::class) -abstract class ChunkUpdateScript - -object ChunkUpdateScriptDefinition : ScriptCompilationConfiguration(listOf(EngineScriptDefinition), { - providedProperties( - "mantleChunk" to MantleChunk::class, - "chunk" to Chunk::class, - "executor" to UpdateExecutor::class - ) -}) { - private fun readResolve(): Any = MobSpawningScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/DataScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/DataScript.kt deleted file mode 100644 index bc8c80748..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/DataScript.kt +++ /dev/null @@ -1,15 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.iris.core.loader.IrisData -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "data.kts", compilationConfiguration = DataScriptDefinition::class) -abstract class DataScript - -object DataScriptDefinition : ScriptCompilationConfiguration(listOf(SimpleScriptDefinition), { - providedProperties("data" to IrisData::class) -}) { - private fun readResolve(): Any = DataScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/EngineScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/EngineScript.kt deleted file mode 100644 index d829679bf..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/EngineScript.kt +++ /dev/null @@ -1,25 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.iris.core.scripting.func.BiomeLookup -import art.arcane.iris.engine.IrisComplex -import art.arcane.iris.engine.framework.Engine -import art.arcane.iris.engine.`object`.IrisDimension -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "engine.kts", compilationConfiguration = EngineScriptDefinition::class) -abstract class EngineScript - -object EngineScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { - providedProperties( - "engine" to Engine::class, - "seed" to Long::class, - "dimension" to IrisDimension::class, - "complex" to IrisComplex::class, - "biome" to BiomeLookup::class, - ) -}) { - - private fun readResolve(): Any = EngineScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/MobSpawningScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/MobSpawningScript.kt deleted file mode 100644 index e6f531acd..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/MobSpawningScript.kt +++ /dev/null @@ -1,15 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import org.bukkit.Location -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "spawn.kts", compilationConfiguration = MobSpawningScriptDefinition::class) -abstract class MobSpawningScript - -object MobSpawningScriptDefinition : ScriptCompilationConfiguration(listOf(EngineScriptDefinition), { - providedProperties("location" to Location::class) -}) { - private fun readResolve(): Any = MobSpawningScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/NoiseScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/NoiseScript.kt deleted file mode 100644 index 5b300d782..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/NoiseScript.kt +++ /dev/null @@ -1,16 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.volmlib.util.math.RNG -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "noise.kts", compilationConfiguration = NoiseScriptDefinition::class) -abstract class NoiseScript - -object NoiseScriptDefinition : ScriptCompilationConfiguration(listOf(DataScriptDefinition), { - providedProperties("rng" to RNG::class) -}) { - - private fun readResolve(): Any = NoiseScriptDefinition -} diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PostMobSpawningScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PostMobSpawningScript.kt deleted file mode 100644 index b2ab432b2..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PostMobSpawningScript.kt +++ /dev/null @@ -1,15 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import org.bukkit.entity.Entity -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "postspawn.kts", compilationConfiguration = PostMobSpawningScriptDefinition::class) -abstract class PostMobSpawningScript - -object PostMobSpawningScriptDefinition : ScriptCompilationConfiguration(listOf(MobSpawningScriptDefinition), { - providedProperties("entity" to Entity::class) -}) { - private fun readResolve(): Any = PostMobSpawningScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PreprocessorScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PreprocessorScript.kt deleted file mode 100644 index 13fbb96a4..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/PreprocessorScript.kt +++ /dev/null @@ -1,22 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.iris.core.loader.IrisRegistrant -import art.arcane.iris.engine.framework.Engine -import art.arcane.iris.engine.`object`.IrisDimension -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.providedProperties - -@KotlinScript(fileExtension = "proc.kts", compilationConfiguration = PreprocessorScriptDefinition::class) -abstract class PreprocessorScript - -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/art/arcane/iris/core/scripting/kotlin/base/SimpleScript.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/SimpleScript.kt deleted file mode 100644 index df3e29259..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/base/SimpleScript.kt +++ /dev/null @@ -1,33 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.base - -import art.arcane.iris.core.scripting.kotlin.runner.configure -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ScriptCompilationConfiguration -import kotlin.script.experimental.api.defaultImports -import kotlin.script.experimental.dependencies.DependsOn -import kotlin.script.experimental.dependencies.Repository -import kotlin.script.experimental.jvm.dependenciesFromClassContext -import kotlin.script.experimental.jvm.jvm - -@KotlinScript(fileExtension = "simple.kts", compilationConfiguration = SimpleScriptDefinition::class) -abstract class SimpleScript - -object SimpleScriptDefinition : ScriptCompilationConfiguration({ - defaultImports( - DependsOn::class.qualifiedName!!, - Repository::class.qualifiedName!!, - "art.arcane.iris.Iris.info", - "art.arcane.iris.Iris.debug", - "art.arcane.iris.Iris.warn", - "art.arcane.iris.Iris.error" - ) - - jvm { - dependenciesFromClassContext(KotlinScript::class, wholeClasspath = true) - dependenciesFromClassContext(SimpleScript::class, wholeClasspath = true) - } - - configure() -}) { - private fun readResolve(): Any = SimpleScriptDefinition -} \ No newline at end of file diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt deleted file mode 100644 index 2978510a2..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisExecutionEnvironment.kt +++ /dev/null @@ -1,63 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.environment - -import art.arcane.iris.core.loader.IrisRegistrant -import art.arcane.iris.core.scripting.environment.EngineEnvironment -import art.arcane.iris.core.scripting.func.BiomeLookup -import art.arcane.iris.core.scripting.func.UpdateExecutor -import art.arcane.iris.core.scripting.kotlin.base.ChunkUpdateScript -import art.arcane.iris.core.scripting.kotlin.base.EngineScript -import art.arcane.iris.core.scripting.kotlin.base.MobSpawningScript -import art.arcane.iris.core.scripting.kotlin.base.PostMobSpawningScript -import art.arcane.iris.core.scripting.kotlin.base.PreprocessorScript -import art.arcane.iris.core.scripting.kotlin.environment.IrisSimpleExecutionEnvironment -import art.arcane.iris.core.scripting.kotlin.runner.ScriptRunner -import art.arcane.iris.engine.framework.Engine -import art.arcane.volmlib.util.mantle.runtime.MantleChunk -import org.bukkit.Chunk -import org.bukkit.Location -import org.bukkit.entity.Entity -import java.io.File - -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) = - execute(script, EngineScript::class.java, engine.parameters()) - - override fun evaluate(script: String) = - evaluate(script, EngineScript::class.java, engine.parameters()) - - override fun spawnMob(script: String, location: Location) = - evaluate(script, MobSpawningScript::class.java, engine.parameters("location" to location)) - - override fun postSpawnMob(script: String, location: Location, mob: Entity) = - 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.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.limitedParameters(vararg values: Pair): Map { - return mapOf( - "data" to data, - "engine" to this, - "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/art/arcane/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt deleted file mode 100644 index 0899291d3..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisPackExecutionEnvironment.kt +++ /dev/null @@ -1,48 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.environment - -import art.arcane.iris.core.loader.IrisData -import art.arcane.iris.core.scripting.environment.EngineEnvironment -import art.arcane.iris.core.scripting.environment.PackEnvironment -import art.arcane.iris.core.scripting.kotlin.base.DataScript -import art.arcane.iris.core.scripting.kotlin.base.NoiseScript -import art.arcane.iris.core.scripting.kotlin.runner.Script -import art.arcane.iris.core.scripting.kotlin.runner.ScriptRunner -import art.arcane.iris.core.scripting.kotlin.runner.valueOrThrow -import art.arcane.iris.engine.framework.Engine -import art.arcane.volmlib.util.math.RNG -import kotlin.reflect.KClass - -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 - - override fun compile(script: String, type: KClass<*>): Script { - val loaded = data.scriptLoader.load(script) - return compileCache.get(script) - .computeIfAbsent(type) { _ -> runner.compile(type, loaded.loadFile, loaded.source) } - .valueOrThrow("Failed to compile script $script") - } - - override fun execute(script: String) = - execute(script, DataScript::class.java, data.parameters()) - - override fun evaluate(script: String) = - evaluate(script, DataScript::class.java, data.parameters()) - - 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, - *values, - ) - } -} diff --git a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt b/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt deleted file mode 100644 index d2f7c89eb..000000000 --- a/core/src/main/kotlin/art/arcane/iris/core/scripting/kotlin/environment/IrisSimpleExecutionEnvironment.kt +++ /dev/null @@ -1,187 +0,0 @@ -package art.arcane.iris.core.scripting.kotlin.environment - -import art.arcane.iris.Iris -import art.arcane.iris.core.IrisSettings -import art.arcane.iris.core.scripting.environment.SimpleEnvironment -import art.arcane.iris.core.scripting.kotlin.base.* -import art.arcane.iris.core.scripting.kotlin.runner.FileComponents -import art.arcane.iris.core.scripting.kotlin.runner.Script -import art.arcane.iris.core.scripting.kotlin.runner.ScriptRunner -import art.arcane.iris.core.scripting.kotlin.runner.classpath -import art.arcane.iris.core.scripting.kotlin.runner.value -import art.arcane.iris.core.scripting.kotlin.runner.valueOrThrow -import art.arcane.volmlib.util.collection.KMap -import art.arcane.volmlib.util.data.KCache -import art.arcane.iris.util.common.format.C -import java.io.File -import kotlin.reflect.KClass -import kotlin.script.experimental.annotations.KotlinScript -import kotlin.script.experimental.api.ResultWithDiagnostics -import kotlin.text.split - -open class IrisSimpleExecutionEnvironment internal constructor( - baseDir: File, - parent: ScriptRunner? -) : SimpleEnvironment { - @JvmOverloads - constructor(baseDir: File = File(".").absoluteFile) : this(baseDir, null) - protected val compileCache = KCache, ResultWithDiagnostics