Skip to content

Commit d7453d8

Browse files
committed
initial progress of making LWJGL load on the server-side
1 parent dff2fe3 commit d7453d8

File tree

10 files changed

+315
-135
lines changed

10 files changed

+315
-135
lines changed

build.gradle.kts

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,13 @@ import com.modrinth.minotaur.dependencies.DependencyType
22
import com.modrinth.minotaur.dependencies.ModDependency
33
import dev.deftu.gradle.tools.minecraft.CurseRelation
44
import dev.deftu.gradle.tools.minecraft.CurseRelationType
5-
import dev.deftu.gradle.utils.*
5+
import dev.deftu.gradle.utils.MinecraftInfo
6+
import dev.deftu.gradle.utils.MinecraftVersion
7+
import dev.deftu.gradle.utils.ModLoader
8+
import dev.deftu.gradle.utils.includeOrShade
9+
import org.jetbrains.kotlin.daemon.common.toHexString
10+
import java.net.URI
11+
import java.security.MessageDigest
612

713
plugins {
814
java
@@ -73,6 +79,14 @@ val architecturyVersion = when (mcData.version.rawVersion) {
7379
else -> throw IllegalStateException()
7480
}
7581

82+
val lwjglVersion = when (mcData.version.rawVersion) {
83+
1_20_01 -> "3.3.1"
84+
1_20_04 -> "3.3.2"
85+
1_20_06, 1_21_01 -> "3.3.3"
86+
87+
else -> throw IllegalStateException()
88+
}
89+
7690
dependencies {
7791
implementation("de.maxhenkel.voicechat:voicechat-api:${project.property("voicechat_api_version")}")
7892
compileOnly("su.plo.voice.api:server:${project.property("plasmo_api_version")}")
@@ -141,7 +155,15 @@ dependencies {
141155
minecraftRuntimeLibraries(jws)
142156
}
143157

144-
shade(implementation("com.squareup.okhttp3:okhttp:${project.property("okhttp_version")}")!!)
158+
val okhttp = shade(implementation("com.squareup.okhttp3:okhttp:${project.property("okhttp_version")}") {
159+
exclude("kotlin")
160+
exclude("org.jetbrains")
161+
})
162+
if (mcData.isForgeLike) {
163+
minecraftRuntimeLibraries(okhttp!!)
164+
}
165+
166+
shade(implementation("org.lwjgl:lwjgl:$lwjglVersion")!!)
145167
}
146168

147169
toolkitReleases {
@@ -184,7 +206,46 @@ toolkitReleases {
184206
}
185207

186208
tasks {
209+
create("getLwjgl") {
210+
doFirst {
211+
val dir = layout.buildDirectory.dir("lwjgl/lwjgl").get().asFile
212+
if (!dir.exists())
213+
dir.mkdirs()
214+
215+
for (suffix in listOf("", "-natives-windows", "-natives-macos", "-natives-linux")) {
216+
val lwjglFile = File(dir, "lwjgl-$lwjglVersion$suffix.jar")
217+
if (lwjglFile.exists()) {
218+
logger.info("LWJGL file ${lwjglFile.name} already downloaded, not redownloading it.")
219+
continue
220+
}
221+
222+
val hash = URI("https://repo1.maven.org/maven2/org/lwjgl/lwjgl/$lwjglVersion/lwjgl-$lwjglVersion$suffix.jar.sha256").toURL().readText()
223+
val downloadUrl = URI("https://repo1.maven.org/maven2/org/lwjgl/lwjgl/$lwjglVersion/lwjgl-$lwjglVersion$suffix.jar").toURL()
224+
225+
logger.info("Downloading ${lwjglFile.name}...")
226+
lwjglFile.createNewFile()
227+
lwjglFile.writeBytes(downloadUrl.readBytes())
228+
229+
val sha1 = MessageDigest.getInstance("SHA256")
230+
sha1.update(lwjglFile.readBytes())
231+
232+
if (hash != sha1.digest().toHexString()) {
233+
lwjglFile.delete()
234+
throw SecurityException("SHA1 mismatch for ${lwjglFile.name} (expected $hash, got ${sha1.digest().toHexString()})")
235+
}
236+
}
237+
238+
val versionFile = File(dir, "version.txt")
239+
240+
if (!versionFile.exists())
241+
versionFile.createNewFile()
242+
243+
versionFile.writeText(lwjglVersion, Charsets.UTF_8)
244+
}
245+
}
246+
187247
processResources {
248+
dependsOn("getLwjgl")
188249
val properties = mutableMapOf<String, String>()
189250

190251
properties.putAll(mapOf(
@@ -254,9 +315,22 @@ tasks {
254315
}
255316
}
256317

318+
// Modified from https://github.com/DexPatcher/dexpatcher-tool/blob/v1.2.1/tool/build.gradle#L57-L79
319+
val shadowBugWorkaround = create<Jar>("shadowBugWorkaround") {
320+
dependsOn("getLwjgl")
321+
destinationDirectory.set(layout.buildDirectory.dir("shadow-bug-workaround"))
322+
archiveBaseName.set("nested-content")
323+
324+
from(layout.buildDirectory.dir("lwjgl"))
325+
}
326+
257327
fatJar {
328+
dependsOn("shadowBugWorkaround")
329+
258330
relocate("okhttp3", "xyz.bluspring.unitytranslate.shaded.okhttp3")
259331
relocate("okio", "xyz.bluspring.unitytranslate.shaded.okio")
260332
exclude("kotlin/**/*", "org/**/*")
333+
334+
from(shadowBugWorkaround)
261335
}
262336
}

src/main/kotlin/xyz/bluspring/unitytranslate/commands/UnityTranslateClientCommands.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ object UnityTranslateClientCommands {
3434
Component.literal("- Server supports UnityTranslate: ${UnityTranslateClient.connectedServerHasSupport}"),
3535
Component.literal("- Supports local translation server: ${LocalLibreTranslateInstance.canRunLibreTranslate()}"),
3636
Component.literal("- Is local translation server running: ${LocalLibreTranslateInstance.hasStarted}"),
37-
Component.literal("- Supports CUDA: ${TranslatorManager.supportsCuda}"),
37+
Component.literal("- Supports CUDA: ${TranslatorManager.checkSupportsCuda()}"),
3838
), Component.literal("\n")
3939
)
4040
}, false)

src/main/kotlin/xyz/bluspring/unitytranslate/commands/UnityTranslateCommands.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ object UnityTranslateCommands {
1717
Component.empty(),
1818
Component.literal("- Supports local translation server: ${LocalLibreTranslateInstance.canRunLibreTranslate()}"),
1919
Component.literal("- Is local translation server running: ${LocalLibreTranslateInstance.hasStarted}"),
20-
Component.literal("- Supports CUDA: ${TranslatorManager.supportsCuda}"),
20+
Component.literal("- Supports CUDA: ${TranslatorManager.checkSupportsCuda()}"),
2121
), Component.literal("\n")))
2222

2323
1

src/main/kotlin/xyz/bluspring/unitytranslate/translator/LocalLibreTranslateInstance.kt

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import java.util.concurrent.CompletableFuture
1717
import java.util.function.Consumer
1818
import java.util.zip.ZipFile
1919

20-
class LocalLibreTranslateInstance private constructor(val process: Process, val port: Int) : LibreTranslateInstance("http://127.0.0.1:$port", 150) {
20+
class LocalLibreTranslateInstance protected constructor(val process: Process, val port: Int) : LibreTranslateInstance("http://127.0.0.1:$port", 150) {
2121
init {
2222
info("Started local LibreTranslate instance on port $port.")
2323

@@ -41,12 +41,12 @@ class LocalLibreTranslateInstance private constructor(val process: Process, val
4141
var hasStarted = false
4242
var currentInstance: LocalLibreTranslateInstance? = null
4343

44-
val libreTranslateDir = File(UnityTranslate.instance.proxy.gameDir.toFile(), ".unitytranslate")
44+
val unityTranslateDir = File(UnityTranslate.instance.proxy.gameDir.toFile(), ".unitytranslate")
4545

4646
fun canRunLibreTranslate(): Boolean {
4747
val systemInfo = SystemInfo()
4848

49-
return (Runtime.getRuntime().availableProcessors() >= 2 || TranslatorManager.supportsCuda) &&
49+
return (Runtime.getRuntime().availableProcessors() >= 2 || TranslatorManager.checkSupportsCuda()) &&
5050
// Require a minimum of 2 GiB free for LibreTranslate
5151
((systemInfo.hardware.memory.total - Runtime.getRuntime().maxMemory()) / 1048576L) >= 2048
5252
}
@@ -84,7 +84,7 @@ class LocalLibreTranslateInstance private constructor(val process: Process, val
8484
}
8585

8686
private fun clearDeadDirectories() {
87-
val files = libreTranslateDir.listFiles()
87+
val files = unityTranslateDir.listFiles()
8888

8989
if (files != null) {
9090
for (file in files) {
@@ -127,7 +127,7 @@ class LocalLibreTranslateInstance private constructor(val process: Process, val
127127
"--disable-files-translation"
128128
))
129129

130-
processBuilder.directory(libreTranslateDir)
130+
processBuilder.directory(unityTranslateDir)
131131

132132
val environment = processBuilder.environment()
133133
environment["PYTHONIOENCODING"] = "utf-8"
@@ -179,26 +179,26 @@ class LocalLibreTranslateInstance private constructor(val process: Process, val
179179
}
180180

181181
fun isLibreTranslateInstalled(): Boolean {
182-
val supportsCuda = TranslatorManager.supportsCuda
182+
val supportsCuda = TranslatorManager.checkSupportsCuda()
183183
val platform = Util.getPlatform()
184184

185185
if (platform != Util.OS.WINDOWS && platform != Util.OS.OSX && platform != Util.OS.LINUX) {
186186
return false
187187
}
188188

189-
val file = File(libreTranslateDir, "libretranslate/libretranslate${if (supportsCuda) "_cuda" else ""}${if (platform == Util.OS.WINDOWS) ".exe" else ""}")
189+
val file = File(unityTranslateDir, "libretranslate/libretranslate${if (supportsCuda) "_cuda" else ""}${if (platform == Util.OS.WINDOWS) ".exe" else ""}")
190190
return file.exists()
191191
}
192192

193193
fun installLibreTranslate(): CompletableFuture<File> {
194-
val supportsCuda = TranslatorManager.supportsCuda
194+
val supportsCuda = TranslatorManager.checkSupportsCuda()
195195
val platform = Util.getPlatform()
196196

197197
if (platform != Util.OS.WINDOWS && platform != Util.OS.OSX && platform != Util.OS.LINUX) {
198198
throw IllegalStateException("Unsupported platform! (Detected platform: $platform)")
199199
}
200200

201-
val file = File(libreTranslateDir, "libretranslate/libretranslate${if (supportsCuda) "_cuda" else ""}${if (platform == Util.OS.WINDOWS) ".exe" else ""}")
201+
val file = File(unityTranslateDir, "libretranslate/libretranslate${if (supportsCuda) "_cuda" else ""}${if (platform == Util.OS.WINDOWS) ".exe" else ""}")
202202
if (!file.parentFile.exists())
203203
file.parentFile.mkdirs()
204204

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package xyz.bluspring.unitytranslate.translator
2+
3+
class NativeLocalLibreTranslateInstance {
4+
}

src/main/kotlin/xyz/bluspring/unitytranslate/translator/TranslatorManager.kt

Lines changed: 13 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@ package xyz.bluspring.unitytranslate.translator
55
//#endif
66
import dev.architectury.event.events.common.LifecycleEvent
77
import dev.architectury.event.events.common.PlayerEvent
8-
import net.minecraft.Util
98
import net.minecraft.network.FriendlyByteBuf
109
import net.minecraft.server.level.ServerPlayer
1110
import net.minecraft.world.entity.player.Player
12-
import org.lwjgl.system.APIUtil
13-
import org.lwjgl.system.JNI
14-
import org.lwjgl.system.MemoryUtil
15-
import org.lwjgl.system.SharedLibrary
1611
import xyz.bluspring.unitytranslate.Language
1712
import xyz.bluspring.unitytranslate.UnityTranslate
1813
import xyz.bluspring.unitytranslate.client.UnityTranslateClient
1914
import xyz.bluspring.unitytranslate.compat.voicechat.UTVoiceChatCompat
2015
import xyz.bluspring.unitytranslate.network.PacketIds
2116
import xyz.bluspring.unitytranslate.util.ClassLoaderProviderForkJoinWorkerThreadFactory
17+
import xyz.bluspring.unitytranslate.util.nativeaccess.CudaState
18+
import xyz.bluspring.unitytranslate.util.nativeaccess.NativeAccess
2219
import java.util.*
2320
import java.util.concurrent.CompletableFuture
2421
import java.util.concurrent.ConcurrentLinkedDeque
@@ -141,132 +138,26 @@ object TranslatorManager {
141138
return null
142139
}
143140

144-
private var isLibraryLoaded = false
145-
private lateinit var library: SharedLibrary
146-
private var PFN_cuInit: Long = 0L
147-
private var PFN_cuDeviceGetCount: Long = 0L
148-
private var PFN_cuDeviceComputeCapability: Long = 0L
141+
var hasChecked = false
149142

150-
private var PFN_cuGetErrorName: Long = 0L
151-
private var PFN_cuGetErrorString: Long = 0L
152-
153-
private fun logCudaError(code: Int, at: String) {
154-
if (code == 0)
155-
return
156-
157-
// TODO: these return ??? for some reason.
158-
// can we figure out why?
159-
160-
val errorCode = if (PFN_cuGetErrorName != MemoryUtil.NULL) {
161-
val ptr = MemoryUtil.nmemAlloc(255)
162-
JNI.callPP(code, ptr, PFN_cuGetErrorName)
163-
MemoryUtil.memUTF16(ptr).apply {
164-
MemoryUtil.nmemFree(ptr)
165-
}
166-
} else "[CUDA ERROR NAME NOT FOUND]"
167-
168-
val errorDesc = if (PFN_cuGetErrorString != MemoryUtil.NULL) {
169-
val ptr = MemoryUtil.nmemAlloc(255)
170-
JNI.callPP(code, ptr, PFN_cuGetErrorString)
171-
MemoryUtil.memUTF16(ptr).apply {
172-
MemoryUtil.nmemFree(ptr)
173-
}
174-
} else "[CUDA ERROR DESC NOT FOUND]"
175-
176-
UnityTranslate.logger.error("CUDA error at $at: $code $errorCode ($errorDesc)")
177-
}
178-
179-
private fun isCudaSupported(): Boolean {
143+
fun checkSupportsCuda(): Boolean {
180144
if (!UnityTranslate.config.server.shouldUseCuda) {
181145
UnityTranslate.logger.info("CUDA is disabled in the config, not enabling CUDA support.")
182146
return false
183147
}
184148

185-
if (!isLibraryLoaded) {
186-
try {
187-
library = if (Util.getPlatform() == Util.OS.WINDOWS) {
188-
APIUtil.apiCreateLibrary("nvcuda.dll")
189-
} else if (Util.getPlatform() == Util.OS.LINUX) {
190-
APIUtil.apiCreateLibrary("libcuda.so")
191-
} else {
192-
return false
149+
return NativeAccess.isCudaSupported().apply {
150+
if (!hasChecked) {
151+
if (this == CudaState.AVAILABLE)
152+
UnityTranslate.logger.info("CUDA is supported, using GPU for translations.")
153+
else {
154+
UnityTranslate.logger.info("CUDA is not supported, using CPU for translations.")
155+
UnityTranslate.logger.info("CUDA state: $ordinal ($name): $message")
193156
}
194157

195-
PFN_cuInit = library.getFunctionAddress("cuInit")
196-
PFN_cuDeviceGetCount = library.getFunctionAddress("cuDeviceGetCount")
197-
PFN_cuDeviceComputeCapability = library.getFunctionAddress("cuDeviceComputeCapability")
198-
PFN_cuGetErrorName = library.getFunctionAddress("cuGetErrorName")
199-
PFN_cuGetErrorString = library.getFunctionAddress("cuGetErrorString")
200-
201-
if (PFN_cuInit == MemoryUtil.NULL || PFN_cuDeviceGetCount == MemoryUtil.NULL || PFN_cuDeviceComputeCapability == MemoryUtil.NULL) {
202-
// TODO: remove in prod
203-
UnityTranslate.logger.info("CUDA results: $PFN_cuInit $PFN_cuDeviceGetCount $PFN_cuDeviceComputeCapability")
204-
return false
205-
}
206-
} catch (_: UnsatisfiedLinkError) {
207-
UnityTranslate.logger.warn("CUDA library failed to load! Not attempting to initialize CUDA functions.")
208-
return false
209-
} catch (e: Throwable) {
210-
UnityTranslate.logger.warn("An error occurred while searching for CUDA devices! You don't have to report this, don't worry.")
211-
e.printStackTrace()
212-
return false
158+
hasChecked = true
213159
}
214-
215-
isLibraryLoaded = true
216-
}
217-
218-
val success = 0
219-
220-
if (JNI.callI(0, PFN_cuInit).apply {
221-
logCudaError(this, "init")
222-
} != success) {
223-
return false
224-
}
225-
226-
val totalPtr = MemoryUtil.nmemAlloc(Int.SIZE_BYTES.toLong())
227-
if (JNI.callPI(totalPtr, PFN_cuDeviceGetCount).apply {
228-
logCudaError(this, "get device count")
229-
} != success) {
230-
return false
231-
}
232-
233-
val totalCudaDevices = MemoryUtil.memGetInt(totalPtr)
234-
UnityTranslate.logger.info("Total CUDA devices: $totalCudaDevices")
235-
if (totalCudaDevices <= 0) {
236-
return false
237-
}
238-
239-
MemoryUtil.nmemFree(totalPtr)
240-
241-
for (i in 0 until totalCudaDevices) {
242-
val minorPtr = MemoryUtil.nmemAlloc(Int.SIZE_BYTES.toLong())
243-
val majorPtr = MemoryUtil.nmemAlloc(Int.SIZE_BYTES.toLong())
244-
245-
if (JNI.callPPI(majorPtr, minorPtr, i, PFN_cuDeviceComputeCapability).apply {
246-
logCudaError(this, "get device compute capability $i")
247-
} != success) {
248-
continue
249-
}
250-
251-
val majorVersion = MemoryUtil.memGetInt(majorPtr)
252-
val minorVersion = MemoryUtil.memGetInt(minorPtr)
253-
254-
MemoryUtil.nmemFree(majorPtr)
255-
MemoryUtil.nmemFree(minorPtr)
256-
257-
UnityTranslate.logger.info("Found device with CUDA compute capability major $majorVersion minor $minorVersion.")
258-
259-
return true
260-
}
261-
262-
return false
263-
}
264-
265-
val supportsCuda = isCudaSupported().apply {
266-
if (this)
267-
UnityTranslate.logger.info("CUDA is supported, using GPU for translations.")
268-
else
269-
UnityTranslate.logger.info("CUDA is not supported, using CPU for translations.")
160+
} == CudaState.AVAILABLE
270161
}
271162

272163
fun installLibreTranslate() {

0 commit comments

Comments
 (0)