web-map tidy up, and add diagram to README

This commit is contained in:
Adam 2022-09-03 11:15:43 +02:00
parent 3976c3cc1b
commit de419aeae8
9 changed files with 60 additions and 49 deletions

View file

@ -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<br/>(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<br/>(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

View file

@ -50,12 +50,10 @@ object App : Application() {
var siteStateStore: ReduxStore<SiteState, SiteAction> 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,

View file

@ -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

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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)

View file

@ -122,6 +122,7 @@ class FactorioMap(
}
}
private fun createServerTileLayer(
layer: ServerMapTileLayer
): TileLayer<TileLayer.TileLayerOptions> {
@ -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 `<img>` tiles that need to be dynamically refreshed. */
const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
}
}

View file

@ -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<EventServerPacket>()
private val packetsMutableFlow = MutableSharedFlow<EventServerPacket>()
val packetsFlow: SharedFlow<EventServerPacket>
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 -> {}

View file

@ -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;*/
}