mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-04-03 06:16:19 +00:00
kts gone
This commit is contained in:
268
build.gradle
Normal file
268
build.gradle
Normal 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' }
|
||||
}
|
||||
}
|
||||
263
build.gradle.kts
263
build.gradle.kts
@@ -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
25
buildSrc/build.gradle
Normal 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')
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
162
buildSrc/src/main/java/ApiGenerator.java
Normal file
162
buildSrc/src/main/java/ApiGenerator.java
Normal 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();
|
||||
}
|
||||
}
|
||||
5
buildSrc/src/main/java/Config.java
Normal file
5
buildSrc/src/main/java/Config.java
Normal file
@@ -0,0 +1,5 @@
|
||||
public class Config {
|
||||
public int jvm = 21;
|
||||
public NMSBinding.Type type = NMSBinding.Type.DIRECT;
|
||||
public String version;
|
||||
}
|
||||
274
buildSrc/src/main/java/NMSBinding.java
Normal file
274
buildSrc/src/main/java/NMSBinding.java
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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
14
core/agent/build.gradle
Normal 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
|
||||
)
|
||||
}
|
||||
@@ -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
257
core/build.gradle
Normal 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 })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 })
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
369
core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java
vendored
Normal file
369
core/src/main/java/art/arcane/iris/core/pregenerator/cache/PregenCacheImpl.java
vendored
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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']");
|
||||
|
||||
@@ -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("");
|
||||
}
|
||||
}
|
||||
143
core/src/main/java/art/arcane/iris/core/safeguard/Mode.java
Normal file
143
core/src/main/java/art/arcane/iris/core/safeguard/Mode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -82,7 +82,6 @@ public final class IrisNoisemapPrebakePipeline {
|
||||
"ravines",
|
||||
"mods",
|
||||
"expressions",
|
||||
"scripts",
|
||||
"images",
|
||||
"snippet"
|
||||
);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<>();
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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<>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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) })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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("")
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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
72
settings.gradle
Normal 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')
|
||||
@@ -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",
|
||||
)
|
||||
Reference in New Issue
Block a user