From a0c330384a5e61cad2ddea75d5c43afb580bdefd Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Tue, 4 Jan 2022 15:09:50 +0100 Subject: [PATCH] it is literally working --- README.md | 11 ++- gradle/kotlin-js-store/yarn.lock | 54 ++++++++++ gradle/libs.versions.toml | 7 +- .../main/typescript/schema/events-schema.ts | 10 ++ .../src/main/typescript/serdes/serdes.ts | 13 +++ modules/events-schema/build.gradle.kts | 6 +- .../events/schema/factorioEvents.kt | 99 ++++++++++++++----- .../events/schema/FactorioEventTest.kt | 8 +- modules/web-map/build.gradle.kts | 45 ++++++--- .../dev/adamko/kafkatorio/webmap/App.kt | 75 ++++++++++---- .../adamko/kafkatorio/webmap/converters.kt | 36 +++++++ .../dev/adamko/kafkatorio/webmap/json.kt | 7 ++ .../dev/adamko/kafkatorio/webmap/map.kt | 37 +++++++ .../dev/adamko/kafkatorio/webmap/state.kt | 92 +++++++++++++++++ .../adamko/kafkatorio/webmap/websockets.kt | 46 +++++++++ modules/web-map/src/main/web/index.html | 5 +- settings.gradle.kts | 11 +++ 17 files changed, 499 insertions(+), 63 deletions(-) create mode 100644 modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt create mode 100644 modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/json.kt create mode 100644 modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt create mode 100644 modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt create mode 100644 modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt diff --git a/README.md b/README.md index b2ee244..5fa1128 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,14 @@ is provided. * https://jbrandhorst.com/post/go-protobuf-tips/ + +##### wasm + +kt -> wasm -> lua? +kt-flatbuffers -> wasm -> lua? + +https://www.npmjs.com/package/wasm2lua?activeTab=readme + ##### flatbuffers https://github.com/dvidelabs/flatcc @@ -56,6 +64,3 @@ https://github.com/dvidelabs/flatcc https://www.zash.se/lua-cbor.html https://code.zash.se/lua-cbor/archive/68b3a36d0816.tar.gz - - - diff --git a/gradle/kotlin-js-store/yarn.lock b/gradle/kotlin-js-store/yarn.lock index 18bda50..09534e0 100644 --- a/gradle/kotlin-js-store/yarn.lock +++ b/gradle/kotlin-js-store/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@babel/runtime@^7.9.2": + version "7.16.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.5.tgz#7f3e34bf8bdbbadf03fbb7b1ea0d929569c9487a" + integrity sha512-TXWihFIS3Pyv5hzR7j6ihmeLkZfrXGxAr5UfSl8CHf+6q/wpiYDkUau0czckpYG8QmnCIuPpdLtuA9VmuGGyMA== + dependencies: + regenerator-runtime "^0.13.4" + "@discoveryjs/json-ext@^0.5.0": version "0.5.6" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f" @@ -81,6 +88,11 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83" integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw== +"@types/geojson@*", "@types/geojson@7946.0.8": + version "7946.0.8" + resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.8.tgz#30744afdb385e2945e22f3b033f897f76b1f12ca" + integrity sha512-1rkryxURpr6aWP7R786/UQOkJ3PcpQiWkAXBmdWc7ryFWqN6a4xfK7BtjXvFBKO9LjQ+MWQSWxYeZX1OApnArA== + "@types/glob@5 - 7": version "7.2.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb" @@ -101,6 +113,13 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== +"@types/leaflet@^1.7.7": + version "1.7.7" + resolved "https://registry.yarnpkg.com/@types/leaflet/-/leaflet-1.7.7.tgz#3c9f629126e03405ed68cc1aa395e32df3312376" + integrity sha512-5/nNqTviSqUwPxbS3PlHl8ODjfnlpOXSFtUAi7dexnisO8E3pfyotChRY/hlIYGvbBiBcKNd6V18xA8O4OpHiQ== + dependencies: + "@types/geojson" "*" + "@types/minimatch@*": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -949,6 +968,14 @@ dukat@0.5.8-rc.3: google-protobuf "3.12.2" typescript "3.9.5" +dukat@0.5.8-rc.4: + version "0.5.8-rc.4" + resolved "https://registry.yarnpkg.com/dukat/-/dukat-0.5.8-rc.4.tgz#90384dcb50b14c26f0e99dae92b2dea44f5fce21" + integrity sha512-ZnMt6DGBjlVgK2uQamXfd7uP/AxH7RqI0BL9GLrrJb2gKdDxvJChWy+M9AQEaL+7/6TmxzJxFOsRiInY9oGWTA== + dependencies: + google-protobuf "3.12.2" + typescript "3.9.5" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1280,6 +1307,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +geojson@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/geojson/-/geojson-0.5.0.tgz#3cd6c96399be65b56ee55596116fe9191ce701c0" + integrity sha1-PNbJY5m+ZbVu5VWWEW/pGRznAcA= + get-caller-file@^2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" @@ -1845,6 +1877,11 @@ kvision-assets@^5.0.7: resolved "https://registry.yarnpkg.com/kvision-assets/-/kvision-assets-5.0.7.tgz#e6dc3e28a2d244b4c89318b060d3e7e337c15495" integrity sha512-szoL8ixFRTivLkbnmkWFjGJMk3bsUW/loUS5QfWThPwFnyFEw8ht3eBUrJ0V51Rn/WaJbVoPvY4SfIaiNu13bg== +leaflet@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" + integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== + loader-runner@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384" @@ -2430,6 +2467,23 @@ rechoir@^0.7.0: dependencies: resolve "^1.9.0" +redux-thunk@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" + integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== + +redux@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" + integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== + dependencies: + "@babel/runtime" "^7.9.2" + +regenerator-runtime@^0.13.4: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + regexp.prototype.flags@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.3.1.tgz#7ef352ae8d159e758c0eadca6f8fcb4eef07be26" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index cd4b7ac..d544e8c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,7 +13,8 @@ camel-kafkaConnectors = "0.11.0" http4k = "4.17.6.0" kafka = "3.0.0" kotlinx-serialization = "1.3.2" -kvision = "5.6.1" +#kvision = "5.6.1" +kvision = "5.6.2-SNAPSHOT" log4j2 = "2.14.1" qosLogback = "1.2.5" slf4j = "1.7.30" @@ -50,8 +51,12 @@ kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx- kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core" } kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json" } kotlinx-serialization-yaml = { group = "com.charleskorn.kaml", name = "kaml", version = "0.37.0" } + kotlinx-coroutines = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version = "1.6.0" } + kotlinx-html = { group = "org.jetbrains.kotlinx", name = "kotlinx-html", version = "0.7.3" } +kotlinx-nodejs = { group = "org.jetbrains.kotlinx", name = "kotlinx-nodejs", version = "0.0.7" } + ## http4k ## http4k-bom = { group = "org.http4k", name = "http4k-bom", version.ref = "http4k" } diff --git a/modules/events-mod/src/main/typescript/schema/events-schema.ts b/modules/events-mod/src/main/typescript/schema/events-schema.ts index fccd740..88aa0db 100644 --- a/modules/events-mod/src/main/typescript/schema/events-schema.ts +++ b/modules/events-mod/src/main/typescript/schema/events-schema.ts @@ -11,6 +11,13 @@ interface FactorioEvent { tick: uint; } +interface Colour { + alpha: float; + blue: float; + green: float; + red: float; +} + interface PositionData { x: double; y: double; @@ -19,6 +26,9 @@ interface PositionData { interface PlayerData extends FactorioObjectData { associated_characters_unit_numbers: uint[]; character_unit_number: uint | null; + chat_colour: Colour; + colour: Colour; + last_online: uint; name: string; object_name: string; position: PositionData; diff --git a/modules/events-mod/src/main/typescript/serdes/serdes.ts b/modules/events-mod/src/main/typescript/serdes/serdes.ts index 76425c4..128f219 100644 --- a/modules/events-mod/src/main/typescript/serdes/serdes.ts +++ b/modules/events-mod/src/main/typescript/serdes/serdes.ts @@ -10,6 +10,9 @@ export namespace Serdes { character_unit_number: player.character?.unit_number ?? null, associated_characters_unit_numbers: charIds, position: positionTableToTable(player.position), + colour: mapColour(player.color), + chat_colour: mapColour(player.chat_color), + last_online: player.last_online } } @@ -62,4 +65,14 @@ export namespace Serdes { } } + export function mapColour(color: Color): Colour { + color = (color as ColorTable) + return { + red: color.r ?? 0, + green: color.g ?? 0, + blue: color.b ?? 0, + alpha: color.a ?? 0, + } + } + } diff --git a/modules/events-schema/build.gradle.kts b/modules/events-schema/build.gradle.kts index ad6a372..94ce097 100644 --- a/modules/events-schema/build.gradle.kts +++ b/modules/events-schema/build.gradle.kts @@ -30,9 +30,9 @@ kotlin { val commonMain by getting { dependencies { - implementation(project.dependencies.enforcedPlatform(libs.kotlin.bom)) + implementation(project.dependencies.platform(libs.kotlin.bom)) - implementation(project.dependencies.enforcedPlatform(libs.kotlinx.serialization.bom)) + implementation(project.dependencies.platform(libs.kotlinx.serialization.bom)) implementation(libs.kotlinx.serialization.core) implementation(libs.kotlinx.serialization.json) @@ -44,7 +44,7 @@ kotlin { implementation(kotlin("test")) - implementation(project.dependencies.enforcedPlatform(libs.kotest.bom)) + implementation(project.dependencies.platform(libs.kotest.bom)) implementation("io.kotest:kotest-assertions-core") implementation("io.kotest:kotest-property") implementation("io.kotest:kotest-assertions-json") diff --git a/modules/events-schema/src/commonMain/kotlin/dev/adamko/kafkatorio/events/schema/factorioEvents.kt b/modules/events-schema/src/commonMain/kotlin/dev/adamko/kafkatorio/events/schema/factorioEvents.kt index 81090ed..ebf5d33 100644 --- a/modules/events-schema/src/commonMain/kotlin/dev/adamko/kafkatorio/events/schema/factorioEvents.kt +++ b/modules/events-schema/src/commonMain/kotlin/dev/adamko/kafkatorio/events/schema/factorioEvents.kt @@ -11,13 +11,18 @@ import kotlinx.serialization.json.JsonClassDiscriminator @Serializable data class FactorioEvent( /** the initial Factorio event ({defines.events}) trigger */ - @SerialName("event_type") val eventType: String, + @SerialName("event_type") + val eventType: String, /** Schema versioning */ - @SerialName("mod_version") val modVersion: String, - @SerialName("factorio_version") val factorioVersion: String, + @SerialName("mod_version") + val modVersion: String, + @SerialName("factorio_version") + val factorioVersion: String, /** game time */ - @SerialName("tick") val tick: UInt, - @SerialName("data") val data: T + @SerialName("tick") + val tick: UInt, + @SerialName("data") + val data: T ) @@ -53,10 +58,20 @@ sealed class FactorioObjectData { @SerialName("LuaPlayer") data class PlayerData( @Serializable(with = ListAsObjectSerializer::class) - @SerialName("associated_characters_unit_numbers") val associatedCharactersUnitNumbers: List, - @SerialName("character_unit_number") val characterUnitNumber: UInt?, - @SerialName("name") val name: String, - @SerialName("position") val position: PositionData, + @SerialName("associated_characters_unit_numbers") + val associatedCharactersUnitNumbers: List, + @SerialName("character_unit_number") + val characterUnitNumber: UInt?, + @SerialName("name") + val name: String, + @SerialName("position") + val position: PositionData, + @SerialName("colour") + val colour: Colour, + @SerialName("chat_colour") + val chatColour: Colour, + @SerialName("last_online") + val lastOnline: UInt, ) : FactorioObjectData() { @Transient override val objectName: String = "LuaPlayer" @@ -65,16 +80,25 @@ data class PlayerData( @Serializable @SerialName("LuaEntity") data class EntityData( - @SerialName("active") val active: Boolean, - @SerialName("health") val health: Double?, - @SerialName("health_ratio") val healthRatio: Double, - @SerialName("name") val name: String, - @SerialName("position") val position: PositionData, - @SerialName("surface_index") val surfaceIndex: Int, - @SerialName("type") val type: String, - @SerialName("unit_number") val unitNumber: UInt? = null, + @SerialName("active") + val active: Boolean, + @SerialName("health") + val health: Double?, + @SerialName("health_ratio") + val healthRatio: Double, + @SerialName("name") + val name: String, + @SerialName("position") + val position: PositionData, + @SerialName("surface_index") + val surfaceIndex: Int, + @SerialName("type") + val type: String, + @SerialName("unit_number") + val unitNumber: UInt? = null, - @SerialName("player_index") val playerIndex: UInt? = null, + @SerialName("player_index") + val playerIndex: UInt? = null, ) : FactorioObjectData() { @Transient override val objectName: String = "LuaEntity" @@ -83,9 +107,12 @@ data class EntityData( @Serializable @SerialName("LuaSurface") data class SurfaceData( - @SerialName("daytime") val daytime: Double, - @SerialName("index") val index: UInt, - @SerialName("name") val name: String, + @SerialName("daytime") + val daytime: Double, + @SerialName("index") + val index: UInt, + @SerialName("name") + val name: String, ) : FactorioObjectData() { @Transient override val objectName: String = "LuaSurface" @@ -93,6 +120,32 @@ data class SurfaceData( @Serializable data class PositionData( - @SerialName("x") val x: Double, - @SerialName("y") val y: Double, + val x: Double, + val y: Double, ) + +/** + * Red, green, blue and alpha values, all in range `[0, 1]` or all in range `[0, 255]` if any + * value is > 1. + * + * All values here are optional. Color channels default to 0, the alpha channel defaults to 1. + */ +@Serializable +data class Colour( + val red: Float = 0f, + val green: Float = 0f, + val blue: Float = 0f, + val alpha: Float = 1f, +) { + /** True if any value is greater than 1, so the values are hexadecimal. */ + fun isDecimal(): Boolean { + return red > 1f + || green > 1f + || blue > 1f + || alpha > 1f + } + + /** True if all values are between `[0..1]`. */ + fun isPercentage(): Boolean = !isDecimal() + +} diff --git a/modules/events-schema/src/jvmTest/kotlin/dev/adamko/kafkatorio/events/schema/FactorioEventTest.kt b/modules/events-schema/src/jvmTest/kotlin/dev/adamko/kafkatorio/events/schema/FactorioEventTest.kt index 8bae62f..312eda6 100644 --- a/modules/events-schema/src/jvmTest/kotlin/dev/adamko/kafkatorio/events/schema/FactorioEventTest.kt +++ b/modules/events-schema/src/jvmTest/kotlin/dev/adamko/kafkatorio/events/schema/FactorioEventTest.kt @@ -21,7 +21,8 @@ class FactorioEventTest : BehaviorSpec({ "position": { "x": -3.5703125, "y": 29.75 - } + }, + "last_online": 123 }, "event_type": "on_player_joined_game", "mod_version": "0.0.4", @@ -41,7 +42,10 @@ class FactorioEventTest : BehaviorSpec({ position = PositionData( x = -3.5703125, y = 29.75 - ) + ), + colour = Colour(), + chatColour = Colour(), + lastOnline = 123u ), eventType = "on_player_joined_game", modVersion = "0.0.4", diff --git a/modules/web-map/build.gradle.kts b/modules/web-map/build.gradle.kts index 16522c2..fb7a2e2 100644 --- a/modules/web-map/build.gradle.kts +++ b/modules/web-map/build.gradle.kts @@ -1,9 +1,9 @@ +import org.jetbrains.kotlin.gradle.plugin.KotlinDependencyHandler import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig plugins { id("dev.adamko.kafkatorio.lang.kotlin-js") kotlin("plugin.serialization") -// kotlin("js") // version kotlinVersion id("io.kvision") version "5.6.1" } @@ -13,7 +13,7 @@ kotlin { browser { runTask { outputFileName = "main.bundle.js" - sourceMaps = false + sourceMaps = true devServer = KotlinWebpackConfig.DevServer( open = false, port = 3000, @@ -37,31 +37,54 @@ kotlin { } sourceSets { - val kvisionVersion: String = libs.versions.kvision.get() + + all { + languageSettings.optIn("kotlin.RequiresOptIn") + languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi") + } main { dependencies { + implementation(projects.modules.eventsSchema) + implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7") + implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.3") - implementation("io.kvision:kvision:$kvisionVersion") - implementation("io.kvision:kvision-bootstrap:$kvisionVersion") - implementation("io.kvision:kvision-bootstrap-css:$kvisionVersion") - implementation("io.kvision:kvision-bootstrap-icons:$kvisionVersion") - implementation("io.kvision:kvision-fontawesome:$kvisionVersion") - implementation("io.kvision:kvision-state:$kvisionVersion") - implementation("io.kvision:kvision-chart:$kvisionVersion") + kvision("kvision") + kvision("kvision-bootstrap") + kvision("kvision-bootstrap-css") + kvision("kvision-bootstrap-icons") + kvision("kvision-fontawesome") + kvision("kvision-state") + kvision("kvision-chart") + kvision("kvision-maps") + kvision("kvision-redux") + kvision("kvision-state-flow") + implementation(libs.kotlinx.coroutines) } val webDir = file("src/main/web") resources.srcDir(webDir) } + test { dependencies { implementation(kotlin("test-js")) - implementation("io.kvision:kvision-testutils:$kvisionVersion") + + kvision("kvision-testutils") } } } } + +fun KotlinDependencyHandler.kvision( + module: String, + version: Provider = libs.versions.kvision, + configure: ExternalModuleDependency.() -> Unit = { + isChanging = true + } +) { + implementation("io.kvision:$module:${version.get()}", configure) +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt index 0e6bcce..762d111 100644 --- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/App.kt @@ -1,34 +1,73 @@ package dev.adamko.kafkatorio.webmap import io.kvision.Application -import io.kvision.CoreModule -import io.kvision.BootstrapModule import io.kvision.BootstrapCssModule import io.kvision.BootstrapIconsModule -import io.kvision.FontAwesomeModule +import io.kvision.BootstrapModule import io.kvision.ChartModule +import io.kvision.CoreModule +import io.kvision.FontAwesomeModule import io.kvision.html.div +import io.kvision.maps.Maps import io.kvision.module import io.kvision.panel.root +import io.kvision.redux.ReduxStore import io.kvision.startApplication -class App : Application() { - override fun start() { - root("kvapp") { - div("Hello world") - } + +class App( + reduxStore: ReduxStore, + wsService: WebsocketService +) : Application() { + private var appState: MutableMap = mutableMapOf() + + var reduxStore: ReduxStore by appState + var wsService: WebsocketService by appState + + init { + this.reduxStore = reduxStore + this.wsService = wsService + } + + private val kvMaps: Maps + get() = reduxStore.getState().kvMaps + + override fun start(state: Map) { + this.appState = state.toMutableMap() + + root("kvapp") { + div("Kafkatorio Web Map") + add(kvMaps) } + } + + override fun dispose(): Map { + return appState + } + } + fun main() { - startApplication( - ::App, - module.hot, - BootstrapModule, - BootstrapCssModule, - BootstrapIconsModule, - FontAwesomeModule, - ChartModule, - CoreModule - ) + startApplication( + { + val reduxStore = createFactorioReduxStore() + val wsService = WebsocketService( + "ws://localhost:9073/ws", + reduxStore, + ) + + App( + reduxStore = reduxStore, + wsService = wsService + ) + }, + module.hot, + BootstrapModule, + BootstrapCssModule, + BootstrapIconsModule, + FontAwesomeModule, + ChartModule, + CoreModule + ) } diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt new file mode 100644 index 0000000..bd83abe --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/converters.kt @@ -0,0 +1,36 @@ +package dev.adamko.kafkatorio.webmap + +import dev.adamko.kafkatorio.events.schema.Colour +import dev.adamko.kafkatorio.events.schema.PlayerData +import externals.leaflet.geo.LatLng +import io.kvision.maps.Maps +import kotlin.math.roundToInt + + +fun Colour.toHexString(includeAlpha: Boolean = false): String { + + val hexConverter: Float.() -> String = if (isDecimal()) { + Float::decimalToHex + } else { + Float::percentageToHex + } + + val length = if (includeAlpha) 9 else 7 + return buildString(length) { + append('#') + append(red.hexConverter()) + append(green.hexConverter()) + append(blue.hexConverter()) + if (includeAlpha) { + append(alpha.hexConverter()) + } + } +} + +fun Float.decimalToHex(): String = + roundToInt().toString(16).padStart(2, ' ') + +fun Float.percentageToHex(): String = + times(255).roundToInt().toString(16).padStart(2, ' ') + +fun PlayerData.latLng(): LatLng = Maps.L.latLng((position.y * -1) to position.x) diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/json.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/json.kt new file mode 100644 index 0000000..d1acf02 --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/json.kt @@ -0,0 +1,7 @@ +package dev.adamko.kafkatorio.webmap + +import kotlinx.serialization.json.Json + +val jsonMapper = Json { + prettyPrint = true +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt new file mode 100644 index 0000000..b4233f1 --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/map.kt @@ -0,0 +1,37 @@ +package dev.adamko.kafkatorio.webmap + +import externals.leaflet.geo.CRS +import externals.leaflet.geo.LatLng +import io.kvision.maps.DefaultTileLayers +import io.kvision.maps.Maps +import io.kvision.utils.px + +// +//val polyline = Maps.L.polyline( +// listOf( +// LatLng(55, 2), +// LatLng(65, 2), +// LatLng(65, 20), +// LatLng(55, 20), +// LatLng(55, 2), +// ) +//) { +// noClip = true +//} + +fun createFactorioMap() = Maps { + width = 800.px + height = 800.px + margin = 10.px + + configureLeafletMap { + setView(LatLng(0, 0), 13) + +// DefaultTileLayers.OpenStreetMap.addTo(this) + + with(options) { + crs = CRS.Simple + } + + } +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt new file mode 100644 index 0000000..ec1c63d --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/state.kt @@ -0,0 +1,92 @@ +package dev.adamko.kafkatorio.webmap + +import dev.adamko.kafkatorio.events.schema.FactorioEvent +import dev.adamko.kafkatorio.events.schema.PlayerData +import externals.leaflet.geo.LatLng +import externals.leaflet.layer.overlay.Tooltip +import externals.leaflet.layer.vector.CircleMarker +import io.kvision.maps.Maps +import io.kvision.maps.Maps.Companion.L +import io.kvision.redux.ReduxStore +import io.kvision.redux.createReduxStore +import io.kvision.utils.obj +import redux.RAction + +fun createFactorioReduxStore(): ReduxStore = + createReduxStore( + ::factorioGameReducer, + FactorioGameState( + kvMaps = createFactorioMap(), + players = emptyMap() + ) + ) + +data class FactorioGameState( + val kvMaps: Maps, + val players: Map, +) + + +sealed class FactorioUpdate : RAction { + data class PlayerUpdate(val event: FactorioEvent<*>, val playerData: PlayerData) : + FactorioUpdate() +} + +fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState = + when (update) { + is FactorioUpdate.PlayerUpdate -> { + + val playerState = state.players.getOrElse(update.playerData.name) { + val newState = PlayerState(update.playerData) + state.kvMaps.leafletMap { + addLayer(newState.mapMarker) + newState.mapMarker.openPopup() +// setView(update.playerData.latLng()) + } + newState + } + + playerState.update(update.playerData) + + val updatedPlayersState = state.players + (update.playerData.name to playerState) + state.copy(players = updatedPlayersState) + } + } + + +data class PlayerState( + private var playerData: PlayerData, + val mapMarker: CircleMarker = createMapMarker(playerData) +) { + + fun update(playerData: PlayerData) { + this.playerData = playerData + mapMarker.setLatLng(playerData.latLng()) + println("updated player ${playerData.name}") + } + + companion object { + + fun createMapMarker(playerData: PlayerData): CircleMarker { + + val playerLatLng = playerData.latLng() + val playerMapTooltip = "${playerData.name} [$playerLatLng]" + + val circle = L.circleMarker(playerLatLng) { + color = playerData.colour.toHexString(false) + fillColor = color + fillOpacity = 1f + radius = 10 + className = playerData.objectName +// interactive = false + } + + circle.bindTooltip(playerMapTooltip, obj { +// interactive = false + }) + + return circle + } + } + +} diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt new file mode 100644 index 0000000..fdc7f95 --- /dev/null +++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/websockets.kt @@ -0,0 +1,46 @@ +package dev.adamko.kafkatorio.webmap + +import dev.adamko.kafkatorio.events.schema.FactorioEvent +import dev.adamko.kafkatorio.events.schema.FactorioObjectData +import dev.adamko.kafkatorio.events.schema.PlayerData +import io.kvision.redux.ReduxStore +import kotlinx.serialization.decodeFromString +import org.w3c.dom.MessageEvent +import org.w3c.dom.WebSocket + +class WebsocketService( + private val wsUrl: String, + private val reduxStore: ReduxStore, +) { + private val ws = WebSocket(wsUrl) + + init { + ws.onmessage = ::handleMessageEvent + } + + private fun handleMessageEvent(msg: MessageEvent) { + + val data = msg.data as? String + + if ( + data != null + && data.trim().startsWith("{") + && data.trim().endsWith("}") + ) { + + println(data.replace('\n', ' ')) + val event: FactorioEvent = jsonMapper.decodeFromString(data) + + when (val eventData = event.data) { + is PlayerData -> { + reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData)) + } + else -> {} + } + } else { + println(data) + + } + + } +} diff --git a/modules/web-map/src/main/web/index.html b/modules/web-map/src/main/web/index.html index f7c610c..53f5626 100644 --- a/modules/web-map/src/main/web/index.html +++ b/modules/web-map/src/main/web/index.html @@ -1,9 +1,10 @@ - + - KVision Template + Kafkatorio Web Map + diff --git a/settings.gradle.kts b/settings.gradle.kts index 29bcb98..d05230b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -21,3 +21,14 @@ enableFeaturePreview("VERSION_CATALOGS") enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") apply(from = "./buildSrc/repositories.settings.gradle.kts") + +@Suppress("UnstableApiUsage") // centralized repositories is incubating +dependencyResolutionManagement { + repositories { + mavenLocal { + content { + includeGroup("io.kvision") + } + } + } +}