From a046b198645811fe1b7db129942505c379aabb03 Mon Sep 17 00:00:00 2001 From: Linnea Gräf Date: Tue, 17 Dec 2024 23:07:06 +0100 Subject: feat: Add name prefetching --- .../mixin/OnInitializationCompletePatch.java | 18 ++++++ src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt | 34 +++++++++++ src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt | 14 +++++ src/main/kotlin/moe/nea/ledger/Ledger.kt | 8 ++- .../nea/ledger/events/InitializationComplete.kt | 6 ++ .../moe/nea/ledger/events/SupplyDebugInfo.kt | 10 ++++ .../moe/nea/ledger/modules/ExternalDataProvider.kt | 43 ++++++++++++++ src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt | 5 ++ src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt | 10 ++++ src/main/kotlin/moe/nea/ledger/utils/di/DI.kt | 7 ++- .../kotlin/moe/nea/ledger/utils/di/DIProvider.kt | 1 + .../kotlin/moe/nea/ledger/utils/network/Request.kt | 40 +++++++++++++ .../moe/nea/ledger/utils/network/RequestUtil.kt | 63 +++++++++++++++++++++ .../moe/nea/ledger/utils/network/Response.kt | 19 +++++++ .../utils/telemetry/ExceptionContextValue.kt | 5 +- src/main/resources/ledgerkeystore.jks | Bin 0 -> 104393 bytes 16 files changed, 277 insertions(+), 6 deletions(-) create mode 100644 src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java create mode 100644 src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt create mode 100644 src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt create mode 100644 src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt create mode 100644 src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/network/Request.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt create mode 100644 src/main/kotlin/moe/nea/ledger/utils/network/Response.kt create mode 100644 src/main/resources/ledgerkeystore.jks (limited to 'src') diff --git a/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java b/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java new file mode 100644 index 0000000..fc9afb7 --- /dev/null +++ b/src/main/java/moe/nea/ledger/mixin/OnInitializationCompletePatch.java @@ -0,0 +1,18 @@ +package moe.nea.ledger.mixin; + +import moe.nea.ledger.events.InitializationComplete; +import net.minecraft.client.Minecraft; +import net.minecraftforge.common.MinecraftForge; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(Minecraft.class) +public class OnInitializationCompletePatch { + + @Inject(method = "startGame", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/client/FMLClientHandler;onInitializationComplete()V")) + private void onInitComplete(CallbackInfo ci) { + MinecraftForge.EVENT_BUS.post(new InitializationComplete()); + } +} diff --git a/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt b/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt new file mode 100644 index 0000000..bab0a78 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/DebugDataCommand.kt @@ -0,0 +1,34 @@ +package moe.nea.ledger + +import moe.nea.ledger.events.SupplyDebugInfo +import moe.nea.ledger.utils.di.Inject +import net.minecraft.command.CommandBase +import net.minecraft.command.ICommandSender +import net.minecraftforge.common.MinecraftForge + +class DebugDataCommand : CommandBase() { + + override fun canCommandSenderUseCommand(sender: ICommandSender?): Boolean { + return true + } + + override fun getCommandName(): String { + return "ledgerdebug" + } + + override fun getCommandUsage(sender: ICommandSender?): String { + return "" + } + + @Inject + lateinit var logger: LedgerLogger + + override fun processCommand(sender: ICommandSender?, args: Array?) { + val debugInfo = SupplyDebugInfo() + MinecraftForge.EVENT_BUS.post(debugInfo) + logger.printOut("Collected debug info:") + debugInfo.data.forEach { + logger.printOut("${it.first}: ${it.second}") + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt index 121586e..a84aac8 100644 --- a/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/ItemIdProvider.kt @@ -3,6 +3,8 @@ package moe.nea.ledger import moe.nea.ledger.events.BeforeGuiAction import moe.nea.ledger.events.ExtraSupplyIdEvent import moe.nea.ledger.events.RegistrationFinishedEvent +import moe.nea.ledger.events.SupplyDebugInfo +import moe.nea.ledger.modules.ExternalDataProvider import net.minecraft.client.Minecraft import net.minecraft.item.ItemStack import net.minecraft.nbt.NBTTagCompound @@ -27,6 +29,13 @@ class ItemIdProvider { private val knownNames = mutableMapOf() + @SubscribeEvent + fun onDataLoaded(event: ExternalDataProvider.DataLoaded) { + event.provider.itemNames.forEach { (itemId, itemName) -> + knownNames[itemName.unformattedString().trim()] = ItemId(itemId) + } + } + @SubscribeEvent fun onRegistrationFinished(event: RegistrationFinishedEvent) { MinecraftForge.EVENT_BUS.post(ExtraSupplyIdEvent(knownNames::put)) @@ -40,6 +49,11 @@ class ItemIdProvider { inventory.armorInventory?.forEach { saveFromSlot(it) } } + @SubscribeEvent + fun onDebugData(event: SupplyDebugInfo) { + event.record("knownItemNames", knownNames.size) + } + fun saveFromSlot(stack: ItemStack?, preprocessName: (String) -> String = { it }) { if (stack == null) return val nbt = stack.tagCompound ?: NBTTagCompound() diff --git a/src/main/kotlin/moe/nea/ledger/Ledger.kt b/src/main/kotlin/moe/nea/ledger/Ledger.kt index b8a4073..58a8b3d 100644 --- a/src/main/kotlin/moe/nea/ledger/Ledger.kt +++ b/src/main/kotlin/moe/nea/ledger/Ledger.kt @@ -15,13 +15,15 @@ import moe.nea.ledger.modules.BazaarOrderDetection import moe.nea.ledger.modules.BitsDetection import moe.nea.ledger.modules.BitsShopDetection import moe.nea.ledger.modules.DungeonChestDetection +import moe.nea.ledger.modules.ExternalDataProvider import moe.nea.ledger.modules.KatDetection import moe.nea.ledger.modules.KuudraChestDetection import moe.nea.ledger.modules.MinionDetection import moe.nea.ledger.modules.NpcDetection import moe.nea.ledger.modules.VisitorDetection -import moe.nea.ledger.utils.di.DI import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.di.DI +import moe.nea.ledger.utils.network.RequestUtil import net.minecraft.client.Minecraft import net.minecraft.command.ICommand import net.minecraftforge.client.ClientCommandHandler @@ -99,12 +101,14 @@ class Ledger { BankDetection::class.java, BazaarDetection::class.java, BazaarOrderDetection::class.java, + DebugDataCommand::class.java, BitsDetection::class.java, BitsShopDetection::class.java, ConfigCommand::class.java, Database::class.java, DungeonChestDetection::class.java, ErrorUtil::class.java, + ExternalDataProvider::class.java, ItemIdProvider::class.java, KatDetection::class.java, KuudraChestDetection::class.java, @@ -113,6 +117,7 @@ class Ledger { MinionDetection::class.java, NpcDetection::class.java, QueryCommand::class.java, + RequestUtil::class.java, VisitorDetection::class.java, ) val errorUtil = di.provide() @@ -125,7 +130,6 @@ class Ledger { errorUtil.catch { di.provide().loadAndUpgrade() - error("Lol") } MinecraftForge.EVENT_BUS.post(RegistrationFinishedEvent()) diff --git a/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt b/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt new file mode 100644 index 0000000..d917039 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/events/InitializationComplete.kt @@ -0,0 +1,6 @@ +package moe.nea.ledger.events + +import net.minecraftforge.fml.common.eventhandler.Event + +class InitializationComplete : Event() { +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt b/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt new file mode 100644 index 0000000..cab0a20 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/events/SupplyDebugInfo.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.events + +import net.minecraftforge.fml.common.eventhandler.Event + +class SupplyDebugInfo : Event() { // TODO: collect this in the event recorder + val data = mutableListOf>() + fun record(key: String, value: Any) { + data.add(key to value) + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt b/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt new file mode 100644 index 0000000..93bb453 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/modules/ExternalDataProvider.kt @@ -0,0 +1,43 @@ +package moe.nea.ledger.modules + +import moe.nea.ledger.events.InitializationComplete +import moe.nea.ledger.events.SupplyDebugInfo +import moe.nea.ledger.utils.GsonUtil +import moe.nea.ledger.utils.di.Inject +import moe.nea.ledger.utils.network.Request +import moe.nea.ledger.utils.network.RequestUtil +import net.minecraftforge.common.MinecraftForge +import net.minecraftforge.fml.common.eventhandler.Event +import net.minecraftforge.fml.common.eventhandler.SubscribeEvent +import java.util.concurrent.CompletableFuture + +class ExternalDataProvider @Inject constructor( + val requestUtil: RequestUtil +) { + + fun createAuxillaryDataRequest(path: String): Request { + return requestUtil.createRequest("https://github.com/nea89o/ledger-auxiliary-data/raw/refs/heads/master/$path") + } + + private val itemNameFuture: CompletableFuture> = CompletableFuture.supplyAsync { + val request = createAuxillaryDataRequest("data/item_names.json") + val response = request.execute(requestUtil) + val nameMap = response.json(GsonUtil.typeToken>()) + return@supplyAsync nameMap + } + + lateinit var itemNames: Map + + class DataLoaded(val provider: ExternalDataProvider) : Event() + + @SubscribeEvent + fun onDebugData(debugInfo: SupplyDebugInfo) { + debugInfo.record("externalItemsLoaded", itemNameFuture.isDone && !itemNameFuture.isCompletedExceptionally) + } + + @SubscribeEvent + fun onInitComplete(event: InitializationComplete) { + itemNames = itemNameFuture.join() + MinecraftForge.EVENT_BUS.post(DataLoaded(this)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt index 4ba313e..344c8bc 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/ErrorUtil.kt @@ -10,6 +10,11 @@ class ErrorUtil { @Inject lateinit var reporter: EventRecorder + fun reportAdHoc(message: String) { + report(Exception(message), message) + + } + fun report(exception: Throwable, message: String?) { Span.current().recordException(reporter, exception, message) } diff --git a/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt new file mode 100644 index 0000000..d3c1f6e --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/GsonUtil.kt @@ -0,0 +1,10 @@ +package moe.nea.ledger.utils + +import com.google.gson.reflect.TypeToken + +object GsonUtil { + inline fun typeToken(): TypeToken { + return object : TypeToken() {} + } + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt index 6940f72..133637a 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/di/DI.kt @@ -6,6 +6,9 @@ import java.util.Stack @Suppress("UNCHECKED_CAST") class DI { + private fun formatInjectionStack() = + injectionStack.joinToString(" -> ") + private fun internalProvide(type: Class, element: AnnotatedElement? = null): T { val provider = providers[type] as BaseDIProvider val context = if (element == null) provider.createEmptyContext() else provider.createContext(element) @@ -13,13 +16,13 @@ class DI { val existingValue = values[key] if (existingValue != null) return existingValue as T if (type in injectionStack) { - error("Found injection cycle: ${injectionStack.joinToString(" -> ")} -> $type") + error("Found injection cycle: ${formatInjectionStack()} -> $type") } injectionStack.push(type) val value = try { provider.provideWithContext(this, context) } catch (ex: Exception) { - throw RuntimeException("Could not create instance for type $type", ex) + throw RuntimeException("Could not create instance for type $type (in stack ${formatInjectionStack()})", ex) } val cycleCheckCookie = injectionStack.pop() require(cycleCheckCookie == type) { "Unbalanced stack cookie: $cycleCheckCookie != $type" } diff --git a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt index b5ce550..bd5b9ef 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/di/DIProvider.kt @@ -24,6 +24,7 @@ fun interface DIProvider : BaseDIProvider { ?: clazz.constructors.find { it.parameterCount == 0 } ?: error("Could not find DI injection entrypoint for class $clazz")) as Constructor + // TODO: consider using unsafe init to inject the parameters *before* calling the constructor return DIProvider { di -> val typArgs = cons.parameters.map { di.provide(it.type, it) diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt new file mode 100644 index 0000000..ddf2fcc --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/Request.kt @@ -0,0 +1,40 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.JsonElement +import java.net.URL + +data class Request( + val url: URL, + val method: Method, + val body: String?, + val headers: Map, +) { + enum class Method { + GET, POST + } + + enum class MediaType(val text: String) { + JSON("application/json"), + TEXT("text/plain"), + HTML("text/html"), + ANY("*/*"), + } + + fun withHeaders(map: Map): Request { + // TODO: enforce caselessness? + return this.copy(headers = headers + map) + } + + fun post() = copy(method = Method.POST) + fun get() = copy(method = Method.GET) + + fun json(element: JsonElement) = copy( + headers = headers + mapOf("content-type" to "application/json"), + body = element.toString()) + + fun accept(request: MediaType) = withHeaders(mapOf("accept" to request.text)) + + fun acceptJson() = accept(MediaType.JSON) + + fun execute(requestUtil: RequestUtil) = requestUtil.executeRequest(this) +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt new file mode 100644 index 0000000..a49c65a --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/RequestUtil.kt @@ -0,0 +1,63 @@ +package moe.nea.ledger.utils.network + +import moe.nea.ledger.utils.ErrorUtil +import moe.nea.ledger.utils.di.Inject +import java.net.URL +import java.net.URLConnection +import java.security.KeyStore +import java.util.zip.GZIPInputStream +import javax.net.ssl.HttpsURLConnection +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext +import javax.net.ssl.TrustManagerFactory + +class RequestUtil @Inject constructor(val errorUtil: ErrorUtil) { + + private fun createSSLContext(): SSLContext? = errorUtil.catch { + val keyStorePath = RequestUtil::class.java.getResourceAsStream("/ledgerkeystore.jks") + ?: error("Could not locate keystore") + val keyStore = KeyStore.getInstance("JKS") + keyStore.load(keyStorePath, "neuneu".toCharArray()) + val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()) + val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()) + kmf.init(keyStore, null) + tmf.init(keyStore) + val ctx = SSLContext.getInstance("TLS") + ctx.init(kmf.keyManagers, tmf.trustManagers, null) + return@catch ctx + } + + val sslContext = createSSLContext() + + fun enhanceConnection(connection: URLConnection) { + if (connection is HttpsURLConnection && sslContext != null) { + connection.sslSocketFactory = sslContext.socketFactory + } + } + + fun createRequest(url: String) = createRequest(URL(url)) + fun createRequest(url: URL) = Request(url, Request.Method.GET, null, mapOf()) + + fun executeRequest(request: Request): Response { + val connection = request.url.openConnection() + enhanceConnection(connection) + connection.setRequestProperty("accept-encoding", "gzip") + request.headers.forEach { (k, v) -> + connection.setRequestProperty(k, v) + } + if (request.body != null) { + connection.getOutputStream().write(request.body.encodeToByteArray()) + connection.getOutputStream().close() + } + var stream = connection.getInputStream() + if (connection.contentEncoding == "gzip") { + stream = GZIPInputStream(stream) + } + val text = stream.bufferedReader().readText() + stream.close() + // Do NOT call connection.disconnect() to allow for connection reuse + return Response(request, text, connection.headerFields) + } + + +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt new file mode 100644 index 0000000..daae7f7 --- /dev/null +++ b/src/main/kotlin/moe/nea/ledger/utils/network/Response.kt @@ -0,0 +1,19 @@ +package moe.nea.ledger.utils.network + +import com.google.gson.reflect.TypeToken +import moe.nea.ledger.Ledger + +data class Response( + val source: Request, + // TODO: allow other body processors, to avoid loading everything as strings + val response: String, + val headers: Map>, +) { + fun json(typ: TypeToken): T { + return Ledger.gson.fromJson(response, typ.type) + } + + fun json(clazz: Class): T { + return Ledger.gson.fromJson(response, clazz) + } +} \ No newline at end of file diff --git a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt index df588a8..96b70ec 100644 --- a/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt +++ b/src/main/kotlin/moe/nea/ledger/utils/telemetry/ExceptionContextValue.kt @@ -22,8 +22,9 @@ class ExceptionContextValue(val exception: Throwable) : ContextValue { obj.addProperty("message", exception.message) // TODO: allow exceptions to implement an "extra info" interface if (searchDepth > 0) { - if (exception.cause != null) { - obj.add("cause", walkExceptions(exception, searchDepth - 1)) + val cause = exception.cause + if (cause != null && cause !== exception) { + obj.add("cause", walkExceptions(cause, searchDepth - 1)) } val suppressions = JsonArray() for (suppressedException in exception.suppressedExceptions) { diff --git a/src/main/resources/ledgerkeystore.jks b/src/main/resources/ledgerkeystore.jks new file mode 100644 index 0000000..b71185a Binary files /dev/null and b/src/main/resources/ledgerkeystore.jks differ -- cgit