This commit is contained in:
Brian Neumann-Fopiano
2026-02-22 08:59:50 -05:00
parent 130073989d
commit 589baafc28
90 changed files with 2798 additions and 3681 deletions

268
build.gradle Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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' }
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<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")
// ==============================================================
val nmsBindings = mapOf(
"v1_21_R7" to "1.21.11-R0.1-SNAPSHOT",
)
nmsBindings.forEach { (key, value) ->
project(":nms:$key") {
apply<JavaPlugin>()
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<Copy>("iris") {
group = "iris"
dependsOn("jar")
from(layout.buildDirectory.file("libs/Iris-${project.version}.jar"))
into(layout.buildDirectory)
}
register<Copy>("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<Download>("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<JavaPlugin>()
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<Jar>("sourcesJar") {
archiveClassifier.set("sources")
from(sourceSets.main.map { it.allSource })
}
register<Jar>("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<Copy>("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<Copy>("build$name") {
group = "development"
outputs.upToDateWhen { false }
dependsOn("iris")
from(layout.buildDirectory.file("Iris-${project.version}.jar"))
into(file(path))
rename { "Iris.jar" }
}
}

25
buildSrc/build.gradle Normal file
View File

@@ -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')
}

View File

@@ -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")
}

View File

@@ -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<Project> {
@Override
public void apply(Project target) {
target.getPlugins().apply("maven-publish");
TaskProvider<GenerateApiTask> 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<Jar> 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",
"<init>",
"(Ljava/lang/String;)V",
false
);
mv.visitInsn(Opcodes.ATHROW);
mv.visitMaxs(0, 0);
mv.visitEnd();
}
}

View File

@@ -0,0 +1,5 @@
public class Config {
public int jvm = 21;
public NMSBinding.Type type = NMSBinding.Type.DIRECT;
public String version;
}

View File

@@ -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<Project> {
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<Config> 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 extends Named> T named(ObjectFactory objects, Class<T> 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<FileTree> 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<File> files = getInputs().getFiles().getFiles();
List<Future<?>> 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<String> 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
}
}

View File

@@ -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<Project> {
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<out String>?
) = 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",
"<init>", "(Ljava/lang/String;)V", false
)
mv.visitInsn(Opcodes.ATHROW)
mv.visitMaxs(0, 0)
mv.visitEnd()
}
}

View File

@@ -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<Project> {
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<String>()
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 <reified T : Named> ObjectFactory.named(name: String): T = named(T::class.java, name)

14
core/agent/build.gradle Normal file
View File

@@ -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
)
}

View File

@@ -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
)
}

257
core/build.gradle Normal file
View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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 })
}
}
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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>("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<Copy>("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<Grgit>().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 })
}

View File

@@ -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<ProjectionInputAsset> 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<String> remappedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> projectedStructureKeys = new LinkedHashSet<>();
LinkedHashSet<String> structureSetReferences = new LinkedHashSet<>();
int templateAliasesApplied = 0;
int emptyElementConversions = 0;
LinkedHashSet<String> unresolvedTemplateRefs = new LinkedHashSet<>();
LinkedHashSet<String> writtenPaths = new LinkedHashSet<>();
ArrayList<ProjectionOutputAsset> 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<String, String> 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<String> referenced = new LinkedHashSet<>();
LinkedHashSet<String> rewritten = new LinkedHashSet<>();
LinkedHashSet<String> 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<String> readStructureSetReferences(JSONObject root) {
LinkedHashSet<String> references = new LinkedHashSet<>();
if (root == null) {
@@ -4002,6 +4114,7 @@ public final class ExternalDataPackPipeline {
Set<String> structureSets,
Map<String, List<String>> structureAliases,
Map<String, String> structureSetAliases,
Map<String, String> templateAliases,
Set<String> configuredFeatures,
Set<String> placedFeatures,
Set<String> templatePools,
@@ -4024,6 +4137,7 @@ public final class ExternalDataPackPipeline {
IrisExternalDatapackReplaceTargets replaceTargets,
KList<IrisExternalDatapackStructureAlias> structureAliases,
KList<IrisExternalDatapackStructureSetAlias> structureSetAliases,
KList<IrisExternalDatapackTemplateAlias> templateAliases,
KList<IrisExternalDatapackStructurePatch> 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<IrisExternalDatapackStructureAlias> structureAliases,
KList<IrisExternalDatapackStructureSetAlias> structureSetAliases,
KList<IrisExternalDatapackTemplateAlias> templateAliases,
KList<IrisExternalDatapackStructurePatch> structurePatches,
Set<String> 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<String, String> normalizeTemplateAliases(KList<IrisExternalDatapackTemplateAlias> aliases) {
LinkedHashMap<String, String> 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<String> immutableSet(Set<String> values) {
LinkedHashSet<String> copy = new LinkedHashSet<>();
if (values != null) {
@@ -4666,6 +4819,9 @@ public final class ExternalDataPackPipeline {
Set<String> resolvedLocateStructures,
int syntheticStructureSets,
Set<String> 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<String> resolvedLocateStructures,
int syntheticStructureSets,
Set<String> projectedStructureKeys
Set<String> 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<String> 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<ProjectionOutputAsset> assets,
Set<String> resolvedLocateStructures,
int syntheticStructureSets,
Set<String> projectedStructureKeys
Set<String> 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);
}
}

View File

@@ -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;

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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<IrisBiome> biomeLoader;
private ResourceLoader<IrisLootTable> lootLoader;
private ResourceLoader<IrisRegion> regionLoader;
@@ -73,7 +69,6 @@ public class IrisData implements ExclusionStrategy, TypeAdapterFactory {
private ResourceLoader<IrisObject> objectLoader;
private ResourceLoader<IrisMatterObject> matterLoader;
private ResourceLoader<IrisImage> imageLoader;
private ResourceLoader<IrisScript> scriptLoader;
private ResourceLoader<IrisMatterObject> matterObjectLoader;
private KMap<String, KList<String>> 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 <rainbow>" + 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 <rainbow>" + 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<T>) new MatterObjectResourceLoader(dataFolder, this, rr.getFolderName(),
rr.getTypeName());
} else if (registrant.equals(IrisScript.class)) {
r = (ResourceLoader<T>) new ScriptResourceLoader(dataFolder, this, rr.getFolderName(),
rr.getTypeName());
} else if (registrant.equals(IrisImage.class)) {
r = (ResourceLoader<T>) 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();

View File

@@ -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<String> preprocessors = new KList<>();
@EqualsAndHashCode.Exclude
private transient IrisData loader;

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<IrisScript> {
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<String> keys = new HashSet<>();
for (File i : getFolders()) {
if (i.isDirectory()) {
keys.addAll(getKeysInDirectory(i));
}
}
possibleKeys = keys.toArray(new String[0]);
return possibleKeys;
}
private Set<String> getKeysInDirectory(File directory) {
Set<String> 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<String> 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<String> 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);
}
}

View File

@@ -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<Plate> 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<CompletableFuture<Void>> futures = new ArrayList<>(cache.size());
for (Plate plate : cache.values()) {
if (!plate.dirty) {
continue;
}
futures.add(CompletableFuture.runAsync(() -> writePlate(plate), DISPATCHER));
}
for (CompletableFuture<Void> future : futures) {
future.join();
}
}
@Override
public void trim(long unloadDuration) {
if (cache.isEmpty()) {
return;
}
long threshold = System.currentTimeMillis() - unloadDuration;
List<CompletableFuture<Void>> futures = new ArrayList<>(cache.size());
Iterator<Plate> 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<Void> 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<CompletableFuture<Void>> futures = new ArrayList<>(cache.size() - maxSize);
while (cache.size() > maxSize) {
Plate evicted = cache.removeLast();
futures.add(CompletableFuture.runAsync(() -> writePlate(evicted), DISPATCHER));
}
for (CompletableFuture<Void> 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);
}
}

View File

@@ -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) {

View File

@@ -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']");

View File

@@ -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<Task, ValueWithDiagnostics<Mode>> results = Collections.emptyMap();
private static Map<String, String> context = Collections.emptyMap();
private static Map<String, List<String>> attachment = Collections.emptyMap();
private static Mode mode = Mode.STABLE;
private static int count = 0;
private IrisSafeguard() {
}
public static void execute() {
List<Task> tasks = Tasks.getTasks();
LinkedHashMap<Task, ValueWithDiagnostics<Mode>> resultValues = new LinkedHashMap<>(tasks.size());
LinkedHashMap<String, String> contextValues = new LinkedHashMap<>(tasks.size());
LinkedHashMap<String, List<String>> attachmentValues = new LinkedHashMap<>(tasks.size());
Mode currentMode = Mode.STABLE;
int issueCount = 0;
for (Task task : tasks) {
ValueWithDiagnostics<Mode> 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<String> 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<String, String> asContext() {
return context;
}
public static Map<String, List<String>> 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<Mode> 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("");
}
}

View File

@@ -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;
}
}

View File

@@ -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<String> logger;
Logger(Consumer<String> 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);
}
}
}

View File

@@ -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<Mode> run();
public static Task of(String id, String name, Supplier<ValueWithDiagnostics<Mode>> action) {
return new Task(id, name) {
@Override
public ValueWithDiagnostics<Mode> run() {
return action.get();
}
};
}
public static Task of(String id, Supplier<ValueWithDiagnostics<Mode>> action) {
return new Task(id) {
@Override
public ValueWithDiagnostics<Mode> run() {
return action.get();
}
};
}
}

View File

@@ -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<String> plugins = new HashSet<>(Set.of("dynmap", "Stratos"));
plugins.removeIf(name -> server().getPluginManager().getPlugin(name) == null);
if (plugins.isEmpty()) {
return withDiagnostics(Mode.STABLE);
}
List<Diagnostic> 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<String> 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<String> 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<Task> TASKS = List.of(
MEMORY,
INCOMPATIBILITIES,
SOFTWARE,
VERSION,
INJECTION,
DIMENSION_TYPES,
DISK_SPACE,
JAVA
);
private Tasks() {
}
public static List<Task> 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<Diagnostic> diagnostics, Diagnostic... values) {
for (Diagnostic value : values) {
diagnostics.add(value);
}
}
private static ValueWithDiagnostics<Mode> withDiagnostics(Mode mode, Diagnostic... diagnostics) {
return new ValueWithDiagnostics<>(mode, diagnostics);
}
private static ValueWithDiagnostics<Mode> withDiagnostics(Mode mode, List<Diagnostic> diagnostics) {
return new ValueWithDiagnostics<>(mode, diagnostics);
}
}

View File

@@ -0,0 +1,40 @@
package art.arcane.iris.core.safeguard.task;
import java.util.List;
public class ValueWithDiagnostics<T> {
private final T value;
private final List<Diagnostic> diagnostics;
public ValueWithDiagnostics(T value, List<Diagnostic> 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<Diagnostic> 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);
}
}
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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();
}
}

View File

@@ -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<Long> 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()

View File

@@ -82,7 +82,6 @@ public final class IrisNoisemapPrebakePipeline {
"ravines",
"mods",
"expressions",
"scripts",
"images",
"snippet"
);

View File

@@ -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) {

View File

@@ -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<String, Set<Long>> REGEN_GENERATED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Set<Long>> REGEN_CLEARED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Set<Long>> REGEN_PLANNED_CHUNKS_BY_PASS = new ConcurrentHashMap<>();
ConcurrentHashMap<String, Long> REGEN_PASS_TOUCHED_MS = new ConcurrentHashMap<>();
Engine getEngine();
Mantle<Matter> getMantle();
int getRadius();
int getRealRadius();
List<Pair<List<MantleComponent>, 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<Long> clearedChunks = optimizedRegen ? getRegenPassSet(REGEN_CLEARED_CHUNKS_BY_PASS, regenPassKey) : new HashSet<>();
Set<Long> 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<List<MantleComponent>, 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<Long> 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<CompletableFuture<Void>> 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<Matter> 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<Matter> 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<Void> 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<Long> getRegenPassSet(ConcurrentHashMap<String, Set<Long>> 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<Map.Entry<String, Long>> iterator = REGEN_PASS_TOUCHED_MS.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Long> 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);
}
}
}

View File

@@ -78,7 +78,6 @@ public class IrisDimension extends IrisRegistrant {
private final transient AtomicCache<Double> cosr = new AtomicCache<>();
private final transient AtomicCache<Double> rad = new AtomicCache<>();
private final transient AtomicCache<Boolean> featuresUsed = new AtomicCache<>();
private final transient AtomicCache<KMap<String, KList<String>>> cachedPreProcessors = new AtomicCache<>();
private final transient AtomicCache<Map<String, IrisDimensionCarvingEntry>> 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<MantleFlag> disabledComponents = new KList<>();
@Desc("A list of globally applied pre-processors")
@ArrayType(type = IrisPreProcessors.class)
private KList<IrisPreProcessors> 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<String> 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<String> 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<String> chunkUpdateScripts = new KList<>();
@RegistryListFunction(ComponentFlagFunction.class)
@ArrayType(type = String.class)
@Desc("Collection of disabled components")
private KList<MantleFlag> disabledComponents = new KList<>();
public int getMaxHeight() {
return (int) getDimensionHeight().getMax();
@@ -398,17 +382,6 @@ public class IrisDimension extends IrisRegistrant {
return r;
}
public KList<String> getPreProcessors(String type) {
return cachedPreProcessors.aquire(() -> {
KMap<String, KList<String>> 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:

View File

@@ -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);
}
}

View File

@@ -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<String> 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<IrisCommand> 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));

View File

@@ -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<IrisExternalDatapackStructureSetAlias> structureSetAliases = new KList<>();
@ArrayType(type = IrisExternalDatapackTemplateAlias.class, min = 1)
@Desc("Optional template location alias mappings applied while projecting template pools")
private KList<IrisExternalDatapackTemplateAlias> templateAliases = new KList<>();
@ArrayType(type = IrisExternalDatapackStructurePatch.class, min = 1)
@Desc("Structure placement patches applied when this external datapack is projected")
private KList<IrisExternalDatapackStructurePatch> structurePatches = new KList<>();

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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<String> scripts = new KList<>();
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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) {
}
}

View File

@@ -72,9 +72,4 @@ public class IrisVanillaLootTable extends IrisLootTable {
public IrisData getLoader() {
throw new UnsupportedOperationException("VanillaLootTables do not have a loader");
}
@Override
public KList<String> getPreprocessors() {
return new KList<>();
}
}

View File

@@ -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);
}

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<IrisScript> {
public ScriptHandler() {
super(IrisScript.class, false);
}
@Override
public String getRandomDefault() {
return "script";
}
}

View File

@@ -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<Double> height;
private final ChunkedDataCache<IrisBiome> biome;
private final ChunkedDataCache<IrisBiome> cave;
private final ChunkedDataCache<BlockData> rock;
private final ChunkedDataCache<BlockData> fluid;
private final ChunkedDataCache<IrisRegion> 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<CompletableFuture<Void>> 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<Void> task : tasks) {
task.join();
}
}
}
public int getX() {
return x;
}
public int getZ() {
return z;
}
public ChunkedDataCache<Double> getHeight() {
return height;
}
public ChunkedDataCache<IrisBiome> getBiome() {
return biome;
}
public ChunkedDataCache<IrisBiome> getCave() {
return cave;
}
public ChunkedDataCache<BlockData> getRock() {
return rock;
}
public ChunkedDataCache<BlockData> getFluid() {
return fluid;
}
public ChunkedDataCache<IrisRegion> getRegion() {
return region;
}
}

View File

@@ -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<T> {
private final int x;
private final int z;
private final ProceduralStream<T> stream;
private final boolean cache;
private final Object[] data;
@BlockCoordinates
public ChunkedDataCache(ProceduralStream<T> stream, int x, int z) {
this(stream, x, z, true);
}
@BlockCoordinates
public ChunkedDataCache(ProceduralStream<T> 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<CompletableFuture<Void>> 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<Void> 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);
}
}

View File

@@ -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<Pair<Int, Int>, 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<Region?>? = 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) })
}
}
}

View File

@@ -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<Task, ValueWithDiagnostics<Mode>> = emptyMap()
private var context: Map<String, String> = emptyMap()
private var attachment: Map<String, List<String>> = emptyMap()
private var mode = Mode.STABLE
private var count = 0
@JvmStatic
fun execute() {
val results = LinkedHashMap<Task, ValueWithDiagnostics<Mode>>(tasks.size)
val context = LinkedHashMap<String, String>(tasks.size)
val attachment = LinkedHashMap<String, List<String>>(tasks.size)
var mode = Mode.STABLE
var count = 0
for (task in tasks) {
var result: ValueWithDiagnostics<Mode>
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("")
}
}

View File

@@ -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
}
}

View File

@@ -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<Mode>
companion object {
fun of(id: String, name: String = id, action: () -> ValueWithDiagnostics<Mode>) = object : Task(id, name) {
override fun run() = action()
}
fun of(id: String, action: () -> ValueWithDiagnostics<Mode>) = object : Task(id) {
override fun run() = action()
}
fun task(action: () -> ValueWithDiagnostics<Mode>) = PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, Task>> { _, _ ->
ReadOnlyProperty { _, property -> of(property.name, action) }
}
}
}

View File

@@ -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<Diagnostic>()
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<String> = 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 <T> MutableList<T>.addAll(vararg values: T) = values.forEach(this::add)
fun task(action: () -> ValueWithDiagnostics<Mode>) = PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, Task>> { _, _ ->
ReadOnlyProperty { _, property -> of(property.name, action) }
}

View File

@@ -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<out T>(
val value: T,
val diagnostics: List<Diagnostic>
) {
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> T.withDiagnostics(vararg diagnostics: Diagnostic) = ValueWithDiagnostics(this, diagnostics.toList())
fun <T> T.withDiagnostics(diagnostics: List<Diagnostic>) = ValueWithDiagnostics(this, diagnostics)

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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<String, Any?>): Map<String, Any?> {
return mapOf(
"data" to data,
"engine" to this,
"seed" to seedManager.seed,
"dimension" to dimension,
*values,
)
}
private fun Engine.parameters(vararg values: Pair<String, Any?>): Map<String, Any?> {
return limitedParameters(
"complex" to complex,
"biome" to BiomeLookup(::getSurfaceBiome),
*values,
)
}
}

View File

@@ -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<String, Any?>): Map<String, Any?> {
return mapOf(
"data" to this,
*values,
)
}
}

View File

@@ -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<String, KMap<KClass<*>, ResultWithDiagnostics<Script>>>({ _ -> KMap() }, 1024L)
protected val runner = ScriptRunner(baseDir, parent)
override fun execute(
script: String
) = execute(script, SimpleScript::class.java, null)
override fun execute(
script: String,
type: Class<*>,
vars: Map<String, Any?>?
) {
Iris.debug("Execute Script (void) " + C.DARK_GREEN + script)
evaluate0(script, type.kotlin, vars)
}
override fun evaluate(
script: String
): Any? = evaluate(script, SimpleScript::class.java, null)
override fun evaluate(
script: String,
type: Class<*>,
vars: Map<String, Any?>?
): Any? {
Iris.debug("Execute Script (for result) " + C.DARK_GREEN + script)
return evaluate0(script, type.kotlin, vars)
}
protected open fun compile(script: String, type: KClass<*>) =
compileCache.get(script)
.computeIfAbsent(type) { _ -> runner.compile(type, script) }
.valueOrThrow("Failed to compile script")
private fun evaluate0(name: String, type: KClass<*>, properties: Map<String, Any?>? = null): Any? {
val current = Thread.currentThread()
val loader = current.contextClassLoader
current.contextClassLoader = this.javaClass.classLoader
try {
return compile(name, type)
.evaluate(properties)
.valueOrThrow("Failed to evaluate script")
.value()
} catch (e: Throwable) {
e.printStackTrace()
}
current.contextClassLoader = loader
return null
}
override fun configureProject() {
runner.baseDir.mkdirs()
val libs = listOf(javaClass.classLoader.classpath, KotlinScript::class.java.classLoader.classpath)
.flatMap { it }
.sortedBy { it.absolutePath }
.toMutableList()
File(runner.baseDir, "build.gradle.kts")
.updateClasspath(libs)
}
companion object {
private const val CLASSPATH = "val classpath = mapOf("
private fun File.updateClasspath(classpath: List<File>) {
val test = if (exists()) readLines() else BASE_GRADLE
writeText(test.updateClasspath(classpath))
}
private fun List<String>.updateClasspath(classpath: List<File>): String {
val components = linkedMapOf<String, FileComponents>()
classpath.forEach {
val parts = it.canonicalPath.split(File.separatorChar)
if (parts.size <= 1) {
Iris.error("Invalid classpath entry: $it")
return@forEach
}
var parent = components.computeIfAbsent(parts[0]) { FileComponents(parts[0], true) }
for (part in parts.subList(1, parts.size)) {
parent = parent.append(part)
}
}
val mapped = components.values.associate {
var current = it
val root = buildString {
while (current.children.size == 1) {
append(current.segment)
append(File.separatorChar)
current = current.children.first()
}
append(current.segment)
append(File.separatorChar)
}.escapedPath
val result = mutableSetOf<String>()
val queue = ArrayDeque<Pair<String?, Collection<FileComponents>>>()
queue.add(null to current.children)
while (queue.isNotEmpty()) {
val pair = queue.removeFirst()
val path = pair.first?.let { p -> p + File.separatorChar } ?: ""
pair.second.forEach { child ->
val path = path + child.segment
if (child.children.isEmpty()) result.add(path.escapedPath)
else queue.add(path to child.children)
}
}
root to result
}
val classpath = mapped.entries.joinToString(",", CLASSPATH, ")") {
"\"${it.key}\" to setOf(${it.value.joinToString(", ") { f -> "\"$f\"" }})"
}
val mod = toMutableList()
val index = indexOfFirst { it.startsWith(CLASSPATH) }
if (index == -1) {
mod.clear()
mod.addAll(BASE_GRADLE)
}
mod[if (index == -1) 0 else index] = classpath
return mod.joinToString("\n")
}
private val String.escapedPath
get() = replace("\\", "\\\\").replace("\"", "\\\"")
private const val ARTIFACT_ID = $$"local:${it.substringBeforeLast(\".jar\")}:1.0.0"
private val BASE_GRADLE = """
val classpath = mapOf()
plugins {
kotlin("jvm") version("2.2.0")
}
repositories {
mavenCentral()
flatDir {
dirs(classpath.keys)
}
}
val script by configurations.creating
configurations.compileOnly { extendsFrom(script) }
configurations.kotlinScriptDef { extendsFrom(script) }
configurations.kotlinScriptDefExtensions { extendsFrom(script) }
configurations.kotlinCompilerClasspath { extendsFrom(script) }
configurations.kotlinCompilerPluginClasspath { extendsFrom(script) }
dependencies {
classpath.values.flatMap { it }.forEach { script("$ARTIFACT_ID") }
}""".trimIndent().split("\n")
}
}

View File

@@ -1,64 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner
import java.util.concurrent.locks.ReentrantLock
import kotlin.reflect.KClass
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.createEvaluationConfigurationFromTemplate
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
data class CachedScript(
private val base: CompiledScript,
private val host: BasicJvmScriptingHost,
private val hostConfig: ScriptingHostConfiguration
) : Script, CompiledScript {
private val scripts = base.otherScripts.map { CachedScript(it, host, hostConfig) }
private val evalConfig = createEvaluationConfiguration()
private val lock = ReentrantLock()
@Volatile
private var value: ResultWithDiagnostics<KClass<*>>? = null
override val otherScripts: List<CompiledScript>
get() = scripts
override val sourceLocationId: String?
get() = base.sourceLocationId
override val compilationConfiguration: ScriptCompilationConfiguration
get() = base.compilationConfiguration
override val resultField: Pair<String, KotlinType>?
get() = base.resultField
override suspend fun getClass(scriptEvaluationConfiguration: ScriptEvaluationConfiguration?) = value ?: run {
lock.lock()
try {
value ?: base.getClass(scriptEvaluationConfiguration).also { value = it }
} finally {
lock.unlock()
}
}
override fun evaluate(properties: Map<String, Any?>?) = host.runInCoroutineContext {
host.evaluator(this, createEvaluationConfiguration(properties))
}
private fun createEvaluationConfiguration(properties: Map<String, Any?>?): ScriptEvaluationConfiguration {
if (properties == null || properties.isEmpty())
return evalConfig
return evalConfig.with {
providedProperties(properties)
}
}
private fun createEvaluationConfiguration(): ScriptEvaluationConfiguration {
val type = compilationConfiguration[ScriptCompilationConfiguration.baseClass]?.fromClass!!
return createEvaluationConfigurationFromTemplate(
KotlinType(type),
hostConfig,
type)
}
}

View File

@@ -1,8 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner
import kotlin.script.experimental.api.EvaluationResult
import kotlin.script.experimental.api.ResultWithDiagnostics
interface Script {
fun evaluate(properties: Map<String, Any?>?): ResultWithDiagnostics<EvaluationResult>
}

View File

@@ -1,63 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner
import art.arcane.iris.core.scripting.kotlin.base.SimpleScript
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.KClass
import kotlin.script.experimental.annotations.KotlinScript
import kotlin.script.experimental.api.KotlinType
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptCompilationConfiguration
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.host.FileScriptSource
import kotlin.script.experimental.host.createCompilationConfigurationFromTemplate
import kotlin.script.experimental.host.toScriptSource
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.dependenciesFromClassContext
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.BasicJvmScriptingHost
class ScriptRunner(
val baseDir: File,
parent: ScriptRunner? = null,
private val host: BasicJvmScriptingHost = BasicJvmScriptingHost()
) {
private val configs = ConcurrentHashMap<KClass<*>, ScriptCompilationConfiguration>()
private val hostConfig = host.baseHostConfiguration.withDefaultsFrom(defaultJvmScriptingHostConfiguration)
private val sharedClassLoader: SharedClassLoader = parent?.sharedClassLoader ?: SharedClassLoader()
private val resolver = createResolver(baseDir)
fun compile(type: KClass<*>, raw: String, name: String? = null) = compile(type, raw.toScriptSource(name))
fun compile(type: KClass<*>, file: File, preloaded: String? = null) = compile(type, FileScriptSource(file, preloaded))
private fun compile(
type: KClass<*>,
code: SourceCode
): ResultWithDiagnostics<Script> = host.runInCoroutineContext {
host.compiler(code, configs.computeIfAbsent(type, ::createConfig))
.map { CachedScript(it, host, hostConfig) }
}
private fun createConfig(type: KClass<*>) = createCompilationConfigurationFromTemplate(
KotlinType(type),
hostConfig,
type
) {
dependencyResolver(resolver)
packDirectory(baseDir)
sharedClassloader(sharedClassLoader)
server(true)
if (SimpleScript::class.java.isAssignableFrom(type.java))
return@createCompilationConfigurationFromTemplate
jvm {
dependenciesFromClassContext(type, wholeClasspath = true)
dependenciesFromClassContext(this::class, wholeClasspath = true)
dependenciesFromClassContext(KotlinScript::class, wholeClasspath = true)
}
configure()
}
}

View File

@@ -1,219 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner
import art.arcane.iris.core.scripting.kotlin.runner.resolver.CompoundDependenciesResolver
import kotlinx.coroutines.runBlocking
import java.io.File
import java.net.URLClassLoader
import kotlin.script.experimental.api.*
import kotlin.script.experimental.dependencies.DependsOn
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.Repository
import kotlin.script.experimental.dependencies.addRepository
import kotlin.script.experimental.dependencies.impl.SimpleExternalDependenciesResolverOptionsParser
import kotlin.script.experimental.jvm.JvmDependency
import kotlin.script.experimental.jvm.updateClasspath
import kotlin.script.experimental.jvm.util.classpathFromClassloader
import kotlin.script.experimental.util.PropertiesCollection
import kotlin.script.experimental.util.filterByAnnotationType
internal fun <T, R> ResultWithDiagnostics<T>.map(transformer: (T) -> R): ResultWithDiagnostics<R> = when (this) {
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(transformer(value), reports)
is ResultWithDiagnostics.Failure -> this
}
internal fun EvaluationResult.value() = returnValue.value()
internal fun ResultValue.value(): Any? =
when (this) {
is ResultValue.Value -> value
is ResultValue.Error -> throw error
else -> null
}
internal class FileComponents(
val segment: String,
val root: Boolean = false,
) {
private val children0 = mutableMapOf<String, FileComponents>()
val children get() = children0.values
fun append(segment: String): FileComponents =
children0.computeIfAbsent(segment) { FileComponents(segment) }
override fun hashCode(): Int {
var result = segment.hashCode()
result = 31 * result + children0.hashCode()
return result
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is FileComponents) return false
if (segment != other.segment) return false
if (children0 != other.children0) return false
return true
}
}
private val workDir = File(".").normalize()
internal fun createResolver(baseDir: File = workDir) = CompoundDependenciesResolver(baseDir)
private val resolver = createResolver()
private val loader = SharedClassLoader()
private fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> = runCatching {
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
?: return context.compilationConfiguration.asSuccess()
val reports = mutableListOf<ScriptDiagnostic>()
val loader = context.compilationConfiguration[ScriptCompilationConfiguration.sharedClassloader]
val resolver = context.compilationConfiguration[ScriptCompilationConfiguration.dependencyResolver] ?: resolver
val server = context.compilationConfiguration[ScriptCompilationConfiguration.server] ?: false
context.compilationConfiguration[ScriptCompilationConfiguration.packDirectory]
?.addPack(resolver)
?: context.script.locationId
?.let(::File)
?.takeIf { it.exists() }
?.run {
val location = SourceCode.LocationWithId(context.script.locationId!!, SourceCode.Location(SourceCode.Position(0, 0)))
val parts = normalize().absolutePath.split(File.separatorChar)
for (i in parts.size - 1 downTo 1) {
if (parts[i] != "scripts") continue
val pack = File(parts.subList(0, i).joinToString(File.separator))
if (!File(pack, "dimensions${File.separator}${parts[i - 1]}.json").exists())
continue
pack.addPack(resolver)
reports.add(ScriptDiagnostic(
ScriptDiagnostic.unspecifiedInfo,
"Adding pack \"$pack\"",
ScriptDiagnostic.Severity.INFO,
location
))
}
}
return runBlocking {
resolver.resolveDependencies(annotations, server)
}.onSuccess { classpath ->
context.compilationConfiguration.with {
if (!server) {
updateClasspath(classpath.map { it.first })
return@with
}
val newClasspath = classpath.filterNewClasspath(this[ScriptCompilationConfiguration.dependencies])
?: return@with
val shared = classpath.mapNotNull { p -> p.first.takeIf { p.second } }
if (shared.isNotEmpty()) loader!!.addFiles(shared)
val regular = newClasspath
.map { p -> p.first }
.let { JvmDependency(it) }
ScriptCompilationConfiguration.dependencies.append(regular)
}.asSuccess()
}.appendReports(reports)
}.getOrElse { ResultWithDiagnostics.Failure(it.asDiagnostics()) }
private fun Collection<Pair<File, Boolean>>.filterNewClasspath(known: Collection<ScriptDependency>?): List<Pair<File, Boolean>>? {
if (isEmpty()) return null
val knownClasspath = known?.flatMapTo(hashSetOf()) {
(it as? JvmDependency)?.classpath ?: emptyList()
}
return filterNot { knownClasspath?.contains(it.first) == true }.takeIf { it.isNotEmpty() }
}
private suspend fun ExternalDependenciesResolver.resolveDependencies(
annotations: Iterable<ScriptSourceAnnotation<*>>,
server: Boolean
): ResultWithDiagnostics<List<Pair<File, Boolean>>> {
val reports = mutableListOf<ScriptDiagnostic>()
annotations.forEach { (annotation, locationWithId) ->
when (annotation) {
is Repository -> {
val options = SimpleExternalDependenciesResolverOptionsParser(*annotation.options, locationWithId = locationWithId)
.valueOr { return it }
for (coordinates in annotation.repositoriesCoordinates) {
val added = addRepository(coordinates, options, locationWithId)
.also { reports.addAll(it.reports) }
.valueOr { return it }
if (!added)
return reports + makeFailureResult(
"Unrecognized repository coordinates: $coordinates",
locationWithId = locationWithId
)
}
}
is DependsOn -> {}
else -> return reports + makeFailureResult("Unknown annotation ${annotation.javaClass}", locationWithId = locationWithId)
}
}
return reports + annotations.filterByAnnotationType<DependsOn>()
.flatMapSuccess { (annotation, locationWithId) ->
SimpleExternalDependenciesResolverOptionsParser(
*annotation.options,
locationWithId = locationWithId
).onSuccess { options ->
if (!server && true == options.server) {
return@onSuccess listOf<Pair<File, Boolean>>().asSuccess()
}
annotation.artifactsCoordinates.asIterable().flatMapSuccess { artifactCoordinates ->
resolve(artifactCoordinates, options, locationWithId)
}.map { files -> files.map { it to (options.shared ?: false) } }
}
}
}
private val ExternalDependenciesResolver.Options.shared get() = flag("shared")
private val ExternalDependenciesResolver.Options.server get() = flag("server")
internal val ClassLoader.classpath get() = classpathFromClassloader(this) ?: emptyList()
internal fun <R> ResultWithDiagnostics<R>.valueOrThrow(message: CharSequence): R = valueOr {
throw RuntimeException(it.reports.joinToString("\n", "$message\n") { r -> r.render(withStackTrace = true) })
}
internal val ScriptCompilationConfigurationKeys.dependencyResolver by PropertiesCollection.key(resolver, true)
internal val ScriptCompilationConfigurationKeys.packDirectory by PropertiesCollection.key<File>(null, true)
internal val ScriptCompilationConfigurationKeys.sharedClassloader by PropertiesCollection.key<SharedClassLoader>(null, true)
internal val ScriptCompilationConfigurationKeys.server by PropertiesCollection.key(false, isTransient = true)
private fun File.addPack(resolver: CompoundDependenciesResolver) = resolver.addPack(this)
private fun <R> ResultWithDiagnostics<R>.appendReports(reports : Collection<ScriptDiagnostic>) =
if (reports.isEmpty()) this
else when (this) {
is ResultWithDiagnostics.Success -> ResultWithDiagnostics.Success(value, this.reports + reports)
is ResultWithDiagnostics.Failure -> ResultWithDiagnostics.Failure(this.reports + reports)
}
internal class SharedClassLoader(parent: ClassLoader = SharedClassLoader::class.java.classLoader) : URLClassLoader(arrayOf(), parent) {
val dependency get() = JvmDependency(classpath)
fun addFiles(files: List<File>) {
files.forEach { addURL(it.toURI().toURL()) }
}
}
internal fun ScriptCompilationConfiguration.Builder.configure() {
refineConfiguration {
beforeParsing { context -> try {
context.compilationConfiguration.with {
if (context.compilationConfiguration[ScriptCompilationConfiguration.server] ?: false) {
val sharedClasspath = this[ScriptCompilationConfiguration.sharedClassloader]!!.classpath
if (sharedClasspath.isNotEmpty()) {
ScriptCompilationConfiguration.dependencies.append(JvmDependency(sharedClasspath))
}
}
}.asSuccess()
} catch (e: Throwable) {
ResultWithDiagnostics.Failure(e.asDiagnostics())
}}
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
}
}

View File

@@ -1,118 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner.resolver
import java.io.File
import kotlin.collections.component1
import kotlin.collections.component2
import kotlin.collections.iterator
import kotlin.collections.set
import kotlin.script.experimental.api.IterableResultsCollector
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.ScriptDiagnostic
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asErrorDiagnostics
import kotlin.script.experimental.api.asSuccess
import kotlin.script.experimental.dependencies.ArtifactWithLocation
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult
class CompoundDependenciesResolver(
baseDir: File
) : DependenciesResolver {
private val resolvers = listOf(FileDependenciesResolver(baseDir), LocalMavenDependenciesResolver())
override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean {
return resolvers.any { it.acceptsRepository(repositoryCoordinates) }
}
override fun acceptsArtifact(artifactCoordinates: String): Boolean {
return resolvers.any { it.acceptsArtifact(artifactCoordinates) }
}
override fun addRepository(
repositoryCoordinates: RepositoryCoordinates,
options: ExternalDependenciesResolver.Options,
sourceCodeLocation: SourceCode.LocationWithId?
): ResultWithDiagnostics<Boolean> {
var success = false
var repositoryAdded = false
val reports = mutableListOf<ScriptDiagnostic>()
for (resolver in resolvers) {
when (val result = resolver.addRepository(repositoryCoordinates, options, sourceCodeLocation)) {
is ResultWithDiagnostics.Success -> {
success = true
repositoryAdded = repositoryAdded || result.value
reports.addAll(result.reports)
}
is ResultWithDiagnostics.Failure -> reports.addAll(result.reports)
}
}
return when {
success -> repositoryAdded.asSuccess(reports)
reports.isEmpty() -> makeResolveFailureResult(
"No dependency resolver found that recognizes the repository coordinates '$repositoryCoordinates'",
sourceCodeLocation
)
else -> ResultWithDiagnostics.Failure(reports)
}
}
override suspend fun resolve(
artifactsWithLocations: List<ArtifactWithLocation>,
options: ExternalDependenciesResolver.Options
): ResultWithDiagnostics<List<File>> {
val resultsCollector = IterableResultsCollector<File>()
val artifactToResolverIndex = mutableMapOf<ArtifactWithLocation, Int>().apply {
for (artifactWithLocation in artifactsWithLocations) {
put(artifactWithLocation, -1)
}
}
while (artifactToResolverIndex.isNotEmpty()) {
val resolverGroups = mutableMapOf<Int, MutableList<ArtifactWithLocation>>()
for ((artifactWithLocation, resolverIndex) in artifactToResolverIndex) {
val (artifact, sourceCodeLocation) = artifactWithLocation
var currentIndex = resolverIndex + 1
while (currentIndex < resolvers.size) {
if (resolvers[currentIndex].acceptsArtifact(artifact)) break
++currentIndex
}
if (currentIndex == resolvers.size) {
if (resolverIndex == -1) {
resultsCollector.addDiagnostic(
"No suitable dependency resolver found for artifact '$artifact'"
.asErrorDiagnostics(locationWithId = sourceCodeLocation)
)
}
} else {
resolverGroups
.getOrPut(currentIndex) { mutableListOf() }
.add(artifactWithLocation)
}
}
artifactToResolverIndex.clear()
for ((resolverIndex, artifacts) in resolverGroups) {
val resolver = resolvers[resolverIndex]
val resolveResult = resolver.resolve(artifacts, options)
resultsCollector.add(resolveResult)
if (resolveResult.reports.isNotEmpty()) {
for (artifact in artifacts) {
artifactToResolverIndex[artifact] = resolverIndex
}
}
}
}
return resultsCollector.getResult()
}
override fun addPack(directory: File) {
resolvers.forEach { it.addPack(directory) }
}
}

View File

@@ -1,8 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner.resolver
import java.io.File
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
interface DependenciesResolver : ExternalDependenciesResolver {
fun addPack(directory: File)
}

View File

@@ -1,69 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner.resolver
import java.io.File
import java.util.concurrent.ConcurrentHashMap
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.api.asSuccess
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.impl.makeResolveFailureResult
import kotlin.script.experimental.dependencies.impl.toRepositoryUrlOrNull
class FileDependenciesResolver(
private val baseDir: File,
) : DependenciesResolver {
private val localRepos = ConcurrentHashMap.newKeySet<File>(1).also { it.add(baseDir) }
private fun String.toRepositoryFileOrNull(): File? =
File(baseDir, this).takeIf { it.exists() && it.isDirectory }
private fun RepositoryCoordinates.toFilePath() =
(this.toRepositoryUrlOrNull()?.takeIf { it.protocol == "file" }?.path ?: string).toRepositoryFileOrNull()
override fun addRepository(
repositoryCoordinates: RepositoryCoordinates,
options: ExternalDependenciesResolver.Options,
sourceCodeLocation: SourceCode.LocationWithId?
): ResultWithDiagnostics<Boolean> {
if (!acceptsRepository(repositoryCoordinates)) return false.asSuccess()
val repoDir = repositoryCoordinates.toFilePath()
?: return makeResolveFailureResult("Invalid repository location: '${repositoryCoordinates}'", sourceCodeLocation)
localRepos.add(repoDir)
return true.asSuccess()
}
override suspend fun resolve(
artifactCoordinates: String,
options: ExternalDependenciesResolver.Options,
sourceCodeLocation: SourceCode.LocationWithId?
): ResultWithDiagnostics<List<File>> {
if (!acceptsArtifact(artifactCoordinates)) throw IllegalArgumentException("Path is invalid")
val messages = mutableListOf<String>()
for (repo in localRepos) {
// TODO: add coordinates and wildcard matching
val file = File(repo, artifactCoordinates)
when {
!file.exists() -> messages.add("File '$file' not found")
!file.isFile && !file.isDirectory -> messages.add("Path '$file' is neither file nor directory")
else -> return ResultWithDiagnostics.Success(listOf(file))
}
}
return makeResolveFailureResult(messages.joinToString("; "), sourceCodeLocation)
}
override fun acceptsArtifact(artifactCoordinates: String) =
!artifactCoordinates.isBlank() // TODO: make check stronger, e.g. using NIO's Path
override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates): Boolean = repositoryCoordinates.toFilePath() != null
override fun addPack(directory: File) {
localRepos.add(directory)
}
}

View File

@@ -1,85 +0,0 @@
package art.arcane.iris.core.scripting.kotlin.runner.resolver
import art.arcane.volmlib.util.io.IO
import org.dom4j.Document
import org.dom4j.DocumentFactory
import org.dom4j.io.SAXReader
import java.io.File
import kotlin.script.experimental.api.ResultWithDiagnostics
import kotlin.script.experimental.api.SourceCode
import kotlin.script.experimental.dependencies.ArtifactWithLocation
import kotlin.script.experimental.dependencies.ExternalDependenciesResolver
import kotlin.script.experimental.dependencies.RepositoryCoordinates
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
class LocalMavenDependenciesResolver : DependenciesResolver {
private lateinit var localRepo: File
private val maven = MavenDependenciesResolver(true)
override fun acceptsRepository(repositoryCoordinates: RepositoryCoordinates) = maven.acceptsRepository(repositoryCoordinates)
override fun acceptsArtifact(artifactCoordinates: String) = maven.acceptsArtifact(artifactCoordinates)
override fun addRepository(
repositoryCoordinates: RepositoryCoordinates,
options: ExternalDependenciesResolver.Options,
sourceCodeLocation: SourceCode.LocationWithId?
) = maven.addRepository(repositoryCoordinates, options, sourceCodeLocation)
override suspend fun resolve(
artifactsWithLocations: List<ArtifactWithLocation>,
options: ExternalDependenciesResolver.Options
): ResultWithDiagnostics<List<File>> {
val userOld: String? = System.getProperty("org.apache.maven.user-settings")
val globalOld: String? = System.getProperty("org.apache.maven.global-settings")
try {
System.setProperty("org.apache.maven.user-settings", createSettings(userOld))
System.clearProperty("org.apache.maven.global-settings")
return maven.resolve(artifactsWithLocations, options)
} finally {
setProperty("org.apache.maven.user-settings", userOld)
setProperty("org.apache.maven.global-settings", globalOld)
}
}
private fun createSettings(user: String?): String {
val settingsFile = File(localRepo, "settings.xml")
val document = readSettings(user)
val node = document.selectSingleNode("//localRepository")
?: document.rootElement.addElement("localRepository")
if (node.text != localRepo.absolutePath) {
node.text = localRepo.absolutePath
IO.write(settingsFile, document)
}
return settingsFile.absolutePath
}
private fun readSettings(user: String?): Document {
val baseFile = user?.let(::File)?.takeIf { it.exists() } ?: File(
System.getProperty("user.home"),
".m2/settings.xml"
).takeIf { it.exists() }?.let { return SAXReader().read(it) }
return if (baseFile != null) SAXReader().read(baseFile) else DocumentFactory.getInstance().createDocument().also {
it.addElement("settings")
.addAttribute("xmlns", "http://maven.apache.org/SETTINGS/1.0.0")
.addAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance")
.addAttribute("xsi:schemaLocation", "http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd")
}
}
private fun setProperty(name: String, value: String?) {
when(value) {
null -> System.clearProperty(name)
else -> System.setProperty(name, value)
}
}
override fun addPack(directory: File) {
if (!::localRepo.isInitialized) {
localRepo = directory.resolve(".iris/m2")
}
}
}

View File

@@ -1,205 +0,0 @@
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.project.context.ChunkContext
import art.arcane.iris.util.common.misc.RegenRuntime
import art.arcane.volmlib.util.matter.Matter
import art.arcane.volmlib.util.documentation.ChunkCoordinates
import art.arcane.volmlib.util.mantle.runtime.Mantle
import art.arcane.volmlib.util.mantle.flag.MantleFlag
import art.arcane.iris.util.common.parallel.MultiBurst
import art.arcane.iris.util.project.matter.TileWrapper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.bukkit.block.data.BlockData
import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.math.min
interface MatterGenerator {
val engine: Engine
val mantle: Mantle<Matter>
val radius: Int
val realRadius: Int
val components: List<Pair<List<MantleComponent>, Int>>
@ChunkCoordinates
fun generateMatter(x: Int, z: Int, multicore: Boolean, context: ChunkContext) {
if (!engine.dimension.isUseMantle) return
val multicore = multicore || IrisSettings.get().generator.isUseMulticoreMantle
val threadName = Thread.currentThread().name
val regenThread = threadName.startsWith("Iris-Regen-")
val traceRegen = regenThread && IrisSettings.get().general.isDebug
val forceRegen = regenThread
val regenPassKey = if (forceRegen) resolveRegenPassKey(threadName) else null
val optimizedRegen = forceRegen && !IrisSettings.get().general.isDebug && regenPassKey != null
val writeRadius = if (optimizedRegen) min(radius, realRadius) else radius
val clearedChunks = if (optimizedRegen) getRegenPassSet(regenClearedChunksByPass, regenPassKey!!) else HashSet<Long>()
val plannedChunks = if (optimizedRegen) getRegenPassSet(regenPlannedChunksByPass, regenPassKey!!) else null
if (optimizedRegen) {
touchRegenPass(regenPassKey!!)
}
if (traceRegen) {
Iris.info("Regen matter start: center=$x,$z radius=$radius realRadius=$realRadius writeRadius=$writeRadius multicore=$multicore components=${components.size} optimized=$optimizedRegen passKey=${regenPassKey ?: "none"} thread=$threadName")
}
MantleWriter(engine.mantle, mantle, x, z, writeRadius, multicore).use { writer ->
for (pair in components) {
val rawPassRadius = pair.b
val passRadius = if (optimizedRegen) min(rawPassRadius, writeRadius) else rawPassRadius
val passFlags = pair.a.joinToString(",") { it.flag.toString() }
val passFlagKey = if (optimizedRegen) "$regenPassKey|$passFlags" else null
val generatedChunks = if (passFlagKey != null) getRegenPassSet(regenGeneratedChunksByPass, passFlagKey) else null
var visitedChunks = 0
var clearedCount = 0
var plannedSkipped = 0
var componentSkipped = 0
var componentForcedReset = 0
var launchedLayers = 0
var dedupSkipped = 0
if (passFlagKey != null) {
touchRegenPass(passFlagKey)
}
if (traceRegen) {
Iris.info("Regen matter pass start: center=$x,$z passRadius=$passRadius rawPassRadius=$rawPassRadius flags=[$passFlags]")
}
runBlocking {
radius(x, z, passRadius) { passX, passZ ->
visitedChunks++
val passKey = chunkKey(passX, passZ)
if (generatedChunks != null && !generatedChunks.add(passKey)) {
dedupSkipped++
return@radius
}
val mc = writer.acquireChunk(passX, passZ)
if (forceRegen) {
if (clearedChunks.add(passKey)) {
mc.deleteSlices(BlockData::class.java)
mc.deleteSlices(String::class.java)
mc.deleteSlices(TileWrapper::class.java)
mc.flag(MantleFlag.PLANNED, false)
clearedCount++
}
}
if (!forceRegen && mc.isFlagged(MantleFlag.PLANNED)) {
plannedSkipped++
return@radius
}
for (c in pair.a) {
if (!forceRegen && mc.isFlagged(c.flag)) {
componentSkipped++
continue
}
if (forceRegen && mc.isFlagged(c.flag)) {
mc.flag(c.flag, false)
componentForcedReset++
}
launchedLayers++
launch(multicore) {
mc.raiseFlagSuspend(c.flag) {
c.generateLayer(writer, passX, passZ, context)
}
}
}
}
}
if (traceRegen) {
Iris.info("Regen matter pass done: center=$x,$z passRadius=$passRadius rawPassRadius=$rawPassRadius visited=$visitedChunks cleared=$clearedCount dedupSkipped=$dedupSkipped plannedSkipped=$plannedSkipped componentSkipped=$componentSkipped componentForcedReset=$componentForcedReset launchedLayers=$launchedLayers flags=[$passFlags]")
}
}
radius(x, z, realRadius) { realX, realZ ->
val realKey = chunkKey(realX, realZ)
if (plannedChunks != null && !plannedChunks.add(realKey)) {
return@radius
}
writer.acquireChunk(realX, realZ)
.flag(MantleFlag.PLANNED, true)
}
}
if (traceRegen) {
Iris.info("Regen matter done: center=$x,$z markedRealRadius=$realRadius forceRegen=$forceRegen")
}
}
private inline fun radius(x: Int, z: Int, radius: Int, crossinline task: (Int, Int) -> Unit) {
for (i in -radius..radius) {
for (j in -radius..radius) {
task(x + i, z + j)
}
}
}
companion object {
private val dispatcher = MultiBurst.burst.dispatcher//.limitedParallelism(128, "Mantle")
private const val regenPassCacheTtlMs = 600000L
private val regenGeneratedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenClearedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenPlannedChunksByPass = ConcurrentHashMap<String, MutableSet<Long>>()
private val regenPassTouchedMs = ConcurrentHashMap<String, Long>()
private fun CoroutineScope.launch(multicore: Boolean, block: suspend CoroutineScope.() -> Unit) =
launch(if (multicore) dispatcher else EmptyCoroutineContext, block = block)
private fun chunkKey(x: Int, z: Int): Long {
return (x.toLong() shl 32) xor (z.toLong() and 0xffffffffL)
}
private fun getRegenPassSet(store: ConcurrentHashMap<String, MutableSet<Long>>, passKey: String): MutableSet<Long> {
return store.computeIfAbsent(passKey) { ConcurrentHashMap.newKeySet<Long>() }
}
private fun resolveRegenPassKey(threadName: String): String? {
val runtimeKey = RegenRuntime.getRunId()
if (!runtimeKey.isNullOrBlank()) {
return runtimeKey
}
if (!threadName.startsWith("Iris-Regen-")) {
return null
}
val suffix = threadName.substring("Iris-Regen-".length)
val lastDash = suffix.lastIndexOf('-')
if (lastDash <= 0) {
return suffix
}
return suffix.substring(0, lastDash)
}
private fun touchRegenPass(passKey: String) {
val now = System.currentTimeMillis()
regenPassTouchedMs[passKey] = now
if (regenPassTouchedMs.size <= 64) {
return
}
val iterator = regenPassTouchedMs.entries.iterator()
while (iterator.hasNext()) {
val entry = iterator.next()
if (now - entry.value <= regenPassCacheTtlMs) {
continue
}
val key = entry.key
iterator.remove()
regenGeneratedChunksByPass.remove(key)
regenClearedChunksByPass.remove(key)
regenPlannedChunksByPass.remove(key)
}
}
}
}

View File

@@ -1,35 +0,0 @@
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 kotlinx.coroutines.*
import org.bukkit.block.data.BlockData
class ChunkContext @JvmOverloads constructor(
val x: Int,
val z: Int,
c: IrisComplex,
cache: Boolean = true,
) {
val height: ChunkedDataCache<Double> = ChunkedDataCache(c.heightStream, x, z, cache)
val biome: ChunkedDataCache<IrisBiome> = ChunkedDataCache(c.trueBiomeStream, x, z, cache)
val cave: ChunkedDataCache<IrisBiome> = ChunkedDataCache(c.caveBiomeStream, x, z, cache)
val rock: ChunkedDataCache<BlockData> = ChunkedDataCache(c.rockStream, x, z, cache)
val fluid: ChunkedDataCache<BlockData> = ChunkedDataCache(c.fluidStream, x, z, cache)
val region: ChunkedDataCache<IrisRegion> = ChunkedDataCache(c.regionStream, x, z, cache)
init {
if (cache) runBlocking {
val dispatcher = MultiBurst.burst.dispatcher
launch { height.fill(dispatcher) }
launch { biome.fill(dispatcher) }
launch { cave.fill(dispatcher) }
launch { rock.fill(dispatcher) }
launch { fluid.fill(dispatcher) }
launch { region.fill(dispatcher) }
}
}
}

View File

@@ -1,48 +0,0 @@
package art.arcane.iris.util.project.context
import art.arcane.volmlib.util.documentation.BlockCoordinates
import art.arcane.iris.util.project.stream.ProceduralStream
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import kotlin.coroutines.CoroutineContext
class ChunkedDataCache<T> private constructor(
private val x: Int,
private val z: Int,
private val stream: ProceduralStream<T?>,
private val cache: Boolean
) {
private val data = arrayOfNulls<Any>(if (cache) 256 else 0)
@JvmOverloads
@BlockCoordinates
constructor(stream: ProceduralStream<T?>, x: Int, z: Int, cache: Boolean = true) : this(x, z, stream, cache)
suspend fun fill(context: CoroutineContext = Dispatchers.Default) {
if (!cache) return
supervisorScope {
for (j in 0 until 16) {
launch(context) {
val rowOffset = j * 16
val zz = (z + j).toDouble()
for (i in 0 until 16) {
data[rowOffset + i] = stream.get((x + i).toDouble(), zz)
}
}
}
}
}
@BlockCoordinates
@Suppress("UNCHECKED_CAST")
fun get(x: Int, z: Int): T? {
if (!cache) {
return stream.get((this.x + x).toDouble(), (this.z + z).toDouble())
}
val t = data[(z * 16) + x] as? T
return t ?: stream.get((this.x + x).toDouble(), (this.z + z).toDouble())
}
}

View File

@@ -39,10 +39,9 @@ byte-buddy = "1.17.6" # https://central.sonatype.com/artifact/net.bytebuddy/byte
dom4j = "2.2.0" # https://central.sonatype.com/artifact/org.dom4j/dom4j
jaxen = "2.0.0" # https://central.sonatype.com/artifact/jaxen/jaxen
# Script Engine
# Kotlin Runtime
kotlin = "2.2.0"
kotlin-coroutines = "1.10.2"
maven-core = "3.9.10"
# Third Party Integrations
nexo = "1.10.0" # https://repo.nexomc.com/#/releases/com/nexomc/nexo
@@ -90,14 +89,9 @@ byteBuddy-agent = { module = "net.bytebuddy:byte-buddy-agent", version.ref = "by
dom4j = { module = "org.dom4j:dom4j", version.ref = "dom4j" }
jaxen = { module = "jaxen:jaxen", version.ref = "jaxen" }
# Script Engine
# Kotlin Runtime
kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlin-scripting-common = { module = "org.jetbrains.kotlin:kotlin-scripting-common", version.ref = "kotlin" }
kotlin-scripting-jvm = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm", version.ref = "kotlin" }
kotlin-scripting-jvm-host = { module = "org.jetbrains.kotlin:kotlin-scripting-jvm-host", version.ref = "kotlin" }
kotlin-coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlin-coroutines" }
kotlin-scripting-dependencies-maven = { module = "org.jetbrains.kotlin:kotlin-scripting-dependencies-maven", version.ref = "kotlin" }
mavenCore = { module = "org.apache.maven:maven-core", version.ref = "maven-core" }
# Third Party Integrations
nexo = { module = "com.nexomc:nexo", version.ref = "nexo" }

72
settings.gradle Normal file
View File

@@ -0,0 +1,72 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import java.io.File
plugins {
id 'org.gradle.toolchains.foojay-resolver-convention' version '1.0.0'
}
rootProject.name = 'Iris'
boolean hasVolmLibSettings(File directory) {
new File(directory, 'settings.gradle.kts').exists() || new File(directory, 'settings.gradle').exists()
}
File resolveLocalVolmLibDirectory() {
String configuredPath = providers.gradleProperty('localVolmLibDirectory')
.orElse(providers.environmentVariable('VOLMLIB_DIR'))
.orNull
if (configuredPath != null && !configuredPath.isBlank()) {
File configuredDirectory = file(configuredPath)
if (hasVolmLibSettings(configuredDirectory)) {
return configuredDirectory
}
}
File currentDirectory = settingsDir
while (currentDirectory != null) {
File candidate = new File(currentDirectory, 'VolmLib')
if (hasVolmLibSettings(candidate)) {
return candidate
}
currentDirectory = currentDirectory.parentFile
}
null
}
boolean useLocalVolmLib = providers.gradleProperty('useLocalVolmLib')
.orElse('true')
.map { String value -> value.equalsIgnoreCase('true') }
.get()
File localVolmLibDirectory = resolveLocalVolmLibDirectory()
if (useLocalVolmLib && localVolmLibDirectory != null) {
includeBuild(localVolmLibDirectory) {
dependencySubstitution {
substitute(module('com.github.VolmitSoftware:VolmLib')).using(project(':shared'))
substitute(module('com.github.VolmitSoftware.VolmLib:shared')).using(project(':shared'))
substitute(module('com.github.VolmitSoftware.VolmLib:volmlib-shared')).using(project(':shared'))
}
}
}
include(':core', ':core:agent')
include(':nms:v1_21_R7')

View File

@@ -1,73 +0,0 @@
/*
* 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 <https://www.gnu.org/licenses/>.
*/
import java.io.File
plugins {
id ("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0"
}
rootProject.name = "Iris"
fun hasVolmLibSettings(directory: File): Boolean {
return directory.resolve("settings.gradle.kts").exists() || directory.resolve("settings.gradle").exists()
}
fun resolveLocalVolmLibDirectory(): File? {
val configuredPath: String? = providers.gradleProperty("localVolmLibDirectory")
.orElse(providers.environmentVariable("VOLMLIB_DIR"))
.orNull
if (!configuredPath.isNullOrBlank()) {
val configuredDirectory: File = file(configuredPath)
if (hasVolmLibSettings(configuredDirectory)) {
return configuredDirectory
}
}
var currentDirectory: File? = settingsDir
while (currentDirectory != null) {
val candidate: File = currentDirectory.resolve("VolmLib")
if (hasVolmLibSettings(candidate)) {
return candidate
}
currentDirectory = currentDirectory.parentFile
}
return null
}
val useLocalVolmLib: Boolean = providers.gradleProperty("useLocalVolmLib")
.orElse("true")
.map { value: String -> value.equals("true", ignoreCase = true) }
.get()
val localVolmLibDirectory: File? = resolveLocalVolmLibDirectory()
if (useLocalVolmLib && localVolmLibDirectory != null) {
includeBuild(localVolmLibDirectory) {
dependencySubstitution {
substitute(module("com.github.VolmitSoftware:VolmLib")).using(project(":shared"))
substitute(module("com.github.VolmitSoftware.VolmLib:shared")).using(project(":shared"))
substitute(module("com.github.VolmitSoftware.VolmLib:volmlib-shared")).using(project(":shared"))
}
}
}
include(":core", ":core:agent")
include(
":nms:v1_21_R7",
)