trigger tile png reload in browser by sending 'saved tile' events over websocket
- create new EventServerPacket for websocket packet data - move common models to events-schema
This commit is contained in:
parent
b15382660b
commit
8c37a0cef9
19 changed files with 303 additions and 108 deletions
|
|
@ -75,3 +75,10 @@ data class EntityIdentifiersData(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val protoType: String,
|
override val protoType: String,
|
||||||
) : EntityIdentifiers
|
) : EntityIdentifiers
|
||||||
|
|
||||||
|
|
||||||
|
@JvmInline
|
||||||
|
@Serializable
|
||||||
|
value class FactorioServerId(private val id: String) {
|
||||||
|
override fun toString() = id
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package dev.adamko.kafkatorio.schema.common
|
package dev.adamko.kafkatorio.schema.common
|
||||||
|
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTileDictionary.PrototypeKey
|
import dev.adamko.kafkatorio.schema.common.MapTileDictionary.PrototypeKey
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
const val MAP_CHUNK_SIZE = 32
|
const val MAP_CHUNK_SIZE = 32
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -69,3 +69,27 @@ data class MapBoundingBox(
|
||||||
val topLeft: MapTilePosition,
|
val topLeft: MapTilePosition,
|
||||||
val bottomRight: MapTilePosition,
|
val bottomRight: MapTilePosition,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ServerMapChunkId(
|
||||||
|
val serverId: FactorioServerId,
|
||||||
|
val chunkPosition: MapChunkPosition,
|
||||||
|
val surfaceIndex: SurfaceIndex,
|
||||||
|
val chunkSize: ChunkSize,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class TilePngFilename(
|
||||||
|
val value: String,
|
||||||
|
) {
|
||||||
|
constructor(id: ServerMapChunkId) : this(buildString {
|
||||||
|
append("s${id.surfaceIndex}")
|
||||||
|
append("/z${id.chunkSize.zoomLevel}")
|
||||||
|
append("/x${id.chunkPosition.x}")
|
||||||
|
append("/y${id.chunkPosition.y}")
|
||||||
|
append(".png")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package dev.adamko.kafkatorio.schema.packets
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.TilePngFilename
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class EventServerPacket {
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("kafkatorio.event-server-packet.Kafkatorio")
|
||||||
|
data class Kafkatorio(
|
||||||
|
val packet: KafkatorioPacket
|
||||||
|
) : EventServerPacket()
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@SerialName("kafkatorio.event-server-packet.ChunkTileSaved")
|
||||||
|
data class ChunkTileSaved(
|
||||||
|
val chunkId: ServerMapChunkId,
|
||||||
|
val filename: TilePngFilename,
|
||||||
|
) : EventServerPacket()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -3,7 +3,7 @@ package dev.adamko.kafkatorio.processor
|
||||||
import dev.adamko.kafkatorio.processor.config.ApplicationProperties
|
import dev.adamko.kafkatorio.processor.config.ApplicationProperties
|
||||||
import dev.adamko.kafkatorio.processor.topology.colourMapChunks
|
import dev.adamko.kafkatorio.processor.topology.colourMapChunks
|
||||||
import dev.adamko.kafkatorio.processor.topology.factorioServerPacketStream
|
import dev.adamko.kafkatorio.processor.topology.factorioServerPacketStream
|
||||||
import dev.adamko.kafkatorio.processor.topology.playerUpdatesToWsServer
|
import dev.adamko.kafkatorio.processor.topology.broadcastToWebsocket
|
||||||
import dev.adamko.kafkatorio.processor.topology.saveMapTiles
|
import dev.adamko.kafkatorio.processor.topology.saveMapTiles
|
||||||
import dev.adamko.kafkatorio.processor.topology.splitFactorioServerPacketStream
|
import dev.adamko.kafkatorio.processor.topology.splitFactorioServerPacketStream
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
@ -39,7 +39,7 @@ internal class KafkatorioTopology(
|
||||||
groupTilesMapChunks()
|
groupTilesMapChunks()
|
||||||
saveTiles()
|
saveTiles()
|
||||||
|
|
||||||
playerUpdates()
|
websocketBroadcaster()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun splitPackets() {
|
private fun splitPackets() {
|
||||||
|
|
@ -64,13 +64,13 @@ internal class KafkatorioTopology(
|
||||||
launchTopology("saveTiles", topology)
|
launchTopology("saveTiles", topology)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playerUpdates() {
|
private fun websocketBroadcaster() {
|
||||||
val builder = StreamsBuilder()
|
val builder = StreamsBuilder()
|
||||||
playerUpdatesToWsServer(websocketServer, builder)
|
broadcastToWebsocket(websocketServer, builder)
|
||||||
|
|
||||||
val topology = builder.build()
|
val topology = builder.build()
|
||||||
|
|
||||||
launchTopology("playerUpdates", topology)
|
launchTopology("websocketBroadcaster", topology)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchTopology(
|
private fun launchTopology(
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,9 @@ const val TOPIC_SUBDIVIDED_MAP_TILES = "$DOMAIN.map-tiles.subdivided"
|
||||||
const val TOPIC_SUBDIVIDED_MAP_TILES_DEBOUNCED = "$TOPIC_SUBDIVIDED_MAP_TILES.debounced"
|
const val TOPIC_SUBDIVIDED_MAP_TILES_DEBOUNCED = "$TOPIC_SUBDIVIDED_MAP_TILES.debounced"
|
||||||
|
|
||||||
|
|
||||||
|
/** Broadcast these events out, via websocket */
|
||||||
|
const val TOPIC_BROADCAST_TO_WEBSOCKET = "$DOMAIN.broadcast.websocket"
|
||||||
|
|
||||||
fun allTopics(): Set<String> = buildSet {
|
fun allTopics(): Set<String> = buildSet {
|
||||||
add(TOPIC_SRC_SERVER_LOG)
|
add(TOPIC_SRC_SERVER_LOG)
|
||||||
|
|
||||||
|
|
@ -38,6 +41,8 @@ fun allTopics(): Set<String> = buildSet {
|
||||||
add(TOPIC_SUBDIVIDED_MAP_TILES)
|
add(TOPIC_SUBDIVIDED_MAP_TILES)
|
||||||
add(TOPIC_SUBDIVIDED_MAP_TILES_DEBOUNCED)
|
add(TOPIC_SUBDIVIDED_MAP_TILES_DEBOUNCED)
|
||||||
|
|
||||||
|
add(TOPIC_BROADCAST_TO_WEBSOCKET)
|
||||||
|
|
||||||
addAll(packetTopicNames.values)
|
addAll(packetTopicNames.values)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,19 +100,19 @@ internal class WebMapTileServer(
|
||||||
currentETag == null -> {
|
currentETag == null -> {
|
||||||
// println("[${request.etagKey}] currentETag is null (requested: $requestedETag), executing request")
|
// println("[${request.etagKey}] currentETag is null (requested: $requestedETag), executing request")
|
||||||
response
|
response
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||||
}
|
}
|
||||||
requestedETag == currentETag -> {
|
requestedETag == currentETag -> {
|
||||||
// println("[${request.etagKey}] currentETag == requested, returning 304 $requestedETag")
|
// println("[${request.etagKey}] currentETag == requested, returning 304 $requestedETag")
|
||||||
Response(Status.NOT_MODIFIED)
|
Response(Status.NOT_MODIFIED)
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
// println("[${request.etagKey}] currentETag != requested, executing request and adding currentETag:$currentETag (requested:$requestedETag)")
|
// println("[${request.etagKey}] currentETag != requested, executing request and adding currentETag:$currentETag (requested:$requestedETag)")
|
||||||
response
|
response
|
||||||
.header("ETag", currentETag)
|
.header("ETag", currentETag)
|
||||||
// .header("Cache-Control", "no-store")
|
// .header("Cache-Control", "no-store")
|
||||||
.header("Cache-Control", "no-cache")
|
.header("Cache-Control", "no-cache, max-age=0, must-revalidate")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import dev.adamko.kafkatorio.processor.misc.DebounceProcessor.Companion.addDebou
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
import dev.adamko.kafkatorio.schema.common.leftTopTile
|
import dev.adamko.kafkatorio.schema.common.leftTopTile
|
||||||
import dev.adamko.kafkatorio.schema.common.toMapChunkPosition
|
import dev.adamko.kafkatorio.schema.common.toMapChunkPosition
|
||||||
import dev.adamko.kafkatorio.schema.packets.MapChunkUpdate
|
import dev.adamko.kafkatorio.schema.packets.MapChunkUpdate
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ package dev.adamko.kafkatorio.processor.topology
|
||||||
import dev.adamko.kafkatorio.processor.admin.TOPIC_SRC_SERVER_LOG
|
import dev.adamko.kafkatorio.processor.admin.TOPIC_SRC_SERVER_LOG
|
||||||
import dev.adamko.kafkatorio.processor.admin.topicName
|
import dev.adamko.kafkatorio.processor.admin.topicName
|
||||||
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||||
import dev.adamko.kotka.extensions.consumedAs
|
import dev.adamko.kotka.extensions.consumedAs
|
||||||
import dev.adamko.kotka.extensions.producedAs
|
import dev.adamko.kotka.extensions.producedAs
|
||||||
|
|
|
||||||
|
|
@ -1,32 +1,14 @@
|
||||||
package dev.adamko.kafkatorio.processor.topology
|
package dev.adamko.kafkatorio.processor.topology
|
||||||
|
|
||||||
import com.sksamuel.scrimage.color.RGBColor
|
import com.sksamuel.scrimage.color.RGBColor
|
||||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTile
|
import dev.adamko.kafkatorio.schema.common.MapTile
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
@JvmInline
|
|
||||||
@Serializable
|
|
||||||
value class FactorioServerId(private val id: String) {
|
|
||||||
override fun toString() = id
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ServerMapChunkId(
|
|
||||||
val serverId: FactorioServerId,
|
|
||||||
val chunkPosition: MapChunkPosition,
|
|
||||||
val surfaceIndex: SurfaceIndex,
|
|
||||||
val chunkSize: ChunkSize,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ServerMapChunkTiles<Data>(
|
data class ServerMapChunkTiles<Data>(
|
||||||
val chunkId: ServerMapChunkId,
|
val chunkId: ServerMapChunkId,
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package dev.adamko.kafkatorio.processor.topology
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.processor.admin.topicName
|
import dev.adamko.kafkatorio.processor.admin.topicName
|
||||||
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacketData
|
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacketData
|
||||||
import dev.adamko.kotka.extensions.consumedAs
|
import dev.adamko.kotka.extensions.consumedAs
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,17 @@
|
||||||
package dev.adamko.kafkatorio.processor.topology
|
package dev.adamko.kafkatorio.processor.topology
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.processor.WebsocketServer
|
import dev.adamko.kafkatorio.processor.WebsocketServer
|
||||||
|
import dev.adamko.kafkatorio.processor.admin.TOPIC_BROADCAST_TO_WEBSOCKET
|
||||||
import dev.adamko.kafkatorio.processor.admin.topicName
|
import dev.adamko.kafkatorio.processor.admin.topicName
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.jsonMapperKafkatorio
|
import dev.adamko.kafkatorio.schema.jsonMapperKafkatorio
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
||||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||||
import dev.adamko.kotka.extensions.consumedAs
|
import dev.adamko.kotka.extensions.consumedAs
|
||||||
|
import dev.adamko.kotka.extensions.producedAs
|
||||||
|
import dev.adamko.kotka.extensions.streams.forEach
|
||||||
|
import dev.adamko.kotka.extensions.streams.map
|
||||||
import dev.adamko.kotka.kxs.serde
|
import dev.adamko.kotka.kxs.serde
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
|
@ -18,15 +24,41 @@ private val jsonMapper: Json = Json {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun playerUpdatesToWsServer(
|
private const val pid = "websocketBroadcast"
|
||||||
|
|
||||||
|
|
||||||
|
fun broadcastToWebsocket(
|
||||||
websocketServer: WebsocketServer,
|
websocketServer: WebsocketServer,
|
||||||
builder: StreamsBuilder = StreamsBuilder(),
|
builder: StreamsBuilder = StreamsBuilder(),
|
||||||
) {
|
) {
|
||||||
builder.stream<FactorioServerId, KafkatorioPacket>(
|
|
||||||
PlayerUpdate::class.topicName,
|
builder.preparePlayerUpdatesBroadcast()
|
||||||
consumedAs("EventUpdate.Player-updates-for-ws-server", jsonMapper.serde(), jsonMapper.serde()),
|
|
||||||
).foreach { _, value ->
|
builder.stream<FactorioServerId, EventServerPacket>(
|
||||||
// println("sending ${value.packetType} packet to websocket")
|
TOPIC_BROADCAST_TO_WEBSOCKET,
|
||||||
websocketServer.sendMessage(jsonMapper.encodeToString(value))
|
consumedAs("$pid.packets-to-broadcast.consume", jsonMapper.serde(), jsonMapper.serde()),
|
||||||
|
).forEach(
|
||||||
|
"$pid.websocket-server.send-message"
|
||||||
|
) { _, esPacket ->
|
||||||
|
websocketServer.sendMessage(jsonMapper.encodeToString(esPacket))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun StreamsBuilder.preparePlayerUpdatesBroadcast() {
|
||||||
|
stream<FactorioServerId, KafkatorioPacket>(
|
||||||
|
PlayerUpdate::class.topicName,
|
||||||
|
consumedAs("$pid.player-updates", jsonMapper.serde(), jsonMapper.serde()),
|
||||||
|
).map<FactorioServerId, KafkatorioPacket, FactorioServerId, EventServerPacket>(
|
||||||
|
"$pid.player-updates.convert"
|
||||||
|
) { serverId, playerPacket ->
|
||||||
|
serverId to EventServerPacket.Kafkatorio(playerPacket)
|
||||||
|
}.to(
|
||||||
|
TOPIC_BROADCAST_TO_WEBSOCKET,
|
||||||
|
producedAs(
|
||||||
|
"$pid.player-updates.stream",
|
||||||
|
jsonMapper.serde(FactorioServerId.serializer()),
|
||||||
|
jsonMapper.serde(EventServerPacket.serializer()),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package dev.adamko.kafkatorio.processor.topology
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
import dev.adamko.kafkatorio.schema.common.FactorioPrototype
|
import dev.adamko.kafkatorio.schema.common.FactorioPrototype
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTile
|
import dev.adamko.kafkatorio.schema.common.MapTile
|
||||||
import dev.adamko.kafkatorio.schema.common.toHex
|
import dev.adamko.kafkatorio.schema.common.toHex
|
||||||
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,19 @@ package dev.adamko.kafkatorio.processor.topology
|
||||||
import com.sksamuel.scrimage.ImmutableImage
|
import com.sksamuel.scrimage.ImmutableImage
|
||||||
import com.sksamuel.scrimage.ScaleMethod
|
import com.sksamuel.scrimage.ScaleMethod
|
||||||
import com.sksamuel.scrimage.nio.PngWriter
|
import com.sksamuel.scrimage.nio.PngWriter
|
||||||
|
import dev.adamko.kafkatorio.processor.admin.TOPIC_BROADCAST_TO_WEBSOCKET
|
||||||
import dev.adamko.kafkatorio.processor.admin.TOPIC_MAP_CHUNK_COLOURED_STATE
|
import dev.adamko.kafkatorio.processor.admin.TOPIC_MAP_CHUNK_COLOURED_STATE
|
||||||
|
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.TilePngFilename
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
import dev.adamko.kotka.extensions.consumedAs
|
import dev.adamko.kotka.extensions.consumedAs
|
||||||
import dev.adamko.kotka.extensions.streams.forEach
|
import dev.adamko.kotka.extensions.producedAs
|
||||||
|
import dev.adamko.kotka.extensions.streams.map
|
||||||
|
import dev.adamko.kotka.extensions.streams.mapValues
|
||||||
import dev.adamko.kotka.kxs.serde
|
import dev.adamko.kotka.kxs.serde
|
||||||
import java.awt.image.BufferedImage
|
import java.awt.image.BufferedImage
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
@ -41,15 +49,18 @@ fun saveMapTiles(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
subdividedMapChunkTilesDebounced.forEachChunkSaveImage(tileDirectory)
|
val savedChunkFilenames: KStream<ServerMapChunkId, TilePngFilename> =
|
||||||
|
subdividedMapChunkTilesDebounced.saveChunkAsImage(tileDirectory)
|
||||||
|
|
||||||
|
savedChunkFilenames.broadcastSavedTileUpdates()
|
||||||
|
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun KStream<ServerMapChunkId, ServerMapChunkTiles<ColourHex>>.forEachChunkSaveImage(
|
private fun KStream<ServerMapChunkId, ServerMapChunkTiles<ColourHex>>.saveChunkAsImage(
|
||||||
tileDirectory: Path,
|
tileDirectory: Path,
|
||||||
) = forEach(
|
): KStream<ServerMapChunkId, TilePngFilename> = mapValues(
|
||||||
"$pid.save-tiles"
|
"$pid.save-tiles"
|
||||||
) { chunkId: ServerMapChunkId, tileColours: ServerMapChunkTiles<ColourHex> ->
|
) { chunkId: ServerMapChunkId, tileColours: ServerMapChunkTiles<ColourHex> ->
|
||||||
|
|
||||||
|
|
@ -77,6 +88,7 @@ private fun KStream<ServerMapChunkId, ServerMapChunkTiles<ColourHex>>.forEachChu
|
||||||
|
|
||||||
val filename = TilePngFilename(chunkId)
|
val filename = TilePngFilename(chunkId)
|
||||||
saveTile(filename, tileDirectory, scaledImage)
|
saveTile(filename, tileDirectory, scaledImage)
|
||||||
|
filename
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -105,15 +117,17 @@ private fun saveTile(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JvmInline
|
private fun KStream<ServerMapChunkId, TilePngFilename>.broadcastSavedTileUpdates() {
|
||||||
private value class TilePngFilename(
|
map<ServerMapChunkId, TilePngFilename, FactorioServerId, EventServerPacket>(
|
||||||
val value: String,
|
"$pid.broadcast.filename-update.convert",
|
||||||
) {
|
) { id, filename ->
|
||||||
constructor(id: ServerMapChunkId) : this(buildString {
|
id.serverId to EventServerPacket.ChunkTileSaved(id, filename)
|
||||||
append("s${id.surfaceIndex}")
|
}.to(
|
||||||
append("/z${id.chunkSize.zoomLevel}")
|
TOPIC_BROADCAST_TO_WEBSOCKET,
|
||||||
append("/x${id.chunkPosition.x}")
|
producedAs(
|
||||||
append("/y${id.chunkPosition.y}")
|
"$pid.broadcast.filename-update.send",
|
||||||
append(".png")
|
jsonMapper.serde(FactorioServerId.serializer()),
|
||||||
})
|
jsonMapper.serde(EventServerPacket.serializer()),
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package dev.adamko.kafkatorio.processor.misc
|
package dev.adamko.kafkatorio.processor.misc
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.processor.topology.FactorioServerId
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkId
|
|
||||||
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkTiles
|
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkTiles
|
||||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||||
|
|
@ -134,7 +134,8 @@ class KxsBinaryTests : FunSpec({
|
||||||
|
|
||||||
test("binary round trip") {
|
test("binary round trip") {
|
||||||
val binaryEncoded = kxsBinary.encodeToByteArray(initial)
|
val binaryEncoded = kxsBinary.encodeToByteArray(initial)
|
||||||
val binaryDecoded = kxsBinary.decodeFromByteArray<ServerMapChunkTiles<ColourHex>>(binaryEncoded)
|
val binaryDecoded =
|
||||||
|
kxsBinary.decodeFromByteArray<ServerMapChunkTiles<ColourHex>>(binaryEncoded)
|
||||||
|
|
||||||
binaryDecoded shouldBeEqualToComparingFields initial
|
binaryDecoded shouldBeEqualToComparingFields initial
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,14 @@
|
||||||
package dev.adamko.kafkatorio.processor.misc
|
package dev.adamko.kafkatorio.processor.misc
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.processor.topology.FactorioServerId
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkId
|
|
||||||
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkTiles
|
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkTiles
|
||||||
import dev.adamko.kafkatorio.schema.common.*
|
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||||
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
|
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||||
|
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||||
|
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlinx.serialization.encodeToByteArray
|
import kotlinx.serialization.encodeToByteArray
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,10 @@ package dev.adamko.kafkatorio.processor.topology
|
||||||
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
|
||||||
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
import dev.adamko.kafkatorio.schema.common.ChunkSize
|
||||||
import dev.adamko.kafkatorio.schema.common.ColourHex
|
import dev.adamko.kafkatorio.schema.common.ColourHex
|
||||||
|
import dev.adamko.kafkatorio.schema.common.FactorioServerId
|
||||||
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
import dev.adamko.kafkatorio.schema.common.MapChunkPosition
|
||||||
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
import dev.adamko.kafkatorio.schema.common.MapTilePosition
|
||||||
|
import dev.adamko.kafkatorio.schema.common.ServerMapChunkId
|
||||||
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
import dev.adamko.kafkatorio.schema.common.SurfaceIndex
|
||||||
import dev.adamko.kafkatorio.schema.common.iterateTiles
|
import dev.adamko.kafkatorio.schema.common.iterateTiles
|
||||||
import dev.adamko.kafkatorio.schema.common.toMapChunkPosition
|
import dev.adamko.kafkatorio.schema.common.toMapChunkPosition
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.adamko.kafkatorio.webmap
|
package dev.adamko.kafkatorio.webmap
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
||||||
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
||||||
import dev.adamko.kafkatorio.webmap.state.createFactorioReduxStore
|
import dev.adamko.kafkatorio.webmap.state.createFactorioReduxStore
|
||||||
|
|
@ -12,23 +13,34 @@ import io.kvision.CoreModule
|
||||||
import io.kvision.FontAwesomeModule
|
import io.kvision.FontAwesomeModule
|
||||||
import io.kvision.html.div
|
import io.kvision.html.div
|
||||||
import io.kvision.maps.Maps
|
import io.kvision.maps.Maps
|
||||||
import io.kvision.maps.Maps.Companion.L
|
|
||||||
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
import io.kvision.maps.externals.leaflet.layer.tile.GridLayer
|
||||||
import io.kvision.module
|
import io.kvision.module
|
||||||
import io.kvision.panel.root
|
import io.kvision.panel.root
|
||||||
import io.kvision.redux.ReduxStore
|
import io.kvision.redux.ReduxStore
|
||||||
import io.kvision.require
|
import io.kvision.require
|
||||||
import io.kvision.startApplication
|
import io.kvision.startApplication
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.coroutines.CoroutineContext
|
||||||
import kotlinx.js.timers.setInterval
|
import kotlinx.coroutines.CoroutineName
|
||||||
import org.w3c.dom.Image
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import org.w3c.dom.asList
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.cancelChildren
|
||||||
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
||||||
|
|
||||||
|
val rootJob = Job()
|
||||||
|
|
||||||
|
|
||||||
class App(
|
class App(
|
||||||
reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
||||||
wsService: WebsocketService,
|
wsService: WebsocketService,
|
||||||
) : Application() {
|
) : Application(), CoroutineScope {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
CoroutineName("WebMapApp") + Job(rootJob)
|
||||||
|
|
||||||
private var appState: MutableMap<String, Any> = mutableMapOf()
|
private var appState: MutableMap<String, Any> = mutableMapOf()
|
||||||
|
|
||||||
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState
|
var reduxStore: ReduxStore<FactorioGameState, FactorioUpdate> by appState
|
||||||
|
|
@ -40,8 +52,12 @@ class App(
|
||||||
require("./css/kafkatorio.css")
|
require("./css/kafkatorio.css")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val gameState: FactorioGameState
|
||||||
|
get() = reduxStore.getState()
|
||||||
|
|
||||||
private val kvMaps: Maps
|
private val kvMaps: Maps
|
||||||
get() = reduxStore.getState().map.kvMap
|
get() = gameState.map.kvMap
|
||||||
|
|
||||||
|
|
||||||
override fun start(state: Map<String, Any>) {
|
override fun start(state: Map<String, Any>) {
|
||||||
this.appState = state.toMutableMap()
|
this.appState = state.toMutableMap()
|
||||||
|
|
@ -62,35 +78,24 @@ class App(
|
||||||
// this.asDynamic()._fadeAnimated = false
|
// this.asDynamic()._fadeAnimated = false
|
||||||
}
|
}
|
||||||
|
|
||||||
setInterval(1.seconds) {
|
val doc = root.getElement()?.ownerDocument
|
||||||
val doc = root.getElement()?.ownerDocument
|
val window = doc!!.defaultView!!
|
||||||
val window = doc!!.defaultView!!
|
|
||||||
|
|
||||||
doc
|
wsService.packetsFlow
|
||||||
.querySelectorAll("img.leaflet-tile-loaded")
|
.filterIsInstance<EventServerPacket.ChunkTileSaved>()
|
||||||
.asList()
|
.onEach {
|
||||||
.filterIsInstance<Image>()
|
println("[packetsFlow] triggering tile refresh ${it.filename.value}")
|
||||||
.map { img ->
|
gameState.map.refreshUpdatedTilePng(doc, window, it.filename)
|
||||||
val imgSrc = img.src.substringBeforeLast('?')
|
}.launchIn(this)
|
||||||
// println("fetching $imgSrc")
|
|
||||||
|
|
||||||
var newImg: Image? = Image()
|
|
||||||
|
|
||||||
newImg!!.onload = {
|
|
||||||
|
|
||||||
window.requestAnimationFrame {
|
|
||||||
// println("image loaded ${newImg!!.src}")
|
|
||||||
img.setAttribute("dynamic-reload", "true")
|
|
||||||
img.src = newImg!!.src
|
|
||||||
newImg = null // try to encourage garbage collection
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newImg!!.src = imgSrc //+ "?t=${currentTimeMillis()}"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose(): Map<String, Any> = appState
|
override fun dispose(): Map<String, Any> {
|
||||||
|
rootJob.cancelChildren()
|
||||||
|
rootJob.cancel("app dispose")
|
||||||
|
rootJob.complete()
|
||||||
|
return appState
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -104,7 +109,7 @@ fun main() {
|
||||||
)
|
)
|
||||||
App(
|
App(
|
||||||
reduxStore = reduxStore,
|
reduxStore = reduxStore,
|
||||||
wsService = wsService
|
wsService = wsService,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
module.hot,
|
module.hot,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package dev.adamko.kafkatorio.webmap
|
package dev.adamko.kafkatorio.webmap
|
||||||
|
|
||||||
|
import dev.adamko.kafkatorio.schema.common.TilePngFilename
|
||||||
import io.kvision.maps.Maps
|
import io.kvision.maps.Maps
|
||||||
import io.kvision.maps.Maps.Companion.L
|
import io.kvision.maps.Maps.Companion.L
|
||||||
import io.kvision.maps.externals.leaflet.DoneCallback
|
import io.kvision.maps.externals.leaflet.DoneCallback
|
||||||
|
|
@ -12,7 +13,19 @@ import io.kvision.maps.externals.leaflet.layer.LayerGroup
|
||||||
import io.kvision.maps.externals.leaflet.layer.tile.TileLayer
|
import io.kvision.maps.externals.leaflet.layer.tile.TileLayer
|
||||||
import io.kvision.utils.obj
|
import io.kvision.utils.obj
|
||||||
import io.kvision.utils.px
|
import io.kvision.utils.px
|
||||||
|
import kotlinx.coroutines.await
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.HTMLElement
|
import org.w3c.dom.HTMLElement
|
||||||
|
import org.w3c.dom.Image
|
||||||
|
import org.w3c.dom.Window
|
||||||
|
import org.w3c.dom.asList
|
||||||
|
import org.w3c.fetch.NO_CORS
|
||||||
|
import org.w3c.fetch.RELOAD
|
||||||
|
import org.w3c.fetch.RequestCache
|
||||||
|
import org.w3c.fetch.RequestInit
|
||||||
|
import org.w3c.fetch.RequestMode
|
||||||
|
|
||||||
|
|
||||||
class FactorioMap(
|
class FactorioMap(
|
||||||
|
|
@ -64,6 +77,49 @@ class FactorioMap(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
suspend fun refreshUpdatedTilePng(
|
||||||
|
document: Document,
|
||||||
|
window: Window,
|
||||||
|
tilePngFilename: TilePngFilename,
|
||||||
|
): Unit = coroutineScope {
|
||||||
|
launch {
|
||||||
|
document
|
||||||
|
.querySelectorAll("img.leaflet-tile-loaded")
|
||||||
|
.asList()
|
||||||
|
.filterIsInstance<Image>()
|
||||||
|
.filter { img ->
|
||||||
|
tilePngFilename.value in img.src
|
||||||
|
}
|
||||||
|
.forEach { img ->
|
||||||
|
val imgSrc = img.src //.substringBeforeLast('?')
|
||||||
|
|
||||||
|
window.fetch(
|
||||||
|
imgSrc,
|
||||||
|
RequestInit(
|
||||||
|
cache = RequestCache.RELOAD,
|
||||||
|
mode = RequestMode.NO_CORS,
|
||||||
|
)
|
||||||
|
).then {
|
||||||
|
println("[refreshUpdatedTilePng] updating $imgSrc")
|
||||||
|
img.setAttribute(DYNAMIC_RELOAD_ATT, "true")
|
||||||
|
img.src = imgSrc
|
||||||
|
}.await()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//// println("fetching $imgSrc")
|
||||||
|
// val newImg = Image()
|
||||||
|
// newImg.onload = {
|
||||||
|
//// window.requestAnimationFrame {
|
||||||
|
//// println("image loaded ${newImg!!.src}")
|
||||||
|
// img.setAttribute(DYNAMIC_RELOAD_ATT, "true")
|
||||||
|
// img.src = newImg.src
|
||||||
|
// Unit
|
||||||
|
//// }
|
||||||
|
// }
|
||||||
|
// newImg.src = imgSrc //+ "?t=${currentTimeMillis()}"
|
||||||
|
}
|
||||||
|
|
||||||
private fun buildFactorioTerrainLayer(): TileLayer<TileLayer.TileLayerOptions> {
|
private fun buildFactorioTerrainLayer(): TileLayer<TileLayer.TileLayerOptions> {
|
||||||
|
|
||||||
val baseTileLayer = L.tileLayer(
|
val baseTileLayer = L.tileLayer(
|
||||||
|
|
@ -87,8 +143,8 @@ class FactorioMap(
|
||||||
val currentTileOnLoad: (DoneCallback, HTMLElement) -> Unit =
|
val currentTileOnLoad: (DoneCallback, HTMLElement) -> Unit =
|
||||||
baseTileLayer.asDynamic()._tileOnLoad as (DoneCallback, HTMLElement) -> Unit
|
baseTileLayer.asDynamic()._tileOnLoad as (DoneCallback, HTMLElement) -> Unit
|
||||||
baseTileLayer.asDynamic()._tileOnLoad = { done: DoneCallback, tile: HTMLElement ->
|
baseTileLayer.asDynamic()._tileOnLoad = { done: DoneCallback, tile: HTMLElement ->
|
||||||
if (tile.hasAttribute("dynamic-reload")) {
|
if (tile.hasAttribute(DYNAMIC_RELOAD_ATT)) {
|
||||||
tile.removeAttribute("dynamic-reload")
|
tile.removeAttribute(DYNAMIC_RELOAD_ATT)
|
||||||
} else {
|
} else {
|
||||||
currentTileOnLoad(done, tile)
|
currentTileOnLoad(done, tile)
|
||||||
}
|
}
|
||||||
|
|
@ -96,4 +152,8 @@ class FactorioMap(
|
||||||
|
|
||||||
return baseTileLayer
|
return baseTileLayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val DYNAMIC_RELOAD_ATT = "dynamic-reload"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import dev.adamko.kafkatorio.schema.packets.ConfigurationUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.ConsoleChatUpdate
|
import dev.adamko.kafkatorio.schema.packets.ConsoleChatUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.ConsoleCommandUpdate
|
import dev.adamko.kafkatorio.schema.packets.ConsoleCommandUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.EntityUpdate
|
import dev.adamko.kafkatorio.schema.packets.EntityUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.KafkatorioPacket
|
import dev.adamko.kafkatorio.schema.packets.EventServerPacket
|
||||||
import dev.adamko.kafkatorio.schema.packets.MapChunkUpdate
|
import dev.adamko.kafkatorio.schema.packets.MapChunkUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
import dev.adamko.kafkatorio.schema.packets.PlayerUpdate
|
||||||
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
import dev.adamko.kafkatorio.schema.packets.PrototypesUpdate
|
||||||
|
|
@ -12,45 +12,72 @@ import dev.adamko.kafkatorio.schema.packets.SurfaceUpdate
|
||||||
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
import dev.adamko.kafkatorio.webmap.state.FactorioGameState
|
||||||
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
import dev.adamko.kafkatorio.webmap.state.FactorioUpdate
|
||||||
import io.kvision.redux.ReduxStore
|
import io.kvision.redux.ReduxStore
|
||||||
|
import kotlin.coroutines.CoroutineContext
|
||||||
|
import kotlinx.coroutines.CoroutineName
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.w3c.dom.MessageEvent
|
import org.w3c.dom.MessageEvent
|
||||||
import org.w3c.dom.WebSocket
|
import org.w3c.dom.WebSocket
|
||||||
|
|
||||||
|
|
||||||
class WebsocketService(
|
class WebsocketService(
|
||||||
private val wsUrl: String,
|
wsUrl: String,
|
||||||
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
|
||||||
) {
|
) : CoroutineScope {
|
||||||
|
|
||||||
|
override val coroutineContext: CoroutineContext =
|
||||||
|
CoroutineName("WebsocketService") + Job(rootJob)
|
||||||
|
|
||||||
private val ws = WebSocket(wsUrl)
|
private val ws = WebSocket(wsUrl)
|
||||||
|
|
||||||
|
private val _packetsFlow = MutableSharedFlow<EventServerPacket>()
|
||||||
|
val packetsFlow: SharedFlow<EventServerPacket>
|
||||||
|
get() = _packetsFlow.asSharedFlow()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
ws.onmessage = ::handleMessageEvent
|
ws.onmessage = ::handleMessageEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleMessageEvent(msg: MessageEvent) {
|
private fun handleMessageEvent(msg: MessageEvent) {
|
||||||
|
|
||||||
val data = msg.data as? String
|
val data = (msg.data as? String)?.trim()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
data != null
|
data != null
|
||||||
&& data.trim().run { startsWith("{") && endsWith("}") }
|
&& data.startsWith("{")
|
||||||
|
&& data.endsWith("}")
|
||||||
) {
|
) {
|
||||||
|
|
||||||
println(data.replace('\n', ' '))
|
println(data.replace('\n', ' '))
|
||||||
|
|
||||||
val packet = jsonMapper.decodeFromString(KafkatorioPacket.serializer(), data)
|
val packet = jsonMapper.decodeFromString(EventServerPacket.serializer(), data)
|
||||||
|
|
||||||
when (val packetData = packet.data) {
|
launch {
|
||||||
|
_packetsFlow.emit(packet)
|
||||||
|
}
|
||||||
|
|
||||||
is PlayerUpdate -> reduxStore.dispatch(FactorioUpdate.Player(packet.tick, packetData))
|
when (packet) {
|
||||||
is ConfigurationUpdate,
|
is EventServerPacket.ChunkTileSaved -> {}
|
||||||
is ConsoleChatUpdate,
|
|
||||||
is ConsoleCommandUpdate,
|
is EventServerPacket.Kafkatorio ->
|
||||||
is PrototypesUpdate,
|
when (val packetData = packet.packet.data) {
|
||||||
is SurfaceUpdate,
|
is PlayerUpdate -> reduxStore.dispatch(
|
||||||
is EntityUpdate,
|
FactorioUpdate.Player(packet.packet.tick, packetData)
|
||||||
is MapChunkUpdate -> {
|
)
|
||||||
// to be continued...
|
is ConfigurationUpdate,
|
||||||
}
|
is ConsoleChatUpdate,
|
||||||
|
is ConsoleCommandUpdate,
|
||||||
|
is PrototypesUpdate,
|
||||||
|
is SurfaceUpdate,
|
||||||
|
is EntityUpdate,
|
||||||
|
is MapChunkUpdate -> {
|
||||||
|
// to be continued...
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
println(data)
|
println(data)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue