so close to working performantly.... just the tiles are transparent???

- bump some versions
- MapTile no longer has objectName field
- fix nulls/optional fields in FactorioConfigurationUpdate
- use bytes to encode rgba colours - waaaayyyy smaller
- increase Kafka record size to ~10mb
This commit is contained in:
Adam 2022-02-08 01:35:52 +01:00
parent ea0bfb350b
commit bdb2d9577f
24 changed files with 619 additions and 404 deletions

View file

@ -6,7 +6,7 @@ plugins {
} }
group = "dev.adamko.kafkatorio" group = "dev.adamko.kafkatorio"
version = "0.2.7" version = "0.2.8"
val licenseFile: RegularFile by extra(layout.projectDirectory.file("LICENSE")) val licenseFile: RegularFile by extra(layout.projectDirectory.file("LICENSE"))

View file

@ -10,17 +10,17 @@ kotlin = "1.6.10"
## libs ## ## libs ##
camel-kafkaConnectors = "0.11.0" camel-kafkaConnectors = "0.11.0"
http4k = "4.17.6.0" http4k = "4.19.1.0"
kafka = "3.1.0" kafka = "3.1.0"
kotlinx-serialization = "1.3.2" kotlinx-serialization = "1.3.2"
kvision = "5.7.1" kvision = "5.8.1"
log4j2 = "2.14.1" log4j2 = "2.14.1"
qosLogback = "1.2.5" qosLogback = "1.2.5"
slf4j = "1.7.30" slf4j = "1.7.30"
## test ## ## test ##
junit = "5.8.2" junit = "5.8.2"
kotest = "5.0.1" kotest = "5.1.0"
## infra ## ## infra ##
factorio = "1.1.53" factorio = "1.1.53"
@ -99,7 +99,7 @@ kotka = { group = "com.github.blueanvil", name = "kotka", versio
### test ### ### test ###
############# #############
mockk = { group = "io.mockk", name = "mockk", version = "1.12.0" } mockk = { group = "io.mockk", name = "mockk", version = "1.12.2" }
## junit ## ## junit ##
junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" } junit-bom = { group = "org.junit", name = "junit-bom", version.ref = "junit" }

View file

@ -43,7 +43,6 @@ interface MapChunkPosition {
} }
interface MapTile { interface MapTile {
objectName: string;
position: MapTilePosition; position: MapTilePosition;
prototypeName: string; prototypeName: string;
} }

View file

@ -98,8 +98,6 @@ export namespace Converters {
export function convertTile(tile: LuaTile): MapTile { export function convertTile(tile: LuaTile): MapTile {
return { return {
objectName: tile.object_name,
position: mapTilePosition(tile.position), position: mapTilePosition(tile.position),
prototypeName: tile.prototype.name, prototypeName: tile.prototype.name,
} }

View file

@ -92,8 +92,7 @@ export function handleTilesUpdate(
let convertedTiles: MapTile[] = updatedTiles.map((it) => { let convertedTiles: MapTile[] = updatedTiles.map((it) => {
return { return {
objectName: "LuaTile", position: it.position, // TODO use converters for LuaTile
position: it.position,
prototypeName: newTileType.name, prototypeName: newTileType.name,
} }
}) })

View file

@ -1,6 +1,6 @@
{ {
"name": "kafkatorio-events-mod", "name": "kafkatorio-events-mod",
"version": "0.2.7", "version": "0.2.8",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View file

@ -1,6 +1,6 @@
{ {
"name": "kafkatorio-events-mod", "name": "kafkatorio-events-mod",
"version": "0.2.7", "version": "0.2.8",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "tstl", "build": "tstl",

View file

@ -23,7 +23,12 @@ fun Colour.toHexadecimal() = if (isPercentage()) {
(alpha * 255).coerceIn(0f..255f), (alpha * 255).coerceIn(0f..255f),
) )
} else { } else {
this Colour(
red.coerceIn(0f..255f),
green.coerceIn(0f..255f),
blue.coerceIn(0f..255f),
alpha.coerceIn(0f..255f),
)
} }
fun Colour.toPercentage() = if (isPercentage()) { fun Colour.toPercentage() = if (isPercentage()) {

View file

@ -1,5 +1,6 @@
package dev.adamko.kafkatorio.events.schema package dev.adamko.kafkatorio.events.schema
import dev.adamko.kafkatorio.events.schema.converters.toHexadecimal
import kotlinx.serialization.EncodeDefault import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -56,7 +57,7 @@ data class MapBoundingBox(
* Red, green, blue and alpha values, all in range `[0, 1]` or all in range `[0, 255]` if any * Red, green, blue and alpha values, all in range `[0, 1]` or all in range `[0, 255]` if any
* value is > 1. * value is > 1.
* *
* All values here are optional. Colour channels default to 0, the alpha channel defaults to 1. * All values here are optional. Colour channels default to `0`, the alpha channel defaults to `1`.
*/ */
@Serializable @Serializable
data class Colour( data class Colour(
@ -68,4 +69,39 @@ data class Colour(
val blue: Float = 0f, val blue: Float = 0f,
@EncodeDefault @EncodeDefault
val alpha: Float = 1f, val alpha: Float = 1f,
) {
fun toHex() = toHexadecimal().let {
ColourHex(
it.red.toUInt().toUByte(),
it.green.toUInt().toUByte(),
it.blue.toUInt().toUByte(),
it.alpha.toUInt().toUByte(),
)
}
}
/** Size-efficient version of [Colour] */
@Serializable
data class ColourHex(
@EncodeDefault
val red: UByte = UByte.MIN_VALUE,
@EncodeDefault
val green: UByte = UByte.MIN_VALUE,
@EncodeDefault
val blue: UByte = UByte.MIN_VALUE,
@EncodeDefault
val alpha: UByte = UByte.MAX_VALUE,
) {
companion object {
val TRANSPARENT = ColourHex(0u, 0u, 0u, 0u)
}
}
@Serializable
data class MapTile(
val prototypeName: String,
val position: MapTilePosition,
) )

View file

@ -16,13 +16,13 @@ data class FactorioConfigurationUpdate(
@Serializable @Serializable
data class FactorioGameDataUpdate( data class FactorioGameDataUpdate(
val oldVersion: String?, val oldVersion: String? = null,
val newVersion: String?, val newVersion: String? = null,
) )
@Serializable @Serializable
data class FactorioModInfo( data class FactorioModInfo(
val modName: String, val modName: String,
val currentVersion: String?, val currentVersion: String? = null,
val previousVersion: String?, val previousVersion: String? = null,
) )

View file

@ -137,17 +137,6 @@ data class MapTiles(
override val objectName = ObjectName.LuaTiles override val objectName = ObjectName.LuaTiles
} }
@Serializable
data class MapTile(
val prototypeName: String,
val position: MapTilePosition,
)
// : FactorioObjectData()
{
// @EncodeDefault
val objectName: String = ""
}
@Serializable @Serializable
data class ConsoleChatMessage( data class ConsoleChatMessage(
val authorPlayerIndex: UInt?, val authorPlayerIndex: UInt?,

View file

@ -203,7 +203,6 @@ class FactorioEventTest : FunSpec({
"surfaceIndex": 1, "surfaceIndex": 1,
"tiles": [ "tiles": [
{ {
"objectName": "LuaTile",
"position": { "position": {
"y": 10, "y": 10,
"x": -65 "x": -65
@ -211,23 +210,20 @@ class FactorioEventTest : FunSpec({
"prototypeName": "refined-concrete" "prototypeName": "refined-concrete"
}, },
{ {
"objectName": "LuaTile",
"position": { "position": {
"y": 11, "y": 11,
"x": -65 "x": -65
}, },
"prototypeName": "refined-concrete" "prototypeName": "red-earth"
}, },
{ {
"objectName": "LuaTile",
"position": { "position": {
"y": 10, "y": 10,
"x": -64 "x": -64
}, },
"prototypeName": "refined-concrete" "prototypeName": "blue-cheese"
}, },
{ {
"objectName": "LuaTile",
"position": { "position": {
"y": 11, "y": 11,
"x": -64 "x": -64
@ -256,11 +252,11 @@ class FactorioEventTest : FunSpec({
position = MapTilePosition(-65, 10) position = MapTilePosition(-65, 10)
), ),
MapTile( MapTile(
prototypeName = "refined-concrete", prototypeName = "red-earth",
position = MapTilePosition(-65, 11) position = MapTilePosition(-65, 11)
), ),
MapTile( MapTile(
prototypeName = "refined-concrete", prototypeName = "blue-cheese",
position = MapTilePosition(-64, 10) position = MapTilePosition(-64, 10)
), ),
MapTile( MapTile(
@ -279,6 +275,60 @@ class FactorioEventTest : FunSpec({
expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaTiles expected.data.objectName shouldBe FactorioObjectData.ObjectName.LuaTiles
} }
test("Then: expect encode equals json") {
val encoded = jsonMapperKafkatorio.encodeToString(actual)
encoded.shouldEqualJson(json)
}
}
}
context("Given: packet FactorioConfigurationUpdate") {
// language=JSON
val json = """
{
"modVersion": "0.2.8",
"packetType": "CONFIG",
"allMods": [
{
"modName": "kafkatorio-events",
"currentVersion": "0.2.8",
"previousVersion": "0.2.7"
},
{
"modName": "base",
"currentVersion": "1.1.53"
}
],
"factorioData": {}
}
""".trimIndent()
context("When: decoded") {
val actual: FactorioConfigurationUpdate = jsonMapperKafkatorio.decodeFromString(json)
test("parse") {
val expected = FactorioConfigurationUpdate(
modVersion = "0.2.8",
factorioData = FactorioGameDataUpdate(null, null),
allMods = listOf(
FactorioModInfo(
modName = "kafkatorio-events",
currentVersion = "0.2.8",
previousVersion = "0.2.7"
),
FactorioModInfo(
modName = "base",
currentVersion = "1.1.53",
previousVersion = null
),
)
)
actual shouldBe expected
expected.packetType shouldBe KafkatorioPacket.PacketType.CONFIG
}
test("Then: expect encode equals json") { test("Then: expect encode equals json") {
val encoded = jsonMapperKafkatorio.encodeToString(actual) val encoded = jsonMapperKafkatorio.encodeToString(actual)
encoded.shouldEqualJson(json) encoded.shouldEqualJson(json)

View file

@ -1,16 +1,16 @@
package dev.adamko.kafkatorio.processor package dev.adamko.kafkatorio.processor
import dev.adamko.kafkatorio.processor.topology.buildFactorioServerMap
import dev.adamko.kafkatorio.processor.topology.factorioServerPacketStream import dev.adamko.kafkatorio.processor.topology.factorioServerPacketStream
import dev.adamko.kafkatorio.processor.topology.groupTilesIntoChunksWithColours
import dev.adamko.kafkatorio.processor.topology.playerUpdatesToWsServer import dev.adamko.kafkatorio.processor.topology.playerUpdatesToWsServer
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 dev.adamko.kafkatorio.processor.topology.tileProtoColourDictionary
import java.time.Duration import java.time.Duration
import org.apache.kafka.streams.KafkaStreams import org.apache.kafka.streams.KafkaStreams
import org.apache.kafka.streams.StreamsBuilder import org.apache.kafka.streams.StreamsBuilder
import org.apache.kafka.streams.TopologyDescription import org.apache.kafka.streams.TopologyDescription
class KafkatorioTopology( class KafkatorioTopology(
private val websocketServer: WebsocketServer, private val websocketServer: WebsocketServer,
private val builder: StreamsBuilder = StreamsBuilder(), private val builder: StreamsBuilder = StreamsBuilder(),
@ -20,17 +20,27 @@ class KafkatorioTopology(
const val sourceTopic = "factorio-server-log" const val sourceTopic = "factorio-server-log"
} }
fun build() { fun build() {
val packets = factorioServerPacketStream(builder) val packets = factorioServerPacketStream(builder)
splitFactorioServerPacketStream(packets) splitFactorioServerPacketStream(packets)
playerUpdatesToWsServer(websocketServer, builder) playerUpdatesToWsServer(websocketServer, builder)
val serverMapDataTable = buildFactorioServerMap(packets) // val serverMapDataTable = buildFactorioServerMap(packets, tilePrototypesTable)
saveMapTiles(serverMapDataTable) val tileProtoColourDict = tileProtoColourDictionary(packets)
val tilePrototypesTable = groupTilesIntoChunksWithColours(
packets,
tileProtoColourDict,
)
saveMapTiles(tilePrototypesTable)
// the old way... // the old way...

View file

@ -1,127 +1,196 @@
package dev.adamko.kafkatorio.processor.topology package dev.adamko.kafkatorio.processor.topology
import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate import dev.adamko.kafkatorio.events.schema.ColourHex
import dev.adamko.kafkatorio.events.schema.FactorioEvent import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioPrototypes
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import dev.adamko.kafkatorio.events.schema.MapChunk import dev.adamko.kafkatorio.events.schema.MapChunk
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
import dev.adamko.kafkatorio.events.schema.MapTilePosition import dev.adamko.kafkatorio.events.schema.MapTilePosition
import dev.adamko.kafkatorio.events.schema.MapTilePrototype
import dev.adamko.kafkatorio.events.schema.MapTiles import dev.adamko.kafkatorio.events.schema.MapTiles
import dev.adamko.kafkatorio.processor.serdes.jsonMapper import dev.adamko.kafkatorio.events.schema.converters.toMapChunkPosition
import dev.adamko.kafkatorio.processor.serdes.kxsBinary import dev.adamko.kafkatorio.processor.serdes.kxsBinary
import dev.adamko.kotka.extensions.groupedAs import dev.adamko.kotka.extensions.groupedAs
import dev.adamko.kotka.extensions.materializedAs import dev.adamko.kotka.extensions.materializedAs
import dev.adamko.kotka.extensions.materializedWith import dev.adamko.kotka.extensions.materializedWith
import dev.adamko.kotka.extensions.streams.aggregate import dev.adamko.kotka.extensions.streams.flatMap
import dev.adamko.kotka.extensions.tables.mapValues import dev.adamko.kotka.extensions.streams.reduce
import dev.adamko.kotka.extensions.tables.join
import dev.adamko.kotka.kxs.serde import dev.adamko.kotka.kxs.serde
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.apache.kafka.streams.kstream.KStream import org.apache.kafka.streams.kstream.KStream
import org.apache.kafka.streams.kstream.KTable import org.apache.kafka.streams.kstream.KTable
//@Serializable
//data class FactorioServerMap(
// val serverId: FactorioServerId,
// val tilePrototypes: Map<PrototypeName, MapTilePrototype>,
// val surfaces: Map<SurfaceIndex, FactorioMapSurface>,
//)
//@Serializable
//data class FactorioMapSurface(
// val index: SurfaceIndex,
// val tiles: Map<MapTilePosition, PrototypeName>,
//)
@Serializable @Serializable
data class FactorioServerMap( data class ServerMapChunkId(
val serverId: FactorioServerId, val serverId: FactorioServerId,
val tilePrototypes: Map<PrototypeName, MapTilePrototype>, val chunkPosition: MapChunkPosition,
val surfaces: Map<SurfaceIndex, FactorioMapSurface>, val surfaceIndex: SurfaceIndex,
val zoomLevel: ZoomLevel,
) )
@Serializable @Serializable
data class FactorioMapSurface( data class ServerMapChunkTiles<Data>(
val index: SurfaceIndex, val chunkId: ServerMapChunkId,
val tiles: Map<MapTilePosition, PrototypeName>, val map: Map<MapTilePosition, Data>,
)
/** Aggregate [KafkatorioPacket]s into a single [FactorioServerMap] (per [serverId][FactorioServerId]). */
fun buildFactorioServerMap(
factorioServerPacketStream: KStream<FactorioServerId, KafkatorioPacket>
): KTable<FactorioServerId, FactorioServerMap> {
return factorioServerPacketStream
.groupByKey(
groupedAs("factorio-server-map-data-grouping", jsonMapper.serde(), jsonMapper.serde())
)
.aggregate<FactorioServerId, KafkatorioPacket, FactorioServerMapAggregator>(
"factorio-server-map-data-aggregate",
materializedAs("factorio-server-map-aggregate-store", kxsBinary.serde(), kxsBinary.serde()),
{ FactorioServerMapAggregator() },
) { _: FactorioServerId, packet: KafkatorioPacket, aggregate: FactorioServerMapAggregator ->
aggregate + packet
}
.mapValues(
"finalise-factorio-server-map-aggregate",
materializedWith(kxsBinary.serde(), kxsBinary.serde())
) { readOnlyKey, aggregate ->
val surfaces = aggregate.surfaces.mapValues { (_, surface) ->
FactorioMapSurface(surface.index, surface.tiles)
}
FactorioServerMap(readOnlyKey, aggregate.tilePrototypes, surfaces)
}
}
@Serializable
private data class FactorioServerMapAggregator(
val tilePrototypes: MutableMap<PrototypeName, MapTilePrototype> = mutableMapOf(),
val surfaces: MutableMap<SurfaceIndex, FactorioMapSurfaceAggregator> = mutableMapOf(),
) { ) {
operator fun plus(other: ServerMapChunkTiles<Data>) = copy(map = map + other.map)
}
operator fun plus(packet: KafkatorioPacket): FactorioServerMapAggregator {
return when (packet) { fun groupTilesIntoChunksWithColours(
is FactorioEvent -> { factorioServerPacketStream: KStream<FactorioServerId, KafkatorioPacket>,
tileProtoColourDict: KTable<FactorioServerId, TileColourDict>,
): KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> {
// group tiles by server & surface & chunk
val chunkedTilesTable: KTable<ServerMapChunkId, ServerMapChunkTiles<TileProtoHashCode>> =
factorioServerPacketStream
.flatMap("server-map-data.tiles.flatMapByChunk") { key: FactorioServerId, packet: KafkatorioPacket ->
val mapTileData = when (packet) {
is FactorioEvent ->
when (val data = packet.data) { when (val data = packet.data) {
is MapChunk -> plus(data) is MapChunk -> data.tiles
is MapTiles -> plus(data) is MapTiles -> data
// is MapTile -> {} // hmmm... no surface index else -> null
else -> this
}
}
is FactorioPrototypes -> plus(packet)
is FactorioConfigurationUpdate -> this
} }
else -> null
} }
operator fun plus(mapTiles: MapTiles): FactorioServerMapAggregator { val zoomLevel = ZoomLevel.ZOOM_0
// println("FactorioServerMapAggregator: adding ${mapTiles.tiles.size} MapTiles from surface ${mapTiles.surfaceIndex}")
val surfaceIndex = SurfaceIndex(mapTiles.surfaceIndex) //
// ZoomLevel
// .values
// .mapNotNull { zoomLevel ->
val surface = surfaces.getOrPut(surfaceIndex) { FactorioMapSurfaceAggregator(surfaceIndex) } mapTileData
?.tiles
val surfaceTiles = mapTiles.tiles.associate { it.position to PrototypeName(it.prototypeName) } ?.groupBy { tile ->
surface.tiles.putAll(surfaceTiles) tile.position.toMapChunkPosition(zoomLevel.chunkSize)
return this
} }
?.map { (chunkPos, tiles) ->
operator fun plus(mapChunk: MapChunk): FactorioServerMapAggregator { val chunkId = ServerMapChunkId(
// println("FactorioServerMapAggregator: adding mapChunk ${mapChunk.position}") key,
return plus(mapChunk.tiles) chunkPos,
} SurfaceIndex(mapTileData.surfaceIndex),
zoomLevel,
operator fun plus(packet: FactorioPrototypes): FactorioServerMapAggregator {
// println("FactorioServerMapAggregator: adding FactorioPrototypes")
val packetPrototypes = packet.prototypes
.filterIsInstance<MapTilePrototype>()
.associateBy { PrototypeName(it.name) }
tilePrototypes.putAll(packetPrototypes)
return this
}
}
@Serializable
private data class FactorioMapSurfaceAggregator(
val index: SurfaceIndex,
val tiles: MutableMap<MapTilePosition, PrototypeName> = mutableMapOf(),
) )
val tilesMap = tiles.associate { it.position to TileProtoHashCode(it) }
val chunkTiles = ServerMapChunkTiles(chunkId, tilesMap)
chunkId to chunkTiles
} ?: listOf()
// }.flatten()
}
.groupByKey(
groupedAs("server-map-data.tiles.group", kxsBinary.serde(), kxsBinary.serde())
)
.reduce(
"server-map-data.tiles.reduce",
materializedAs("server-map-data.tiles.store", kxsBinary.serde(), kxsBinary.serde()),
) { a, b -> a + b }
val colourisedChunkTable: KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> =
chunkedTilesTable
.join(
other = tileProtoColourDict,
name = "server-map-data.join-tiles-with-prototypes",
materialized = materializedWith(kxsBinary.serde(), kxsBinary.serde()),
foreignKeyExtractor = { chunkTiles: ServerMapChunkTiles<TileProtoHashCode> -> chunkTiles.chunkId.serverId }
) { tiles: ServerMapChunkTiles<TileProtoHashCode>, colourDict: TileColourDict ->
ServerMapChunkTiles(
tiles.chunkId,
tiles.map.mapValues { (_, code) ->
colourDict.map.getOrElse(code) {
println("missing prototype $code")
ColourHex.TRANSPARENT
}
}
)
}
return colourisedChunkTable
}
//@Serializable
//private data class FactorioServerMapAggregator(
// val tilePrototypes: SortedMap<PrototypeName, Colour> = TreeMap(),
// val surfaces: MutableMap<SurfaceIndex, FactorioMapSurfaceAggregator> = mutableMapOf(),
//) {
//
// operator fun plus(packet: KafkatorioPacket): FactorioServerMapAggregator {
// return when (packet) {
// is FactorioEvent -> {
// when (val data = packet.data) {
// is MapChunk -> plus(data)
// is MapTiles -> plus(data)
// else -> this
// }
// }
// is FactorioPrototypes -> plus(packet)
// is FactorioConfigurationUpdate -> this
// }
// }
//
// operator fun plus(mapTiles: MapTiles): FactorioServerMapAggregator {
//// println("FactorioServerMapAggregator: adding ${mapTiles.tiles.size} MapTiles from surface ${mapTiles.surfaceIndex}")
//
// val surfaceIndex = SurfaceIndex(mapTiles.surfaceIndex)
//
// val surface = surfaces.getOrPut(surfaceIndex) { FactorioMapSurfaceAggregator(surfaceIndex) }
//
// val surfaceTiles = mapTiles.tiles.associate { it.position to PrototypeName(it.prototypeName) }
// surface.tiles.putAll(surfaceTiles)
//
// return this
// }
//
// operator fun plus(mapChunk: MapChunk): FactorioServerMapAggregator {
//// println("FactorioServerMapAggregator: adding mapChunk ${mapChunk.position}")
// return plus(mapChunk.tiles)
// }
//
// operator fun plus(packet: FactorioPrototypes): FactorioServerMapAggregator {
//// println("FactorioServerMapAggregator: adding FactorioPrototypes")
//
// val packetPrototypes = packet.prototypes
// .filterIsInstance<MapTilePrototype>()
// .associate { PrototypeName(it.name) to it.mapColour }
//
// tilePrototypes.putAll(packetPrototypes)
//
// return this
// }
//
//}
//
//@Serializable
//private data class FactorioMapSurfaceAggregator(
// val index: SurfaceIndex,
// val tiles: MutableMap<MapTilePosition, PrototypeName> = mutableMapOf(),
//)

View file

@ -3,10 +3,9 @@ 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.events.schema.Colour import dev.adamko.kafkatorio.events.schema.ColourHex
import dev.adamko.kafkatorio.events.schema.MapChunkPosition import dev.adamko.kafkatorio.events.schema.MapChunkPosition
import dev.adamko.kafkatorio.events.schema.MapTilePosition import dev.adamko.kafkatorio.events.schema.MapTilePosition
import dev.adamko.kafkatorio.events.schema.converters.toMapChunkPosition
import java.awt.image.BufferedImage import java.awt.image.BufferedImage
import java.io.File import java.io.File
import kotlin.math.abs import kotlin.math.abs
@ -15,17 +14,17 @@ import kotlinx.coroutines.CoroutineName
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.conflate import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import org.apache.kafka.streams.kstream.KTable import org.apache.kafka.streams.kstream.KTable
@ -36,106 +35,84 @@ private val cScope = CoroutineScope(
) )
/** A bridge between Kafka and Kotlin */ /** A bridge between Kafka and Kotlin */
private val serverMapDataFlow: MutableStateFlow<FactorioServerMap?> = MutableStateFlow(null) private val chunkedTiles: MutableSharedFlow<ServerMapChunkTiles<ColourHex>> =
MutableSharedFlow(
0,
1,
BufferOverflow.SUSPEND
)
fun saveMapTiles( fun saveMapTiles(
serverMapTable: KTable<FactorioServerId, FactorioServerMap> serverMapTable: KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>>
) { ) {
serverMapTable serverMapTable
.toStream() .toStream()
.foreach { _, value -> .foreach { _, value ->
if (value != null) {
runBlocking(Dispatchers.Default) { runBlocking(Dispatchers.Default) {
serverMapDataFlow.emit(value) println("emitting chunkedTiles ${value.chunkId} tiles: ${value.map.size}")
chunkedTiles.emit(value)
}
} }
} }
cScope.launch { cScope.launch {
println("launching serverMapDataFlow merging") supervisorScope {
chunkedTiles
serverMapDataFlow
.filterNotNull()
serverMapDataFlow
.filterNotNull()
.conflate() .conflate()
// .transform { serverMapData -> .distinctUntilChanged()
.flatMapMerge(10) { serverMapData -> .debounce(5.seconds)
println("flatMapMerge serverMapData ${serverMapData.serverId}, surfaces ${serverMapData.surfaces.keys}") .map {
println("createMapTileImage ${it.chunkId}")
val prototypes = serverMapData.tilePrototypes createMapTileImage(it)
}
serverMapData .collect()
.surfaces
.values
// .forEach { surfaceData ->
.asFlow()
.flatMapMerge(10) { surfaceData ->
println("flatMapMerge surfaceData ${surfaceData.index}")
// get all tile colours for the surface
val surfaceTileColours = surfaceData.tiles
.entries
.mapNotNull { (tilePosition, prototypeName) ->
prototypes[prototypeName]?.let { prototype ->
tilePosition to prototype.mapColour
} }
} }
println("surfaceTileColours.size ${surfaceTileColours.size}")
// split a surface into chunks, per zoom-level // cScope.launch {
ZoomLevel.values // println("launching serverMapDataFlow merging")
// .forEach { zoom -> //
.asFlow() // chunkedTiles
.flatMapMerge(10) { zoom -> // .runningFold(
println("flatMapMerge zoom $zoom") // mapOf<ServerMapChunkId, MutableSharedFlow<ServerMapChunkTiles<ColourHex>>>()
// ) { accumulator, value ->
surfaceTileColours //
.groupBy { (tilePosition, _) -> // println("folding chunkedTiles ${value.chunkId}, current flows: ${accumulator.keys}")
tilePosition.toMapChunkPosition(zoom.chunkSize) //
} // val chunkFlow = accumulator.getOrElse(value.chunkId) {
.entries //
// .forEach { (chunkPosition, tileColours) -> // println("creating new MutableSharedFlow for ${value.chunkId}")
.asFlow() //
.mapLatest { (chunkPosition, tileColours) -> // val input = MutableSharedFlow<ServerMapChunkTiles<ColourHex>>(
println("mapLatest chunkPosition $chunkPosition") // replay = 0,
// extraBufferCapacity = 1,
// mapLatest + delay = an alternative to debounce-per-key? // onBufferOverflow = BufferOverflow.DROP_OLDEST,
delay(30.seconds) // )
//
println("emitting ChunkData $chunkPosition") // supervisorScope {
// println("launching MutableSharedFlow for ${value.chunkId}")
ChunkData( // input
surfaceData.index, // .debounce(30.seconds)
zoom, // .mapLatest {
chunkPosition, // println("createMapTileImage ${it.chunkId}")
tileColours.toMap() // createMapTileImage(it)
) // }.launchIn(this)
} // }
} //
} // input
} // }
.onEach { chunkData -> //
// chunkFlow.emit(value)
println("starting to save chunkData ${chunkData.chunkPosition}...") //
// accumulator + (value.chunkId to chunkFlow)
val chunkImageFile = File(chunkData.filename.value) // }
if (chunkImageFile.parentFile.mkdirs()) { // .collect()
println("created new map tile parentFile directory ${chunkImageFile.absolutePath}") //
} // println("end of cScope.launch")
// }
val image =
createMapTileImage(chunkData.chunkPosition, chunkData.tileColours, chunkData.zoomLevel)
println("saving map tile $chunkImageFile")
val savedTile = image.output(PngWriter.NoCompression, chunkImageFile)
println("savedTile: $savedTile")
}
.launchIn(this)
}
} }
@ -145,35 +122,58 @@ private value class TilePngFilename(
) { ) {
constructor( constructor(
surfaceIndex: SurfaceIndex, chunkId: ServerMapChunkId
zoom: Int, ) : this(buildString {
chunkX: Int, append("src/main/resources/kafkatorio-web-map/s")
chunkY: Int, append(chunkId.surfaceIndex.index)
) : this("src/main/resources/kafkatorio-web-map/s${surfaceIndex.index}/z$zoom/x${chunkX}/y${chunkY}.png") append("/z")
append(chunkId.zoomLevel.level)
} append("/x")
append(chunkId.chunkPosition.x)
append("/y")
private data class ChunkData( append(chunkId.chunkPosition.y)
val surfaceIndex: SurfaceIndex, append(".png")
val zoomLevel: ZoomLevel, })
val chunkPosition: MapChunkPosition,
val tileColours: Map<MapTilePosition, Colour>,
) {
val filename = TilePngFilename(surfaceIndex, zoomLevel.level, chunkPosition.x, chunkPosition.y)
} }
private fun createMapTileImage(
chunkTiles: ServerMapChunkTiles<ColourHex>
) {
println("emitting ChunkData ${chunkTiles.chunkId.chunkPosition}")
println("starting to save chunkData ${chunkTiles.chunkId.chunkPosition}...")
val filename = TilePngFilename(chunkTiles.chunkId)
val chunkImageFile = File(filename.value)
if (chunkImageFile.parentFile.mkdirs()) {
println("created new map tile parentFile directory ${chunkImageFile.absolutePath}")
}
val image =
createMapTileImage(
chunkTiles.chunkId.chunkPosition,
chunkTiles.map,
chunkTiles.chunkId.zoomLevel
)
println("saving map tile $chunkImageFile")
val savedTile = image.output(PngWriter.NoCompression, chunkImageFile)
println("savedTile: $savedTile")
}
private fun createMapTileImage( private fun createMapTileImage(
chunkPosition: MapChunkPosition, chunkPosition: MapChunkPosition,
chunkColours: Map<MapTilePosition, Colour>, chunkColours: Map<MapTilePosition, ColourHex>,
z: ZoomLevel z: ZoomLevel
): ImmutableImage { ): ImmutableImage {
val chunkImage = ImmutableImage.filled( val chunkImage = ImmutableImage.filled(
z.chunkSize, z.chunkSize,
z.chunkSize, z.chunkSize,
COLOUR_TRANSPARENT.awt(), ColourHex.TRANSPARENT.toRgbColor().awt(),
BufferedImage.TYPE_INT_ARGB BufferedImage.TYPE_INT_ARGB
) )

View file

@ -2,6 +2,7 @@ package dev.adamko.kafkatorio.processor.topology
import com.sksamuel.scrimage.color.RGBColor import com.sksamuel.scrimage.color.RGBColor
import dev.adamko.kafkatorio.events.schema.Colour import dev.adamko.kafkatorio.events.schema.Colour
import dev.adamko.kafkatorio.events.schema.ColourHex
import kotlin.math.pow import kotlin.math.pow
import kotlin.math.roundToInt import kotlin.math.roundToInt
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -23,12 +24,12 @@ value class FactorioServerId(val id: String) {
} }
fun Colour.toRgbColor(): RGBColor { fun ColourHex.toRgbColor(): RGBColor {
return RGBColor( return RGBColor(
red.roundToInt(), red.toInt(),
green.roundToInt(), green.toInt(),
blue.roundToInt(), blue.toInt(),
alpha.roundToInt(), alpha.toInt(),
) )
} }
@ -36,15 +37,14 @@ fun Colour.toRgbColor(): RGBColor {
enum class ZoomLevel( enum class ZoomLevel(
val level: Int val level: Int
) { ) {
ZOOM_0(0), ZOOM_0(0), // 512
ZOOM_1(1), ZOOM_1(1), // 256
ZOOM_2(2), ZOOM_2(2), // 128
ZOOM_3(3), ZOOM_3(3), // 64
ZOOM_4(4), ZOOM_4(4), // 32
ZOOM_5(5),
; ;
val chunkSize: Int = 2f.pow(10 - level).roundToInt() val chunkSize: Int = 2f.pow(9 - level).roundToInt()
init { init {
require(chunkSize > 0) { "chunkSize must be positive" } require(chunkSize > 0) { "chunkSize must be positive" }

View file

@ -1,19 +1,17 @@
package dev.adamko.kafkatorio.processor.topology package dev.adamko.kafkatorio.processor.topology
import com.sksamuel.scrimage.color.RGBColor import dev.adamko.kafkatorio.events.schema.ColourHex
import dev.adamko.kafkatorio.events.schema.Colour
import dev.adamko.kafkatorio.events.schema.FactorioPrototypes import dev.adamko.kafkatorio.events.schema.FactorioPrototypes
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import dev.adamko.kafkatorio.events.schema.MapTile
import dev.adamko.kafkatorio.events.schema.MapTilePrototype import dev.adamko.kafkatorio.events.schema.MapTilePrototype
import dev.adamko.kafkatorio.processor.serdes.jsonMapper import dev.adamko.kafkatorio.processor.serdes.jsonMapper
import dev.adamko.kafkatorio.processor.serdes.kxsBinary import dev.adamko.kotka.extensions.materializedAs
import dev.adamko.kotka.extensions.consumedAs import dev.adamko.kotka.extensions.streams.mapValues
import dev.adamko.kotka.extensions.materializedWith import dev.adamko.kotka.extensions.streams.toTable
import dev.adamko.kotka.extensions.streams.flatMap
import dev.adamko.kotka.kxs.serde import dev.adamko.kotka.kxs.serde
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import org.apache.kafka.common.serialization.Serdes import org.apache.kafka.streams.kstream.KStream
import org.apache.kafka.streams.StreamsBuilder
import org.apache.kafka.streams.kstream.KTable import org.apache.kafka.streams.kstream.KTable
@ -24,50 +22,45 @@ value class PrototypeName(val name: String) {
} }
//fun prototypesTable(builder: StreamsBuilder): KTable<PrototypeName, MapTilePrototype> { @JvmInline
// return builder.stream<FactorioPacketKey, FactorioPrototypes>( @Serializable
// "kafkatorio.${KafkatorioPacket.PacketType.PROTOTYPES}.all", value class TileProtoHashCode(val code: Int) {
// consumedAs( constructor(prototype: MapTilePrototype) : this(prototype.name.hashCode())
// "consume-all-prototypes-packets", constructor(tile: MapTile) : this(tile.prototypeName.hashCode())
// jsonMapper.serde(), }
// jsonMapper.serde()
// )
// )
// .flatMap("map-MapTilePrototype") { _, prototypes: FactorioPrototypes ->
// prototypes.prototypes
// .filterIsInstance<MapTilePrototype>()
// .map { PrototypeName(it.name) to it }
// }
// .toTable(
// materializedWith(
// kxsBinary.serde(),
// kxsBinary.serde()
// )
// )
//}
/** Get the [Colour] of each [MapTilePrototype] */ @JvmInline
fun tilePrototypeColourTable(builder: StreamsBuilder): KTable<PrototypeName, Colour> { @Serializable
return builder.stream<FactorioServerId, FactorioPrototypes>( value class TileColourDict(val map: Map<TileProtoHashCode, ColourHex>) {
"kafkatorio.${KafkatorioPacket.PacketType.PROTOTYPES}.all", operator fun plus(other: TileColourDict) = TileColourDict(this.map + other.map)
consumedAs( }
"consume-all-prototypes-packets",
/** Aggregate [KafkatorioPacket]s into a single [FactorioServerMap] (per [serverId][FactorioServerId]). */
fun tileProtoColourDictionary(
factorioServerPacketStream: KStream<FactorioServerId, KafkatorioPacket>
): KTable<FactorioServerId, TileColourDict> {
// get all the prototypes
return factorioServerPacketStream
.mapValues("server-map-data.tile-prototypes.mapValues") { _, packet ->
val map = when (packet) {
is FactorioPrototypes -> packet
.prototypes
.filterIsInstance<MapTilePrototype>()
.associate { TileProtoHashCode(it) to it.mapColour.toHex() }
else -> mapOf()
}
TileColourDict(map)
}
.toTable(
"server-map-data.tile-prototypes",
materializedAs(
"server-map-data.tile-prototypes.store",
jsonMapper.serde(), jsonMapper.serde(),
jsonMapper.serde() jsonMapper.serde()
) )
) )
.flatMap("map-MapTilePrototype") { _, prototypes: FactorioPrototypes ->
prototypes.prototypes
.filterIsInstance<MapTilePrototype>()
.map {
PrototypeName(it.name) to it.mapColour
}
}
.toTable(
materializedWith(
kxsBinary.serde(),
kxsBinary.serde()
)
)
} }

View file

@ -13,89 +13,89 @@ import kotlin.math.abs
import kotlin.math.roundToInt import kotlin.math.roundToInt
import org.apache.kafka.streams.kstream.KTable import org.apache.kafka.streams.kstream.KTable
val COLOUR_TRANSPARENT = RGBColor.fromAwt(Color(0f, 0f, 0f, 0f))
fun saveTileImages(
webMapTiles: KTable<WebMapTileChunkPosition, WebMapTileChunkPixels>
) {
webMapTiles
.toStream("get-webmap-tile-updates")
.foreach { position: WebMapTileChunkPosition, pixels: WebMapTileChunkPixels ->
runCatching {
saveMapTilesPng(position, pixels.pixels)
}.onFailure { e ->
println("error saving map tile png chunk: $position")
e.printStackTrace()
throw e
}
}
}
//val saveMapTilesContext = Dispatchers.IO.limitedParallelism(1)
private fun saveMapTilesPng(
chunkPosition: WebMapTileChunkPosition,
pixels: Map<MapTilePosition, Colour>,
) {
val chunkOriginX: Int = chunkPosition.x * chunkPosition.chunkSize // - 1
val chunkOriginY: Int = chunkPosition.y * chunkPosition.chunkSize // - 1
val surfaceIndex: Int = chunkPosition.surfaceIndex.index
val chunkImage =
ImmutableImage.filled(
chunkPosition.chunkSize,
chunkPosition.chunkSize,
COLOUR_TRANSPARENT.awt(),
BufferedImage.TYPE_INT_ARGB
)
pixels.forEach { (tilePosition, colour) ->
val rgbColour = colour.toRgbColor()
val pixelX = abs(abs(tilePosition.x) - abs(chunkOriginX))
val pixelY = abs(abs(tilePosition.y) - abs(chunkOriginY))
chunkImage.setColor(
pixelX,
pixelY,
rgbColour
)
}
val zoom = 1u
val file =
File(tileFilename("$surfaceIndex", "$zoom", "${chunkPosition.x}", "${chunkPosition.y}"))
if (file.parentFile.mkdirs()) {
println("created new map tile parentFile directory ${file.absolutePath}")
}
println("saving map tile $file")
val savedTile = chunkImage.output(PngWriter.NoCompression, file)
println("savedTile: $savedTile")
}
fun tileFilename(
surfaceIndex: String,
zoom: String,
chunkX: String,
chunkY: String,
) = "src/main/resources/kafkatorio-web-map/s${surfaceIndex}/z$zoom/x${chunkX}/y${chunkY}.png"
//fun subdivide(tile: ImmutableImage, size: Int) {
// //
// val halfSize = size / 2 //val COLOUR_TRANSPARENT: RGBColor = RGBColor.fromAwt(Color(0f, 0f, 0f, 0f))
// //
// val bl = tile.resizeTo(halfSize, halfSize, Position.BottomLeft) //fun saveTileImages(
// val savedTile = bl.output(PngWriter.NoCompression, file) // webMapTiles: KTable<WebMapTileChunkPosition, WebMapTileChunkPixels>
//) {
// //
// tile.resizeTo(halfSize, halfSize, Position.BottomRight) // webMapTiles
// tile.resizeTo(halfSize, halfSize, Position.TopLeft) // .toStream("get-webmap-tile-updates")
// tile.resizeTo(halfSize, halfSize, Position.TopRight) // .foreach { position: WebMapTileChunkPosition, pixels: WebMapTileChunkPixels ->
// runCatching {
// saveMapTilesPng(position, pixels.pixels)
// }.onFailure { e ->
// println("error saving map tile png chunk: $position")
// e.printStackTrace()
// throw e
// }
// }
//}
//
////val saveMapTilesContext = Dispatchers.IO.limitedParallelism(1)
//
//
//private fun saveMapTilesPng(
// chunkPosition: WebMapTileChunkPosition,
// pixels: Map<MapTilePosition, Colour>,
//) {
// val chunkOriginX: Int = chunkPosition.x * chunkPosition.chunkSize // - 1
// val chunkOriginY: Int = chunkPosition.y * chunkPosition.chunkSize // - 1
// val surfaceIndex: Int = chunkPosition.surfaceIndex.index
//
// val chunkImage =
// ImmutableImage.filled(
// chunkPosition.chunkSize,
// chunkPosition.chunkSize,
// COLOUR_TRANSPARENT.awt(),
// BufferedImage.TYPE_INT_ARGB
// )
//
// pixels.forEach { (tilePosition, colour) ->
//
// val rgbColour = colour.toRgbColor()
//
// val pixelX = abs(abs(tilePosition.x) - abs(chunkOriginX))
// val pixelY = abs(abs(tilePosition.y) - abs(chunkOriginY))
//
// chunkImage.setColor(
// pixelX,
// pixelY,
// rgbColour
// )
// //
// } // }
//
// val zoom = 1u
// val file =
// File(tileFilename("$surfaceIndex", "$zoom", "${chunkPosition.x}", "${chunkPosition.y}"))
//
// if (file.parentFile.mkdirs()) {
// println("created new map tile parentFile directory ${file.absolutePath}")
// }
//
// println("saving map tile $file")
// val savedTile = chunkImage.output(PngWriter.NoCompression, file)
// println("savedTile: $savedTile")
//}
//
//fun tileFilename(
// surfaceIndex: String,
// zoom: String,
// chunkX: String,
// chunkY: String,
//) = "src/main/resources/kafkatorio-web-map/s${surfaceIndex}/z$zoom/x${chunkX}/y${chunkY}.png"
//
////fun subdivide(tile: ImmutableImage, size: Int) {
////
//// val halfSize = size / 2
////
//// val bl = tile.resizeTo(halfSize, halfSize, Position.BottomLeft)
//// val savedTile = bl.output(PngWriter.NoCompression, file)
////
//// tile.resizeTo(halfSize, halfSize, Position.BottomRight)
//// tile.resizeTo(halfSize, halfSize, Position.TopLeft)
//// tile.resizeTo(halfSize, halfSize, Position.TopRight)
////
////}

View file

@ -0,0 +1,62 @@
package dev.adamko.kafkatorio.processor.misc
import dev.adamko.kafkatorio.events.schema.ColourHex
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
import dev.adamko.kafkatorio.events.schema.MapTilePosition
import dev.adamko.kafkatorio.processor.serdes.kxsBinary
import dev.adamko.kafkatorio.processor.topology.FactorioServerId
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkId
import dev.adamko.kafkatorio.processor.topology.ServerMapChunkTiles
import dev.adamko.kafkatorio.processor.topology.SurfaceIndex
import dev.adamko.kafkatorio.processor.topology.ZoomLevel
import java.util.UUID
import kotlinx.serialization.encodeToByteArray
import org.junit.jupiter.api.Test
class Blah {
@Test
fun asdad() {
val tiles = ServerMapChunkTiles<ColourHex>(
chunkId = ServerMapChunkId(
serverId = FactorioServerId(UUID.randomUUID().toString()),
chunkPosition = MapChunkPosition(Int.MAX_VALUE, Int.MAX_VALUE),
surfaceIndex = SurfaceIndex(Int.MAX_VALUE),
zoomLevel = ZoomLevel.ZOOM_0
),
buildMap {
repeat(512) { x ->
repeat(512) { y ->
put(MapTilePosition(x, y), ColourHex(255u, 255u, 255u, 255u))
}
}
}
)
val encode = kxsBinary.encodeToByteArray(tiles)
encode.inputStream()
val size = encode.size * Byte.SIZE_BYTES
println(size)
val map2 = buildList {
repeat(512) { x ->
repeat(512) { y ->
val element = (x to y) to Int.MAX_VALUE
add(element)
}
}
}
// .toTypedArray()
val encode2 = kxsBinary.encodeToByteArray(map2)
encode2.inputStream()
val size2 = encode2.size * Byte.SIZE_BYTES
println(size2)
}
}

View file

@ -1,4 +1,4 @@
# Do not edit manually. This file was created with task 'dockerEnv' # Do not edit manually. This file was created with task 'dockerEnv'
COMPOSE_PROJECT_NAME=kafkatorio COMPOSE_PROJECT_NAME=kafkatorio
FACTORIO_VERSION=1.1.53 FACTORIO_VERSION=1.1.53
KAFKATORIO_VERSION=0.2.7 KAFKATORIO_VERSION=0.2.8

View file

@ -1,3 +1,3 @@
# Do not edit manually. This file was created with task 'dockerEnv' # Do not edit manually. This file was created with task 'dockerEnv'
COMPOSE_PROJECT_NAME=kafkatorio COMPOSE_PROJECT_NAME=kafkatorio
KAFKATORIO_VERSION=0.2.7 KAFKATORIO_VERSION=0.2.8

View file

@ -120,3 +120,8 @@ log.retention.check.interval.ms=300000
### Compression ### ### Compression ###
#compression.type=snappy #compression.type=snappy
#linger.ms=5 #linger.ms=5
### record size ###
message.max.bytes=10485760
replica.fetch.max.bytes=10485760

View file

@ -1,4 +1,4 @@
ARG KAFKA_DL_URL=https://dlcdn.apache.org/kafka/3.0.0/kafka_2.13-3.0.0.tgz ARG KAFKA_DL_URL=https://dlcdn.apache.org/kafka/3.1.0/kafka_2.13-3.1.0.tgz
## builder ## ## builder ##
FROM debian:11-slim as Kafka-Download FROM debian:11-slim as Kafka-Download

View file

@ -4,7 +4,7 @@ import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig
plugins { plugins {
id("dev.adamko.kafkatorio.lang.kotlin-js") id("dev.adamko.kafkatorio.lang.kotlin-js")
kotlin("plugin.serialization") kotlin("plugin.serialization")
id("io.kvision") version "5.7.1" id("io.kvision") version "5.8.1"
} }
kotlin { kotlin {
@ -48,8 +48,8 @@ kotlin {
implementation(projects.modules.eventsSchema) implementation(projects.modules.eventsSchema)
implementation("org.jetbrains.kotlinx:kotlinx-nodejs:0.0.7") implementation(libs.kotlinx.nodejs)
implementation("org.jetbrains.kotlinx:kotlinx-html:0.7.3") implementation(libs.kotlinx.html)
kvision("kvision") kvision("kvision")
kvision("kvision-bootstrap") kvision("kvision-bootstrap")