From de419aeae8b055d56732cdad1d15775141a6beb9 Mon Sep 17 00:00:00 2001
From: Adam <897017+aSemy@users.noreply.github.com>
Date: Sat, 3 Sep 2022 11:15:43 +0200
Subject: [PATCH] web-map tidy up, and add diagram to README
---
README.md | 38 ++++++++++++++++++-
.../dev/adamko/kafkatorio/webmap/App.kt | 24 ++----------
.../webmap/config/ApplicationProperties.kt | 2 +
.../kafkatorio/webmap/layout/headerNav.kt | 13 ++++---
.../kafkatorio/webmap/layout/homePage.kt | 2 +-
.../webmap/layout/servers/serverPage.kt | 2 +-
.../dev/adamko/kafkatorio/webmap/map.kt | 7 ++--
.../webmap/services/WebsocketService.kt | 10 ++---
.../src/main/resources/css/kafkatorio.css | 11 +-----
9 files changed, 60 insertions(+), 49 deletions(-)
diff --git a/README.md b/README.md
index 94fc22c..54c20e7 100644
--- a/README.md
+++ b/README.md
@@ -10,8 +10,42 @@ and WebSocket APIs.
Development is ongoing. At present the only application is a
[live-view web map](https://factorio.adamko.dev), that shows the current status of a Factorio
-server, and any connected players. Other possibilities are exposing metrics, and inter-server
-communication.
+server, and any connected players. Other usage possibilities include exposing live metrics, and
+inter-server communication.
+
+### Overview
+
+```mermaid
+sequenceDiagram
+
+Factorio multiplayer server->>events-mod: in-game events
+Note over events-mod: converts events to packets
+events-mod->>logs processor: packets logged to console
+logs processor->>Kafka: publishes packets
(Requires API key!)
+
+Kafka->>events-server:
+
+Note over events-server: Packets are processed
+
+events-server->>web-map: Web map tiles (REST API)
+events-server-)web-map: WebSocket updates
(e.g. player movement)
+```
+
+1. The Kafkatorio game mod, [`events-mod`](./modules/events-mod), collects event data, de-bouncing
+ and grouping events, and converts them to JSON packets
+2. `events-mod` prints the packets to the Factorio game server logs
+3. The Factorio logs are forwarded to a Kafka cluster, either using
+ * [syslog](https://docs.docker.com/config/containers/logging/syslog/) (using Docker logging
+ plugin)
+ * or [Kafka Pipe](./modules/infra-kafka-pipe) (a script that reads the server logs and forwards
+ them to a Kafka topic)
+4. The packets are consumed by [`events-server`](./modules/events-server), which processes the
+ packets using Kafka Streams, creating and hosting map tiles.
+
+ `events-server` serves the map tiles via a REST API, and produces WebSocket messages regarding
+ updates (for example, when a player's location has changed).
+5. The `web-map` uses Leaflet/JS to display the map, using the tiles and WebSocket messages from
+ `events-server`
## License
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 35b0729..f823576 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
@@ -50,12 +50,10 @@ object App : Application() {
var siteStateStore: ReduxStore by appState
private set
-// var wsService: WebsocketService by appState
-// private set
init {
this.siteStateStore = createReduxStore(SiteState::plus, SiteState())
-// this.wsService = wsService
+
require("./css/kafkatorio.css")
WebsocketService.packetsFlow
@@ -78,9 +76,6 @@ object App : Application() {
this.appState = state.toMutableMap()
-// val siteRouting = SiteRouting(reduxStore)
-// val websocketService = WebsocketService(siteStateStore)
-
SiteRouting.init()
root("kvapp") {
@@ -99,9 +94,6 @@ object App : Application() {
}
}
}
-
-// div("Kafkatorio Web Map")
-// add(kvMaps)
}
// kvMaps.leafletMap {
@@ -124,20 +116,10 @@ object App : Application() {
}
+
fun main() {
startApplication(
- {
-// val reduxStore = createFactorioReduxStore()
-// val wsService = WebsocketService(
-// reduxStore = reduxStore,
-// )
-// App(
-// reduxStore = reduxStore,
-// wsService = wsService,
-// )
-
- App
- },
+ { App },
module.hot,
BootstrapModule,
// BootstrapCssModule,
diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt
index 02485cb..652481c 100644
--- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt
+++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/config/ApplicationProperties.kt
@@ -4,6 +4,8 @@ import kotlinx.browser.window
object ApplicationProperties {
+
+ /** Determine the WebSocket URL based on the window location URL. */
@Suppress("HttpUrlsUsage")
val websocketServerUrl: String
get() = window.location.origin
diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt
index 5cf782f..10d6702 100644
--- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt
+++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/headerNav.kt
@@ -3,14 +3,15 @@ package dev.adamko.kafkatorio.webmap.layout
import dev.adamko.kafkatorio.webmap.SiteState
import dev.adamko.kafkatorio.webmap.SiteView
import io.kvision.core.Container
-import io.kvision.html.div
-import io.kvision.html.link
-import io.kvision.html.nav
+import io.kvision.navbar.nav
+import io.kvision.navbar.navLink
+import io.kvision.navbar.navbar
+
fun Container.headerNav(state: SiteState) {
- nav(className = "navbar") {
- div(className = "container") {
- link("home", SiteView.HOME.path)
+ navbar {
+ nav {
+ navLink("Home", SiteView.HOME.path)
}
}
}
diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt
index 2356aa2..91187c7 100644
--- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt
+++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/homePage.kt
@@ -40,7 +40,7 @@ private fun Container.servers(state: SiteState) {
// }
// }
- button(text = "$serverId") {
+ button(text = "$serverId", className = "btn btn-link") {
style = ButtonStyle.PRIMARY
size = ButtonSize.SMALL
diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt
index 7d8d2b3..2319b6a 100644
--- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt
+++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/layout/servers/serverPage.kt
@@ -9,7 +9,7 @@ import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
fun Container.serverPage(state: SiteState) {
val factorioGameState = state.factorioGameState
- div(className = "server-page") {
+ div(className = "server-page container-fluid") {
if (factorioGameState != null) {
add(factorioGameState.map.kvMap)
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
index cc661b4..e47c3fe 100644
--- 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
@@ -122,6 +122,7 @@ class FactorioMap(
}
}
+
private fun createServerTileLayer(
layer: ServerMapTileLayer
): TileLayer {
@@ -160,11 +161,11 @@ class FactorioMap(
return baseTileLayer
}
- private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String {
- return "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
- }
+ private fun tileLayerUrlTemplate(layer: ServerMapTileLayer): String =
+ "/kafkatorio/data/servers/${serverId.id}/map/layers/${layer.dir}/s1/z{z}/x{x}/y{y}.png"
companion object {
+ /** Add this attribute to `
` tiles that need to be dynamically refreshed. */
const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
}
}
diff --git a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt
index aacb43f..8553f08 100644
--- a/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt
+++ b/modules/web-map/src/main/kotlin/dev/adamko/kafkatorio/webmap/services/WebsocketService.kt
@@ -20,6 +20,7 @@ import dev.adamko.kafkatorio.webmap.rootJob
import kotlin.coroutines.CoroutineContext
import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
@@ -33,14 +34,13 @@ import org.w3c.dom.events.Event
object WebsocketService : CoroutineScope {
override val coroutineContext: CoroutineContext =
- CoroutineName("WebsocketService") + Job(rootJob)
+ Dispatchers.Default + CoroutineName("WebsocketService") + Job(rootJob)
private val ws = WebSocket(ApplicationProperties.websocketServerUrl)
-// private val ws = WebSocket("ws://localhost:12080/ws/foo")
- private val _packetsFlow = MutableSharedFlow()
+ private val packetsMutableFlow = MutableSharedFlow()
val packetsFlow: SharedFlow
- get() = _packetsFlow.asSharedFlow()
+ get() = packetsMutableFlow.asSharedFlow()
init {
ws.onmessage = ::handleMessageEvent
@@ -69,7 +69,7 @@ object WebsocketService : CoroutineScope {
if (packet != null) {
- launch { _packetsFlow.emit(packet) }
+ launch { packetsMutableFlow.emit(packet) }
when (packet) {
is EventServerPacket.ChunkTileSaved -> {}
diff --git a/modules/web-map/src/main/resources/css/kafkatorio.css b/modules/web-map/src/main/resources/css/kafkatorio.css
index 2533785..00e0e43 100644
--- a/modules/web-map/src/main/resources/css/kafkatorio.css
+++ b/modules/web-map/src/main/resources/css/kafkatorio.css
@@ -7,16 +7,7 @@
image-rendering: pixelated;
}
-/*html {*/
-/* background: #2B2B2BFF;*/
-/*}*/
-
-/*body {*/
-/* background: #2B2B2BFF;*/
-/*}*/
-
.leaflet-container {
+ /* make the Leaflet map background transparent, so it matches the site theme */
background-color: rgba(255, 0, 0, 0.0) !important;
- /*background: rgb(34, 34, 34) !important;*/
- /*background: var(--bs-body-bg-rgb) !important;*/
}