From 59dc3613a0ef751239c0efdffc89fc00047fbb14 Mon Sep 17 00:00:00 2001 From: Jonn Date: Thu, 14 Aug 2025 22:56:26 +0900 Subject: [PATCH 1/2] Add lag.proto and proof of concept for using them in the notebook. --- Notebook.ipynb | 64 ++++++++++++++++++++++--------- build.gradle.kts | 53 ++++++++++++++++++++++++- src/main/proto/lag.proto | 83 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 179 insertions(+), 21 deletions(-) create mode 100644 src/main/proto/lag.proto diff --git a/Notebook.ipynb b/Notebook.ipynb index 64e0991..0ec840e 100644 --- a/Notebook.ipynb +++ b/Notebook.ipynb @@ -2,7 +2,9 @@ "cells": [ { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import com.hopskipnfall.*\n", "import java.io.File\n", @@ -29,7 +31,7 @@ "val clients =\n", " listOf(\n", " Client(id = 0, frameDelay = 1, WIFI),\n", - " Client(id = 1, frameDelay = 1, WIRED),\n", + "// Client(id = 1, frameDelay = 1, WIRED),\n", " )\n", "val server = Server(clients)\n", "for (client in clients) {\n", @@ -38,7 +40,7 @@ "}\n", "\n", "while (now <= 1.minutes) {\n", - " server.run(now, diagramBuilder, frameDriftLogger)\n", + " server.run(now, diagramBuilder, frameDriftLogger, objectiveLagLogger)\n", " for (it in clients) it.run(now, frameNumberLogger, diagramBuilder, objectiveLagLogger, TIME_STEP)\n", "\n", " now += TIME_STEP\n", @@ -48,13 +50,13 @@ "}\n", "server.lagstat(now)\n", "\n" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "%use kandy, dataframe\n", "import com.github.nwillc.ksvg.RenderMode\n", @@ -75,29 +77,53 @@ " color(\"Client\")\n", " }\n", "}" - ], - "outputs": [], - "execution_count": null + ] }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "%use kandy, dataframe\n", "\n", - "objectiveLagLogger.buildDataFrame().plot {\n", - " points {\n", - " x(\"Timstamp (seconds)\")\n", - " y(\"Objective lag in a single frame (ms)\")\n", - "\n", - " color(\"Client\")\n", - " }\n", + "val df = objectiveLagLogger.buildDataFrame()\n", "\n", - " layout { title = \"Objective lag experienced by clients\" }\n", - "}\n" - ], + "df\n", + "//df.plot {\n", + "// points {\n", + "// x(\"Timstamp (seconds)\")g\n", + "// y(\"Objective lag in a single frame (ms)\")\n", + "//\n", + "// color(\"Client\")\n", + "// }\n", + "//\n", + "// layout { title = \"Objective lag experienced by clients\" }\n", + "//}\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Optional: Load in a `GameLag` record:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, "outputs": [], - "execution_count": null + "source": [ + "import java.io.FileInputStream\n", + "import org.emulinker.proto.GameLog\n", + "\n", + "FileInputStream(\"gamelog_3pall1f.bin\").use { fileInputStream ->\n", + " val log = GameLog.parseFrom(fileInputStream.readBytes())\n", + " println(\"Number of events: ${log.eventsList.size}\")\n", + " log.eventsList.take(20).map { it.eventTypeCase }\n", + "}" + ] } ], "metadata": { diff --git a/build.gradle.kts b/build.gradle.kts index 8c9cb76..d90075c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,9 @@ +import build.buf.gradle.BUF_BINARY_CONFIGURATION_NAME +import com.google.protobuf.gradle.id + plugins { + id("com.google.protobuf") version "0.9.5" + id("build.buf") version "0.10.2" id("com.diffplug.spotless") version "6.25.0" application @@ -20,6 +25,10 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlin-statistics-jvm:0.3.0") implementation("com.github.nwillc.ksvg:ksvg:master-SNAPSHOT") + + implementation("com.google.protobuf:protobuf-kotlin:4.31.1") + implementation("com.google.protobuf:protobuf-java:4.31.1") + implementation("com.google.protobuf:protobuf-java-util:4.31.1") } group = "com.hopskipnfall" @@ -30,10 +39,22 @@ version = "0.12.0" kotlin { jvmToolchain(17) } +tasks.processResources { + // Fails to compile without this. + // https://github.com/google/protobuf-gradle-plugin/issues/522#issuecomment-1195266995 + duplicatesStrategy = DuplicatesStrategy.EXCLUDE +} + sourceSets { - main { kotlin.srcDir("src/main/java") } + main { + proto.srcDir("src/main/proto") + kotlin.srcDir("src/main/java") + } - test { kotlin.srcDir("src/test/java") } + test { + proto.srcDir("src/main/proto") + kotlin.srcDir("src/test/java") + } } tasks.withType { @@ -46,8 +67,21 @@ tasks.withType { ) } +// Disable formatting via buf plugin directly. We just need it for the binary. +buf { enforceFormat = false } + +tasks.named("bufLint") { enabled = false } + // Formatting/linting. spotless { + protobuf { + buf("1.46.0") + .pathToExe( + configurations.getByName(BUF_BINARY_CONFIGURATION_NAME).getSingleFile().getAbsolutePath() + ) + target("src/**/*.proto") + } + kotlin { target("**/*.kt", "**/*.kts") targetExclude("build/", ".git/", ".idea/", ".mvn", "src/main/java-templates/") @@ -61,4 +95,19 @@ spotless { } } +protobuf { + protoc { artifact = "com.google.protobuf:protoc:4.31.1" } + + generateProtoTasks { + ofSourceSet("main").forEach { + it.plugins { + // Generates Kotlin DSL builders. + id("kotlin") {} + } + } + } +} + +tasks.named("compileKotlin") { dependsOn(":generateProto") } + application { mainClass.set("com.hopskipnfall.MainKt") } diff --git a/src/main/proto/lag.proto b/src/main/proto/lag.proto new file mode 100644 index 0000000..124fa4b --- /dev/null +++ b/src/main/proto/lag.proto @@ -0,0 +1,83 @@ +// This file should be kept up-to-date with the copy in the ELK repo: +// https://github.com/hopskipnfall/EmuLinker-K/blob/master/emulinker/src/main/proto/lag.proto +// +// How to use protos: https://protobuf.dev/programming-guides/editions +edition = "2023"; + +package org.emulinker.proto; + +import "google/protobuf/timestamp.proto"; + +option java_multiple_files = true; + +enum PlayerNumber { + // Note: In some languages having multiple enum values with the same + // name causes issues so it's good to prefix with the name. + PLAYER_NUMBER_UNKNOWN = 0; + ONE = 1; + TWO = 2; + THREE = 3; + FOUR = 4; +} + +message Event { + // Number of nanoseconds from an arbitrary point of time in the past. + // This can be used to check elapsed time between events for a single + // game, but not necessarily between events between two games. + int64 timestamp_ns = 1; + + message GameStart { + message PlayerDetails { + PlayerNumber player_number = 1; + + // User's average ping when they joined the server. + double ping_ms = 2; + + int32 frame_delay = 3; + } + + repeated PlayerDetails players = 1; + + google.protobuf.Timestamp timestamp = 2; + } + + message ReceivedGameData { + PlayerNumber received_from = 1; + } + + // Server has collected data from all parties for the frame and will + // now combine and broadcast them back out to each player. + message FanOut {} + + // It might be useful to know what the /lagstat command would say + // as a point of comparison. + message LagstatSummary { + // The duration over which lag is being measured. + int32 window_duration_ms = 1; + + // The measured drift over the window + double game_lag_ms = 2; + + message PlayerAttributedLag { + PlayerNumber player = 1; + + // How much of the drift can be reliably attributed to the + // player. + double attributed_lag_ms = 2; + } + + repeated PlayerAttributedLag player_attributed_lags = 3; + } + + oneof event_type { + GameStart game_start = 2; + ReceivedGameData received_game_data = 3; + FanOut fan_out = 4; + LagstatSummary lagstat_summary = 5; + } +} + +// All metadata stored about the game. +message GameLog { + repeated Event events = 1; +} From 7c1cb8914d57dd9ac053eb1c92b742083def1ad8 Mon Sep 17 00:00:00 2001 From: Jonn Date: Thu, 14 Aug 2025 23:00:32 +0900 Subject: [PATCH 2/2] Oops, revert some unrelated changes. --- Notebook.ipynb | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/Notebook.ipynb b/Notebook.ipynb index 0ec840e..4be318a 100644 --- a/Notebook.ipynb +++ b/Notebook.ipynb @@ -31,7 +31,7 @@ "val clients =\n", " listOf(\n", " Client(id = 0, frameDelay = 1, WIFI),\n", - "// Client(id = 1, frameDelay = 1, WIRED),\n", + " Client(id = 1, frameDelay = 1, WIRED),\n", " )\n", "val server = Server(clients)\n", "for (client in clients) {\n", @@ -40,7 +40,7 @@ "}\n", "\n", "while (now <= 1.minutes) {\n", - " server.run(now, diagramBuilder, frameDriftLogger, objectiveLagLogger)\n", + " server.run(now, diagramBuilder, frameDriftLogger)\n", " for (it in clients) it.run(now, frameNumberLogger, diagramBuilder, objectiveLagLogger, TIME_STEP)\n", "\n", " now += TIME_STEP\n", @@ -87,19 +87,16 @@ "source": [ "%use kandy, dataframe\n", "\n", - "val df = objectiveLagLogger.buildDataFrame()\n", + "objectiveLagLogger.buildDataFrame().plot {\n", + " points {\n", + " x(\"Timstamp (seconds)\")\n", + " y(\"Objective lag in a single frame (ms)\")\n", + "\n", + " color(\"Client\")\n", + " }\n", "\n", - "df\n", - "//df.plot {\n", - "// points {\n", - "// x(\"Timstamp (seconds)\")g\n", - "// y(\"Objective lag in a single frame (ms)\")\n", - "//\n", - "// color(\"Client\")\n", - "// }\n", - "//\n", - "// layout { title = \"Objective lag experienced by clients\" }\n", - "//}\n" + " layout { title = \"Objective lag experienced by clients\" }\n", + "}" ] }, { @@ -112,7 +109,12 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "ExecuteTime": { + "end_time": "2025-08-14T13:59:42.613501Z", + "start_time": "2025-08-14T13:59:42.485496Z" + } + }, "outputs": [], "source": [ "import java.io.FileInputStream\n",