add project configuration caching (I'm serious)

This commit is contained in:
moehreag 2024-08-04 00:11:29 +02:00
parent e54543b2b7
commit 9cccca3d8d
6 changed files with 405 additions and 22 deletions

View file

@ -126,6 +126,12 @@ class PhytotelmaPlugin : Plugin<Project> {
}
setupTasks(project)
project.afterEvaluate {
project.afterEvaluate {
ProjectStorage.write(project)
}
}
}
private fun setupTasks(project: Project) {

View file

@ -1,21 +1,74 @@
package dev.frogmc.phytotelma
import com.google.gson.FieldNamingPolicy
import com.google.gson.GsonBuilder
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonToken
import com.google.gson.stream.JsonWriter
import dev.frogmc.thyroxine.api.data.DocumentationData
import dev.frogmc.thyroxine.api.data.DocumentationData.Field
import dev.frogmc.thyroxine.api.data.DocumentationData.Method
import dev.frogmc.thyroxine.api.data.MappingBundle
import dev.frogmc.thyroxine.api.data.MappingData
import dev.frogmc.thyroxine.api.data.Member
import org.gradle.api.Project
import java.nio.file.Path
import java.util.concurrent.ConcurrentHashMap
import java.util.*
import kotlin.io.path.*
object ProjectStorage {
private val data = ConcurrentHashMap<String, ProjectData>()
private val data = WeakHashMap<String, ProjectData>()
private val gson = GsonBuilder()
.setPrettyPrinting()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(ProjectData::class.java, ProjectDataTypeAdapter())
.registerTypeAdapter(MappingBundle::class.java, MappingBundleTypeAdapter())
.create()
fun get(project: Project): ProjectData {
val id = project.getId()
if (!data.containsKey(id)) {
data[id] = ProjectData(null, null, null, null, null, null)
val cacheFile = getCacheFile(project)
val projectData = cacheFile.takeIf { it.exists() }?.let { read(it) }
if (projectData != null) {
data[id] = projectData
} else {
println("Creating project data store for $id, size: ${data.size}, ${this.hashCode()}")
data[id] = ProjectData(null, null, null, null, null, null)
}
}
return data[id]!!
}
private fun getCacheFile(project: Project): Path {
return project.projectDir.resolve(".gradle/phytotelma/${project.getId()}_data").toPath()
}
fun write(project: Project) {
write(getCacheFile(project), data[project.getId()]!!)
}
private fun write(path: Path, projectData: ProjectData) {
/**
* If you're wondering "why not use the stream-based API, it should offer better performance *especially*
* for huge documents like this!"
* The answer is because it doesn't work. Somewhere in the buffer handling the document just
* gets cut off and left in an invalid state. Yes, 30+ MiB Strings aren't ideal either.
* But I'd honestly prefer that nightmare over invalid json files.
*/
path.writeText(gson.toJson(projectData))
}
private fun read(path: Path): ProjectData? {
val reader = path.takeIf { it.exists() }?.reader()
if (reader != null) {
val data = gson.fromJson(reader, ProjectData::class.java)
return data
}
return null
}
}
class ProjectData(
@ -25,4 +78,313 @@ class ProjectData(
var mappings: MappingBundle?,
var mappingsName: String?,
var targetNamespace: String?
)
)
class ProjectDataTypeAdapter : TypeAdapter<ProjectData>() {
override fun write(out: JsonWriter, value: ProjectData) {
out.beginObject()
out.serializeNulls = true
out.name("local_cache_dir").value(value.localCacheDir?.absolutePathString())
out.name("minecraft_version").value(value.minecraftVersion)
out.name("remapped_game_jar_path").value(value.remappedGameJarPath?.absolutePathString())
out.name("mappings")
value.mappings?.let { MappingBundleTypeAdapter().write(out, it) }?:out.nullValue()
out.name("mappings_name").value(value.mappingsName)
out.name("target_namespace").value(value.targetNamespace)
out.endObject()
}
override fun read(r: JsonReader): ProjectData {
r.beginObject()
val data = ProjectData(null, null, null, null, null, null)
while (r.peek() != JsonToken.END_OBJECT) {
val name = r.nextName()
if (r.peek() == JsonToken.STRING) {
val value = r.nextString()
when (name) {
"local_cache_dir" -> {
data.localCacheDir = Path.of(value)
}
"minecraft_version" -> {
data.minecraftVersion = value
}
"remapped_game_jar_path" -> {
data.remappedGameJarPath = Path.of(value)
}
"mappings_name" -> {
data.mappingsName = value
}
"target_namespace" -> {
data.targetNamespace = value
}
}
} else if (name == "mappings") {
data.mappings = MappingBundleTypeAdapter().read(r)
} else {
r.skipValue()
}
}
r.endObject()
return data
}
}
class MappingBundleTypeAdapter : TypeAdapter<MappingBundle>() {
override fun write(out: JsonWriter, value: MappingBundle) {
out.beginObject()
out.name("data").beginArray()
value.data.forEach {
out.beginObject()
out.name("srcNs").value(it.srcNamespace)
out.name("dstNs").value(it.dstNamespace)
out.name("classes").beginObject()
it.classes.forEach { (k, v) ->
out.name(k).value(v)
}
out.endObject()
out.name("methods").beginArray()
it.methods.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").value(v)
out.endObject()
}
out.endArray()
out.name("fields").beginArray()
it.fields.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").value(v)
out.endObject()
}
out.endArray()
out.name("parameters").beginArray()
it.parameters.forEach { (k, v) ->
out.beginObject()
out.name("owner").value(k.owner)
out.name("desc").value(k.descriptor)
out.name("name").value(k.name)
out.name("value").beginObject()
v.forEach { (i, s) ->
out.name("$i").value(s)
}
out.endObject()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.name("documentation").beginArray()
value.documentation.forEach {
out.beginObject()
out.name("ns").value(it.namespace)
out.name("classes").beginArray()
it.classes.forEach { c ->
out.beginObject()
out.name("name").value(c.name)
out.name("javadoc").beginArray()
c.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.name("methods").beginArray()
c.methods.forEach { m ->
out.beginObject()
out.name("name").value(m.name)
out.name("desc").value(m.descriptor)
out.name("javadoc").beginArray()
m.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.endObject()
}
out.endArray()
out.name("fields").beginArray()
c.fields.forEach { f ->
out.beginObject()
out.name("name").value(f.name)
out.name("desc").value(f.descriptor)
out.name("javadoc").beginArray()
f.javadoc.forEach { d ->
out.value(d)
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
out.endArray()
out.endObject()
}
override fun read(r: JsonReader): MappingBundle {
val out = MappingBundle()
r.beginObject()
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
out.data.add(readData(r))
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val namespace = r.nextString()
val data = DocumentationData(namespace)
out.documentation.add(data)
r.nextName()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val name = r.nextString()
r.nextName()
val javadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
javadoc.add(r.nextString())
}
r.endArray()
r.nextName()
val methods = mutableListOf<Method>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val methodName = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val methodJavadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
methodJavadoc.add(r.nextString())
}
r.endArray()
methods.add(Method(methodName, desc, methodJavadoc))
r.endObject()
}
r.endArray()
r.nextName()
val fields = mutableListOf<Field>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val fieldName = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val fieldJavadoc = mutableListOf<String>()
r.beginArray()
while (r.peek() != JsonToken.END_ARRAY) {
fieldJavadoc.add(r.nextString())
}
r.endArray()
fields.add(Field(fieldName, desc, fieldJavadoc))
r.endObject()
}
r.endArray()
data.classes.add(DocumentationData.Class(name, javadoc, fields, methods))
r.endObject()
}
r.endArray()
r.endObject()
}
r.endArray()
r.endObject()
return out
}
private fun readData(r: JsonReader): MappingData {
r.nextName()
val srcNs = r.nextString()
r.nextName()
val dstNs = r.nextString()
r.nextName()
r.beginObject()
val classes = mutableMapOf<String, String>()
while (r.peek() != JsonToken.END_OBJECT) {
classes[r.nextName()] = r.nextString()
}
r.endObject()
r.nextName()
r.beginArray()
val methods = mutableMapOf<Member, String>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
val value = r.nextString()
methods[Member(owner, name, desc)] = value
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
val fields = mutableMapOf<Member, String>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
val value = r.nextString()
fields[Member(owner, name, desc)] = value
r.endObject()
}
r.endArray()
r.nextName()
r.beginArray()
val parameters = mutableMapOf<Member, MutableMap<Int, String>>()
while (r.peek() != JsonToken.END_ARRAY) {
r.beginObject()
r.nextName()
val owner = r.nextString()
r.nextName()
val desc = r.nextString()
r.nextName()
val name = r.nextString()
r.nextName()
r.beginObject()
val values = mutableMapOf<Int, String>()
while (r.peek() != JsonToken.END_OBJECT) {
values[r.nextName().toInt()] = r.nextString()
}
r.endObject()
parameters[Member(owner, name, desc)] = values
r.endObject()
}
r.endArray()
return MappingData(srcNs, dstNs, classes, methods, fields, parameters)
}
}

View file

@ -74,6 +74,10 @@ object AccessWidener {
return false
}
fun hasAW(project: Project): Boolean {
return getAWFile(project) != null
}
fun checkAW(path: Path, gamePath: Path) {
val reader = path.bufferedReader(StandardCharsets.UTF_8)
if (!HEADER.test(reader.readLine() ?: "")) {

View file

@ -49,7 +49,7 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
projectData.localCacheDir!!.resolve("net/minecraft/client/$version/client-$version-remapped.jar")
remappedJar.createParentDirectories()
projectData.remappedGameJarPath = remappedJar
val applyAW = AccessWidener.needsUpdate(project)
var applyAW = AccessWidener.needsUpdate(project)
if (remappedJar.notExists() || applyAW || mcConf.mappingsName != projectData.mappingsName) {
projectData.mappingsName = mcConf.mappingsName
println("Remapping the game...")
@ -59,21 +59,8 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
clientJar, remappedJar, true, true
)
VersionChecker.savePomFile(version, remappedJar.parent)
if (mcConf.targetNamespace != Constants.MOJMAP_NAMESPACE) {
Thyroxine.remap(
MappingData("", ""),
remappedJar,
remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME),
false,
listOf(RemappingStep { classVisitor, _ ->
RuntimeAccessFixVisitor(classVisitor)
})
)
} else {
Files.deleteIfExists(remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME))
}
RunConfigGenerator.generate(project)
applyAW = AccessWidener.hasAW(project)
}
project.dependencies.add(
@ -91,6 +78,19 @@ abstract class PhytotelmaGradleExtensionImpl @Inject constructor(
project.afterEvaluate {
println("Applying AccessWideners...")
AccessWidener.apply(project, remappedJar)
if (mcConf.targetNamespace != Constants.MOJMAP_NAMESPACE) {
Thyroxine.remap(
MappingData("", ""),
remappedJar,
remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME),
false,
listOf(RemappingStep { classVisitor, _ ->
RuntimeAccessFixVisitor(classVisitor)
})
)
} else {
Files.deleteIfExists(remappedJar.resolveSibling(Constants.ALTERNATIVE_RUNTIME_JAR_NAME))
}
}
}
}

View file

@ -6,6 +6,7 @@ import dev.frogmc.phytotelma.ext.datagen.DatagenExtension
import dev.frogmc.phytotelma.run.RunConfigGenerator
import org.gradle.api.Project
import org.gradle.api.logging.configuration.ConsoleOutput
import org.gradle.api.plugins.JavaPluginExtension
import java.nio.file.Files
import kotlin.io.path.absolute
import kotlin.io.path.createParentDirectories
@ -47,7 +48,12 @@ object DatagenConfigGenerator {
"-Dlog4j2.formatMsgNoLookups=true",
"-Dfrog.datagen.enabled=true",
"-Dfrog.datagen.modid=${Datagen.readModId(project)}",
"-Dfrog.datagen.generator=${conf.generatorClass.get()}"
"-Dfrog.datagen.generator=${conf.generatorClass.get()}",
"-Dfrog.datagen.output=${
project.extensions.findByType(JavaPluginExtension::class.java)?.sourceSets?.maybeCreate(
"generated"
)?.resources?.srcDirs?.first()?.toString()
}"
).also {
if (project.gradle.startParameter.consoleOutput != ConsoleOutput.Plain) {
it.add("-Dfrogmc.log.disableAnsi=false")
@ -57,4 +63,4 @@ object DatagenConfigGenerator {
)
}
}
}
}

View file

@ -26,7 +26,12 @@ abstract class DatagenTask @Inject constructor(conf: DatagenExtension) : RunGame
jvmArguments.addAll(
"-Dfrog.datagen.enabled=true",
"-Dfrog.datagen.modid=${Datagen.readModId(project)}",
"-Dfrog.datagen.generator=${conf.generatorClass.get()}"
"-Dfrog.datagen.generator=${conf.generatorClass.get()}",
"-Dfrog.datagen.output=${
project.extensions.findByType(JavaPluginExtension::class.java)?.sourceSets?.maybeCreate(
"generated"
)?.resources?.srcDirs?.first()?.toString()
}"
)
}
}