Move UncivServer to own module (and jar) (#6468)

* Move UncivServer to own module (and jar)

* UncivServer isalive logged

* Separate UncivServer - some wiki hints

* Separate UncivServer - how to build UncivServer.jar

Co-authored-by: Yair Morgenstern <yairm210@hotmail.com>
This commit is contained in:
SomeTroglodyte 2022-05-08 20:22:43 +02:00 committed by GitHub
parent 3ea9c6503b
commit 4290373cf1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 336 additions and 23 deletions

8
.gitignore vendored
View File

@ -39,6 +39,7 @@ com_crashlytics_export_strings.xml
/android/bin/
/core/bin/
/desktop/bin/
/server/bin/
/html/bin/
/ios/bin/
/ios-moe/bin/
@ -57,6 +58,7 @@ com_crashlytics_export_strings.xml
/android/nbproject/private/
/core/nbproject/private/
/desktop/nbproject/private/
/server/nbproject/private/
/html/nbproject/private/
/ios/nbproject/private/
/ios-moe/nbproject/private/
@ -65,6 +67,7 @@ com_crashlytics_export_strings.xml
/android/build/
/core/build/
/desktop/build/
/server/build/
/html/build/
/ios/build/
/ios-moe/build/
@ -74,6 +77,7 @@ com_crashlytics_export_strings.xml
/android/nbbuild/
/core/nbbuild/
/desktop/nbbuild/
/server/nbbuild/
/html/nbbuild/
/ios/nbbuild/
/ios-moe/nbbuild/
@ -82,6 +86,7 @@ com_crashlytics_export_strings.xml
/android/dist/
/core/dist/
/desktop/dist/
/server/dist/
/html/dist/
/ios/dist/
/ios-moe/dist/
@ -90,6 +95,7 @@ com_crashlytics_export_strings.xml
/android/nbdist/
/core/nbdist/
/desktop/nbdist/
/server/nbdist/
/html/nbdist/
/ios/nbdist/
/ios-moe/nbdist/
@ -131,6 +137,8 @@ android/release/android-release.aab
tests/build/
desktop/packr/
desktop/packrCache/
server/packr/
server/packrCache/
deploy/
android/release/

View File

@ -70,12 +70,18 @@ project(":desktop") {
}
"implementation"("com.github.MinnDevelopment:java-discord-rpc:v2.0.1")
"implementation"("net.java.dev.jna:jna:5.11.0")
"implementation"("net.java.dev.jna:jna-platform:5.11.0")
}
}
// For server-side
project(":server") {
apply(plugin = "kotlin")
dependencies {
// For server-side
"implementation"("io.ktor:ktor-server-core:1.6.8")
"implementation"("io.ktor:ktor-server-netty:1.6.8")
"implementation"("ch.qos.logback:logback-classic:1.2.5")

View File

@ -63,15 +63,16 @@ data class KeyCharAndCode(val char: Char, val code: Int) {
/** Guaranteed to be ignored by [KeyPressDispatcher.set] and never to be generated for an actual event, used as fallback to ensure no action is taken */
val UNKNOWN = KeyCharAndCode(Input.Keys.UNKNOWN)
// Kludges because we got crashes: java.lang.NoSuchMethodError: 'int kotlin.CharCodeKt.access$getCode$p(char)'
fun Char.toCode() =
try { code } catch (ex: Throwable) { null }
?: try { @Suppress("DEPRECATION") toInt() } catch (ex: Throwable) { null }
?: 0
fun Int.makeChar() =
try { Char(this) } catch (ex: Throwable) { null }
?: try { toChar() } catch (ex: Throwable) { null }
?: Char.MIN_VALUE
// Kludges because we got crashes: java.lang.NoSuchMethodError: 'int kotlin.CharCodeKt.access$getCode$p(char)'
//TODO fixed by removing UncivServer from the desktop module - clean up comments and all uses
fun Char.toCode() = code
// try { code } catch (ex: Throwable) { null }
// ?: try { @Suppress("DEPRECATION") toInt() } catch (ex: Throwable) { null }
// ?: 0
fun Int.makeChar() = Char(this)
// try { Char(this) } catch (ex: Throwable) { null }
// ?: try { toChar() } catch (ex: Throwable) { null }
// ?: Char.MIN_VALUE
/** mini-factory for control codes - case insensitive */
fun ctrl(letter: Char) = KeyCharAndCode((letter.toCode() and 31).makeChar(),0)

View File

@ -20,18 +20,21 @@ So first things first - the initial "No assumptions" setup to have Unciv run fro
- Select "Show Package Details" in the bottom right
- Choose version 30.0.3 under "Android SDK Build-Tools <whatever version you have>"
- Click "Apply"
- In Android Studio, Run > Edit configurations.
- In Android Studio, Run > Edit configurations (be sure the Gradle sync is finished successfully first).
- Click "+" to add a new configuration
- Choose "Application"
- Set the module to `Unciv.desktop`, main class to `com.unciv.app.desktop.DesktopLauncher` and `<repo_folder>\android\assets\` as the Working directory, OK to close the window
- Give the configuration a name, we recommend "Desktop"
- Set the module classpath (the box to the right of the Java selection) to `Unciv.desktop`, main class to `com.unciv.app.desktop.DesktopLauncher` and `<repo_folder>\android\assets\` as the Working directory, OK to close the window
- If you get a `../../docs/uniques.md (No such file or directory)` error that means you forgot to set the working directory!
- Select the Desktop configuration and click the green arrow button to run!
- Select the Desktop configuration (or however you chose to name it) and click the green arrow button to run! Or you can use the next button -the green critter with six legs and two feelers - to start debugging.
- I also recommend going to Settings > Version Control > Commit and turning off 'Before commit - perform code analysis'
Unciv uses Gradle to specify dependencies and how to run. In the background, the Gradle gnomes will be off fetching the packages (a one-time effort) and, once that's done, will build the project!
Unciv uses Grade 7.2 and the Android Gradle Plugin 7.1.0
Note advanced build commands as described in the next paragraph, specifically the `gradlew desktop:dist` one to build a jar, run just fine in Android Studio's terminal (Alt+F12), with most dependencies already taken care of.
## Without Android Studio
If you also have JDK 11 installed, you can compile Unciv on your own by cloning (or downloading and unzipping) the project, opening a terminal in the Unciv folder and run the following commands:
@ -63,6 +66,26 @@ After building, the output .JAR file should be in /desktop/build/libs/Unciv.jar
For actual development, you'll probably need to download Android Studio and build it yourself - see Contributing :)
## UncivServer
The simple multiplayer host included in the sources can be set up to debug or run analogously to the main game:
- In Android Studio, Run > Edit configurations.
- Click "+" to add a new configuration
- Choose "Application" and name the config, e.g. "UncivServer"
- Set the module to `Unciv.server`, main class to `com.unciv.app.server.DesktopLauncher` and `<repo_folder>/android/assets/` as the Working directory, OK to close the window.
- Select the UncivServer configuration and click the green arrow button to run! Or start a debug session as above.
To build a jar file, refer to [Without Android Studio](#Without-Android-Studio) and replace 'desktop' with 'server'. That is, run `./gradlew server:dist` and when it's done look for /server/build/libs/UncivServer.jar
## Unit Tests
You can (and in some cases _should_) run and even debug the unit tests locally.
- In Android Studio, Run > Edit configurations.
- Click "+" to add a new configuration
- Choose "Gradle" and name the config, e.g. "Unit Tests"
- Under "Gradle Project", choose "Unciv" from the dropdown (or type it), set "Tasks" to `:tests:test` and "Arguments" to `--tests "com.unciv.*"`, OK to close the window.
- Select the "Unit Tests" configuration and click the green arrow button to run! Or start a debug session as above.
## Next steps
Congratulations! Unciv should now be running on your computer! Now we can start changing some code, and later we'll see how your changes make it into the main repository!

View File

@ -2,11 +2,14 @@
Since LibGDX, and therefore Unciv, are built for multi-platform support, the project structure is built accordingly.
99% of the code is in the [Core](/com/unciv) project, which contains all the platform-independant code.
99% of the code is in the [core](https://github.com/yairm210/Unciv/tree/master/core) project, which contains all the platform-independant code.
The [Desktop](/) and [Android](/) folders contain platform-specific things, and the Android folder also contains the game Images and the all-important Assets, which are required for running from Desktop as well, so we bundle them up into the .jar file when releasing.
The [desktop](https://github.com/yairm210/Unciv/tree/master/desktop) and [android](https://github.com/yairm210/Unciv/tree/master/android) folders contain platform-specific things, and the Android folder also contains the game Images and the all-important Assets, which are required for running from Desktop as well, so we bundle them up into the .jar file when releasing.
The [tests](https://github.com/yairm210/Unciv/tree/master/tests) folder contains tests that can be run manually via gradle with `./gradlew tests:test`, and are run automatically by Travis for every push.
The [server](https://github.com/yairm210/Unciv/tree/master/server) folder contains the sources for the UncivServer (a host enabling communication between multiplayer game instances), which is packaged into its own separate jar.
The [Test](/com/unciv) folder contains tests that can be run manually via gradle with `./gradlew tests:test`, and are run automatically by Travis for every push.
## Translations

View File

@ -6,8 +6,8 @@ Therefore, you can now host your own Unciv server, when not on Android.
To do so, you must have a JDK installed.
From the directory where the Unciv.jar file is located, create a folder named "MultiplayerFiles", open a terminal and run the following line:
`java -cp Unciv.jar com.unciv.app.desktop.UncivServer`
From the directory where the UncivServer.jar file is located, create a folder named "MultiplayerFiles", open a terminal and run the following line:
`java -jar UncivServer.jar`
Don't forget to use 'cd' to switch to the correct dictionary. Here's an example in Windows.
@ -16,7 +16,7 @@ D:
cd Games
cd unciv
mkdir MultiplayerFiles
java -cp Unciv.jar com.unciv.app.desktop.UncivServer
java -jar UncivServer.jar
```
Your server has now started!

161
server/build.gradle.kts Normal file
View File

@ -0,0 +1,161 @@
import com.badlogicgames.packr.Packr
import com.badlogicgames.packr.PackrConfig
import com.unciv.build.BuildConfig
plugins {
id("kotlin")
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
}
sourceSets {
main {
java.srcDir("src/")
}
}
val mainClassName = "com.unciv.app.server.UncivServer"
val assetsDir = file("../android/assets")
val deployFolder = file("../deploy")
// See https://github.com/libgdx/libgdx/wiki/Starter-classes-and-configuration#common-issues
// and https://github.com/yairm210/Unciv/issues/5679
val jvmArgsForMac = listOf("-XstartOnFirstThread", "-Djava.awt.headless=true")
tasks.register<JavaExec>("run") {
jvmArgs = mutableListOf<String>()
if ("mac" in System.getProperty("os.name").toLowerCase())
(jvmArgs as MutableList<String>).addAll(jvmArgsForMac)
// These are non-standard, only available/necessary on Mac.
dependsOn(tasks.getByName("classes"))
main = mainClassName
classpath = sourceSets.main.get().runtimeClasspath
standardInput = System.`in`
workingDir = assetsDir
isIgnoreExitValue = true
}
tasks.register<JavaExec>("debug") {
jvmArgs = jvmArgsForMac
dependsOn(tasks.getByName("classes"))
main = mainClassName
classpath = sourceSets.main.get().runtimeClasspath
standardInput = System.`in`
workingDir = assetsDir
isIgnoreExitValue = true
debug = true
}
tasks.register<Jar>("dist") { // Compiles the jar file
dependsOn(tasks.getByName("classes"))
// META-INF/INDEX.LIST and META-INF/io.netty.versions.properties are duplicated, but I don't know why
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(files(sourceSets.main.get().output.resourcesDir))
from(files(sourceSets.main.get().output.classesDirs))
// see Laurent1967's comment on https://github.com/libgdx/libgdx/issues/5491
from({ configurations.compileClasspath.get().resolve().map { if (it.isDirectory) it else zipTree(it) } })
archiveFileName.set("UncivServer.jar")
manifest {
attributes(mapOf("Main-Class" to mainClassName, "Specification-Version" to BuildConfig.appVersion))
}
}
for (platform in PackrConfig.Platform.values()) {
val platformName = platform.toString()
tasks.create("packr${platformName}") {
dependsOn(tasks.getByName("dist"))
// Needs to be here and not in doLast because the zip task depends on the outDir
val jarFile = "$rootDir/server/build/libs/UncivServer.jar"
val config = PackrConfig()
config.platform = platform
config.apply {
executable = "UncivServer"
classpath = listOf(jarFile)
removePlatformLibs = config.classpath
mainClass = mainClassName
vmArgs = listOf("Xmx1G")
minimizeJre = "server/packrConfig.json"
outDir = file("packr")
}
doLast {
// https://gist.github.com/seanf/58b76e278f4b7ec0a2920d8e5870eed6
fun String.runCommand(workingDir: File) {
val process = ProcessBuilder(*split(" ").toTypedArray())
.directory(workingDir)
.redirectOutput(ProcessBuilder.Redirect.PIPE)
.redirectError(ProcessBuilder.Redirect.PIPE)
.start()
if (!process.waitFor(30, TimeUnit.SECONDS)) {
process.destroy()
throw RuntimeException("execution timed out: $this")
}
if (process.exitValue() != 0) {
println("execution returned code ${process.exitValue()}: $this")
}
println(process.inputStream.bufferedReader().readText())
}
if (config.outDir.exists()) delete(config.outDir)
// Requires that both packr and the jre are downloaded, as per buildAndDeploy.yml, "Upload to itch.io"
// Use old version of packr - newer versions aren't Windows32-compliant
if (platform == PackrConfig.Platform.Windows32) {
config.jdk = "jdk-windows-32.zip"
Packr().pack(config)
} else {
val jdkFile =
when (platform) {
PackrConfig.Platform.Linux64 -> "jre-linux-64.tar.gz"
PackrConfig.Platform.Windows64 -> "jdk-windows-64.zip"
else -> "jre-macOS.tar.gz"
}
val platformNameForPackrCmd =
if (platform == PackrConfig.Platform.MacOS) "mac"
else platform.name.toLowerCase()
val command = "java -jar $rootDir/packr-all-4.0.0.jar" +
" --platform $platformNameForPackrCmd" +
" --jdk $jdkFile" +
" --executable UncivServer" +
" --classpath $jarFile" +
" --mainclass $mainClassName" +
" --vmargs Xmx1G " +
(if (platform == PackrConfig.Platform.MacOS) jvmArgsForMac.joinToString(" ") {
it.removePrefix("-")
}
else "") +
" --output ${config.outDir}"
command.runCommand(rootDir)
}
}
tasks.register<Zip>("zip${platformName}") {
archiveFileName.set("UncivServer-${platformName}.zip")
from(config.outDir)
destinationDirectory.set(deployFolder)
}
finalizedBy("zip${platformName}")
}
}
tasks.register<Zip>("zipLinuxFilesForJar") {
archiveFileName.set("linuxFilesForJar.zip")
from(file("linuxFilesForJar"))
destinationDirectory.set(deployFolder)
}

View File

@ -0,0 +1,46 @@
#!/bin/sh
CONFIG_DIR="$HOME/.local/share/Unciv"
USAGE="UncivServer [--help | -h | --config-dir PATH]
Run the Unciv Multiplayer Server.
With '--help' or '-h', show this help info and exit.
With '--config-dir PATH', use/make configuration files in PATH instead
of the default of '$CONFIG_DIR'.
"
usage() {
echo "$USAGE"
exit 0
}
fail() {
echo "Error: $1"
usage
exit 1
}
if [ "$#" -gt "0" ]; then
case "$1" in
--help|-h)
shift
usage
;;
--config-dir)
CONFIG_DIR="$2"
shift 2
;;
esac
shift
fi
if ! [ "$#" -eq "0" ]; then
fail "Unknown argument(s): $*"
fi
mkdir -p "$CONFIG_DIR"
cd "$CONFIG_DIR" || fail "Could not 'cd' to '$CONFIG_DIR'"
java -jar /usr/share/Unciv/UncivServer.jar

View File

@ -0,0 +1,12 @@
[Desktop Entry]
Comment=Open-source Android/Desktop remake of Civ V, Multiplayer Server
Exec=UncivServer
GenericName=4X Game
Icon=unciv
Name=Unciv
NoDisplay=false
StartupNotify=true
Terminal=true
Type=Application
Categories=Game
Name[en_US]=uncivserver.desktop

52
server/packrConfig.json Normal file
View File

@ -0,0 +1,52 @@
{
"reduce": [
{
"archive": "jre/lib/rt.jar",
"paths": [
"com/sun/corba",
"com/sun/jmx",
"com/sun/jndi",
"com/sun/media",
"com/sun/naming",
"com/sun/org",
"com/sun/rowset",
"com/sun/script",
"com/sun/xml",
"sun/applet",
"sun/corba",
"sun/management"
]
},
{
"archive": "jre/lib/charsets.jar",
"paths": [
]
},
{
"archive": "jre/lib/jsse.jar",
"paths": [
]
},
{
"archive": "jre/lib/resources.jar",
"paths": [
]
}
],
"remove": [
{
"platform": "*",
"paths": [
"jre/lib/rhino.jar",
"jre/lib/amd64/libjfxwebkit.so"
]
},
{
"platform": "windows",
"paths": [
"jre/bin/*.exe",
"jre/bin/client"
]
}
]
}

View File

@ -1,4 +1,4 @@
package com.unciv.app.desktop
package com.unciv.app.server
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.parameters.options.default
@ -43,6 +43,7 @@ private class UncivServerRunner : CliktCommand() {
embeddedServer(Netty, port = serverPort) {
routing {
get("/isalive") {
println("Received isalive request from ${call.request.local.remoteHost}")
call.respondText("true")
}
put("/files/{fileName}") {

View File

@ -1 +1 @@
include("desktop", "android", "ios", "core", "tests")
include("desktop", "android", "ios", "core", "tests", "server")