- wicked sick size optimisation of map-tiles update packet
- fix ts-generator recursive value class unwrapping - move kafka state dir - try fixing kafka streams for tile generation (simplifying it, and fixing some nullability problems)
This commit is contained in:
parent
c5d4ae8d63
commit
b055397d25
14 changed files with 253 additions and 118 deletions
|
|
@ -15,7 +15,7 @@ interface FactorioPrototypes extends KafkatorioPacket {
|
||||||
interface MapTilePrototype extends FactorioPrototype {
|
interface MapTilePrototype extends FactorioPrototype {
|
||||||
canBeMined: boolean;
|
canBeMined: boolean;
|
||||||
collisionMasks: string[];
|
collisionMasks: string[];
|
||||||
layer: int;
|
layer: uint;
|
||||||
mapColour: Colour;
|
mapColour: Colour;
|
||||||
name: string;
|
name: string;
|
||||||
order: string;
|
order: string;
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,8 @@ interface EntityIdentifiers {
|
||||||
type: string;
|
type: string;
|
||||||
unitNumber: uint | null;
|
unitNumber: uint | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface MapTileDictionary {
|
||||||
|
protos: { [key: string]: string };
|
||||||
|
tilesXY: { [key: string]: { [key: string]: string } };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ interface FactorioEventUpdateKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface FactorioEventUpdate extends FactorioEventUpdateKey {
|
interface FactorioEventUpdate extends FactorioEventUpdateKey {
|
||||||
eventCounts: { [key: string]: int } | null;
|
eventCounts: { [key: string]: uint } | null;
|
||||||
updateType: FactorioEventUpdateType;
|
updateType: FactorioEventUpdateType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,7 +30,7 @@ interface PlayerUpdate extends FactorioEventUpdate, PlayerUpdateKey {
|
||||||
colour: Colour | null;
|
colour: Colour | null;
|
||||||
diedCause: EntityIdentifiers | null;
|
diedCause: EntityIdentifiers | null;
|
||||||
disconnectReason: string | null;
|
disconnectReason: string | null;
|
||||||
eventCounts: { [key: string]: int } | null;
|
eventCounts: { [key: string]: uint } | null;
|
||||||
forceIndex: uint | null;
|
forceIndex: uint | null;
|
||||||
index: uint;
|
index: uint;
|
||||||
isAdmin: boolean | null;
|
isAdmin: boolean | null;
|
||||||
|
|
@ -57,12 +57,12 @@ interface EntityUpdateKey extends EntityIdentifiers, FactorioEventUpdateKey {
|
||||||
|
|
||||||
interface EntityUpdate extends FactorioEventUpdate, EntityIdentifiers, EntityUpdateKey {
|
interface EntityUpdate extends FactorioEventUpdate, EntityIdentifiers, EntityUpdateKey {
|
||||||
chunkPosition: MapChunkPosition | null;
|
chunkPosition: MapChunkPosition | null;
|
||||||
eventCounts: { [key: string]: int } | null;
|
eventCounts: { [key: string]: uint } | null;
|
||||||
graphicsVariation: number | null;
|
graphicsVariation: uint8 | null;
|
||||||
health: float | null;
|
health: float | null;
|
||||||
isActive: boolean | null;
|
isActive: boolean | null;
|
||||||
isRotatable: boolean | null;
|
isRotatable: boolean | null;
|
||||||
lastUser: int | null;
|
lastUser: uint | null;
|
||||||
localisedDescription: string | null;
|
localisedDescription: string | null;
|
||||||
localisedName: string | null;
|
localisedName: string | null;
|
||||||
name: string;
|
name: string;
|
||||||
|
|
@ -79,12 +79,12 @@ interface MapChunkUpdateKey extends FactorioEventUpdateKey {
|
||||||
|
|
||||||
interface MapChunkUpdate extends FactorioEventUpdate, MapChunkUpdateKey {
|
interface MapChunkUpdate extends FactorioEventUpdate, MapChunkUpdateKey {
|
||||||
chunkPosition: MapChunkPosition;
|
chunkPosition: MapChunkPosition;
|
||||||
eventCounts: { [key: string]: int } | null;
|
eventCounts: { [key: string]: uint } | null;
|
||||||
force: uint | null;
|
force: uint | null;
|
||||||
isDeleted: boolean | null;
|
isDeleted: boolean | null;
|
||||||
player: uint | null;
|
player: uint | null;
|
||||||
robot: EntityIdentifiers | null;
|
robot: EntityIdentifiers | null;
|
||||||
surfaceIndex: uint;
|
surfaceIndex: uint;
|
||||||
tiles: MapTile[] | null;
|
tileDictionary: MapTileDictionary | null;
|
||||||
updateType: FactorioEventUpdateType;
|
updateType: FactorioEventUpdateType;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ export namespace EventDataCache {
|
||||||
|
|
||||||
log("Initialising global.DEFAULT_CACHE_DURATION_TICKS")
|
log("Initialising global.DEFAULT_CACHE_DURATION_TICKS")
|
||||||
global.defaultCacheDurationTicks = {
|
global.defaultCacheDurationTicks = {
|
||||||
"PLAYER": 60, // 1 second
|
"PLAYER": 15, // 0.25 seconds
|
||||||
"MAP_CHUNK": 60 * 30, // 30 seconds
|
"MAP_CHUNK": 60 * 30, // 30 seconds
|
||||||
"ENTITY": 60 * 2, // 2 seconds
|
"ENTITY": 60 * 2, // 2 seconds
|
||||||
"default": 60 * 5, // 5 seconds
|
"default": 60 * 5, // 5 seconds
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,17 @@ import CacheData = EventDataCache.CacheData;
|
||||||
type MapChunkUpdater = (data: CacheData<"MAP_CHUNK">) => void
|
type MapChunkUpdater = (data: CacheData<"MAP_CHUNK">) => void
|
||||||
|
|
||||||
|
|
||||||
|
const mapProtoNameToKey: { [key: string]: string } = {}
|
||||||
|
let protoIndex: uint = 0
|
||||||
|
|
||||||
|
function getProtoKey(protoName: string): string {
|
||||||
|
if (mapProtoNameToKey[protoName] == undefined) {
|
||||||
|
mapProtoNameToKey[protoName] = `${protoIndex++}`
|
||||||
|
}
|
||||||
|
return mapProtoNameToKey[protoName]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
function mapTilesUpdateDebounce(
|
function mapTilesUpdateDebounce(
|
||||||
surface: LuaSurface | undefined,
|
surface: LuaSurface | undefined,
|
||||||
chunkPosition: ChunkPositionTable,
|
chunkPosition: ChunkPositionTable,
|
||||||
|
|
@ -27,24 +38,26 @@ function mapTilesUpdateDebounce(
|
||||||
updateType: "MAP_CHUNK",
|
updateType: "MAP_CHUNK",
|
||||||
},
|
},
|
||||||
data => {
|
data => {
|
||||||
if (data.tiles == undefined) {
|
data.tileDictionary ??= <MapTileDictionary>{
|
||||||
data.tiles = []
|
tilesXY: {},
|
||||||
|
protos: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const tile of tiles) {
|
for (let tile of tiles) {
|
||||||
data.tiles.push(
|
|
||||||
{
|
const xString = `${tile.position.x}`
|
||||||
x: tile.position.x,
|
const yString = `${tile.position.y}`
|
||||||
y: tile.position.y,
|
const protoKey = getProtoKey(tile.name)
|
||||||
proto: tile.name,
|
|
||||||
}
|
data.tileDictionary.protos[protoKey] = tile.name
|
||||||
)
|
|
||||||
|
data.tileDictionary.tilesXY[xString] ??= {}
|
||||||
|
data.tileDictionary.tilesXY[xString][yString] = protoKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.eventCounts == undefined) {
|
data.eventCounts ??= {}
|
||||||
data.eventCounts = {}
|
data.eventCounts[eventName] ??= 0
|
||||||
}
|
data.eventCounts[eventName]++
|
||||||
data.eventCounts[eventName] = ((data.eventCounts ?? {}) [eventName] ?? 0) + 1
|
|
||||||
|
|
||||||
if (updater != undefined) {
|
if (updater != undefined) {
|
||||||
updater(data)
|
updater(data)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package dev.adamko.kafkatorio.events.schema
|
package dev.adamko.kafkatorio.events.schema
|
||||||
|
|
||||||
import kotlin.jvm.JvmInline
|
import kotlin.jvm.JvmInline
|
||||||
import kotlinx.serialization.EncodeDefault
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -101,3 +100,41 @@ interface EntityIdentifiers {
|
||||||
val name: String
|
val name: String
|
||||||
val type: String
|
val type: String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size-optimised 2D map of x/y tile positions and prototype-name.
|
||||||
|
*
|
||||||
|
* Each X/Y coordinate maps to an arbitrary [PrototypeKey] (which is only valid for any specific
|
||||||
|
* instance). [protos] is specific to each instance of [MapTileDictionary], can be used to convert from a
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class MapTileDictionary(
|
||||||
|
/** Map an X,Y coordinate a prototype name */
|
||||||
|
val tilesXY: Map<String, Map<String, PrototypeKey>>,
|
||||||
|
val protos: Map<PrototypeKey, PrototypeName>
|
||||||
|
) {
|
||||||
|
fun toMapTileList(): List<MapTile> = buildList {
|
||||||
|
tilesXY.forEach { (xString, row) ->
|
||||||
|
row.forEach { (yString, protoIndex) ->
|
||||||
|
val x = xString.toIntOrNull()
|
||||||
|
val y = yString.toIntOrNull()
|
||||||
|
val protoName = protos[protoIndex]
|
||||||
|
if (x != null && y != null && protoName != null) {
|
||||||
|
add(MapTile(x, y, protoName))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun MapTileDictionary?.isNullOrEmpty(): Boolean {
|
||||||
|
return this == null || (tilesXY.isEmpty() && tilesXY.all { it.value.isEmpty() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
@JvmInline
|
||||||
|
value class PrototypeKey(val index: String)
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ data class EntityUpdate(
|
||||||
override val eventCounts: Map<String, UInt>? = null,
|
override val eventCounts: Map<String, UInt>? = null,
|
||||||
|
|
||||||
val chunkPosition: MapChunkPosition? = null,
|
val chunkPosition: MapChunkPosition? = null,
|
||||||
val graphicsVariation: UShort? = null,
|
val graphicsVariation: UByte? = null,
|
||||||
val health: Float? = null,
|
val health: Float? = null,
|
||||||
val isActive: Boolean? = null,
|
val isActive: Boolean? = null,
|
||||||
val isRotatable: Boolean? = null,
|
val isRotatable: Boolean? = null,
|
||||||
|
|
@ -180,11 +180,8 @@ data class MapChunkUpdate(
|
||||||
val player: PlayerIndex? = null,
|
val player: PlayerIndex? = null,
|
||||||
val robot: EntityIdentifiers? = null,
|
val robot: EntityIdentifiers? = null,
|
||||||
val force: ForceIndex? = null,
|
val force: ForceIndex? = null,
|
||||||
/** updated tiles - might be partial */
|
/** updated tiles - might be partial and not all tiles in the chunk */
|
||||||
// note: this must be a List, not a Map<Location, ProtoName>
|
val tileDictionary: MapTileDictionary? = null,
|
||||||
// because JSON can't have non-string keys.
|
|
||||||
@Contextual
|
|
||||||
val tiles: List<MapTile>? = null,
|
|
||||||
val isDeleted: Boolean? = null,
|
val isDeleted: Boolean? = null,
|
||||||
) : FactorioEventUpdate(), MapChunkUpdateKey {
|
) : FactorioEventUpdate(), MapChunkUpdateKey {
|
||||||
@EncodeDefault
|
@EncodeDefault
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,15 @@
|
||||||
package dev.adamko.kafkatorio.events.schema
|
package dev.adamko.kafkatorio.events.schema
|
||||||
|
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
import kotlin.reflect.KClass
|
import kotlin.reflect.KClass
|
||||||
|
import kotlin.reflect.KClassifier
|
||||||
import kotlin.reflect.KProperty
|
import kotlin.reflect.KProperty
|
||||||
import kotlin.reflect.KType
|
import kotlin.reflect.KType
|
||||||
import kotlin.reflect.KTypeProjection
|
import kotlin.reflect.KTypeProjection
|
||||||
import kotlin.reflect.full.createType
|
import kotlin.reflect.full.createType
|
||||||
import kotlin.reflect.full.declaredMemberProperties
|
import kotlin.reflect.full.declaredMemberProperties
|
||||||
|
import kotlin.reflect.full.starProjectedType
|
||||||
import kotlin.reflect.full.withNullability
|
import kotlin.reflect.full.withNullability
|
||||||
import me.ntrrgc.tsGenerator.ClassTransformer
|
import me.ntrrgc.tsGenerator.ClassTransformer
|
||||||
|
|
||||||
|
|
@ -14,54 +18,77 @@ import me.ntrrgc.tsGenerator.ClassTransformer
|
||||||
*/
|
*/
|
||||||
object ValueClassTransformer : ClassTransformer {
|
object ValueClassTransformer : ClassTransformer {
|
||||||
|
|
||||||
|
private val mappedValueClasses: MutableMap<KType, KType> = mutableMapOf(
|
||||||
|
// don't unwrap unsigned numbers - they're mapped to Factorio types
|
||||||
|
UInt::class.starProjectedType to UInt::class.starProjectedType,
|
||||||
|
UByte::class.starProjectedType to UByte::class.starProjectedType,
|
||||||
|
UShort::class.starProjectedType to UShort::class.starProjectedType,
|
||||||
|
ULong::class.starProjectedType to ULong::class.starProjectedType,
|
||||||
|
)
|
||||||
|
|
||||||
override fun transformPropertyType(type: KType, property: KProperty<*>, klass: KClass<*>): KType {
|
override fun transformPropertyType(type: KType, property: KProperty<*>, klass: KClass<*>): KType {
|
||||||
|
val unwrappedType =
|
||||||
val classifier = type.classifier ?: return super.transformPropertyType(type, property, klass)
|
unwrapValueClassType(type, property, klass)
|
||||||
|
?: unwrapArgs(type, property, klass)
|
||||||
val valueClassType = unwrapValueClassType(type)
|
return when (unwrappedType) {
|
||||||
|
null -> super.transformPropertyType(type, property, klass)
|
||||||
if (valueClassType != null) {
|
else -> super.transformPropertyType(unwrappedType, property, klass)
|
||||||
println("$klass has a value class $type -> $valueClassType")
|
|
||||||
return valueClassType
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val hasValueClassTypeParameters = type.arguments
|
private fun unwrapArgs(type: KType, property: KProperty<*>, klass: KClass<*>): KType? {
|
||||||
.mapNotNull { it.type?.classifier }
|
|
||||||
.filterIsInstance<KClass<*>>()
|
|
||||||
.any { it.isValue }
|
|
||||||
|
|
||||||
if (hasValueClassTypeParameters) {
|
val classifier: KClassifier = type.classifier ?: return type
|
||||||
|
|
||||||
val args = type.arguments.map { arg ->
|
return if (type.arguments.isNotEmpty()) {
|
||||||
when (val vct = unwrapValueClassType(arg.type)) {
|
println("unwrapping args for $type - ${type.arguments}")
|
||||||
null -> arg
|
|
||||||
else -> KTypeProjection(arg.variance, vct.withNullability(arg.type!!.isMarkedNullable))
|
val args = type.arguments.map { arg: KTypeProjection ->
|
||||||
}
|
// recursively map each argument
|
||||||
|
val argType = arg.type!!
|
||||||
|
val converted = transformPropertyType(argType, property, klass)
|
||||||
|
KTypeProjection(arg.variance, converted.withNullability(argType.isMarkedNullable))
|
||||||
}
|
}
|
||||||
|
|
||||||
println("$klass has a prop with value class type parameters ${type.arguments} -> $args")
|
classifier.createType(
|
||||||
|
|
||||||
return classifier.createType(
|
|
||||||
arguments = args,
|
arguments = args,
|
||||||
nullable = type.isMarkedNullable,
|
nullable = type.isMarkedNullable,
|
||||||
annotations = type.annotations,
|
annotations = type.annotations,
|
||||||
)
|
)
|
||||||
}
|
} else {
|
||||||
|
null
|
||||||
return super.transformPropertyType(type, property, klass)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun unwrapValueClassType(type: KType?): KType? {
|
|
||||||
val klass = type?.classifier as? KClass<*> ?: return null
|
|
||||||
|
|
||||||
return when {
|
|
||||||
klass.isValue -> klass
|
|
||||||
.declaredMemberProperties
|
|
||||||
.first()
|
|
||||||
.returnType
|
|
||||||
.withNullability(type.isMarkedNullable)
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun unwrapValueClassType(type: KType, property: KProperty<*>, klass: KClass<*>): KType? {
|
||||||
|
|
||||||
|
val valueKlass: KClass<*> = type.classifier as? KClass<*> ?: return null
|
||||||
|
|
||||||
|
return if (isValueClass(type)) {
|
||||||
|
val mappedType = mappedValueClasses.getOrPut(type.withNullability(false)) {
|
||||||
|
|
||||||
|
val result = valueKlass
|
||||||
|
.declaredMemberProperties
|
||||||
|
.first()
|
||||||
|
.returnType
|
||||||
|
.withNullability(type.isMarkedNullable)
|
||||||
|
|
||||||
|
println("unwrapping value class $type -> $result")
|
||||||
|
|
||||||
|
// recursively unwrap...
|
||||||
|
val transformed = transformPropertyType(result, property, klass)
|
||||||
|
transformed.withNullability(false)
|
||||||
|
}
|
||||||
|
mappedType.withNullability(type.isMarkedNullable)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
private fun isValueClass(type: KType?): Boolean {
|
||||||
|
contract { returnsNotNull() implies (type != null) }
|
||||||
|
return (type?.classifier as? KClass<*>)?.isValue == true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,12 +21,9 @@ fun main(args: Array<String>) {
|
||||||
add(FactorioPrototypes::class)
|
add(FactorioPrototypes::class)
|
||||||
add(FactorioConfigurationUpdate::class)
|
add(FactorioConfigurationUpdate::class)
|
||||||
|
|
||||||
|
|
||||||
add(FactorioEventUpdatePacket::class)
|
add(FactorioEventUpdatePacket::class)
|
||||||
addAll(FactorioEventUpdate::class.sealedSubclasses)
|
addAll(FactorioEventUpdate::class.sealedSubclasses)
|
||||||
addAll(FactorioEventUpdateKey::class.sealedSubclasses)
|
addAll(FactorioEventUpdateKey::class.sealedSubclasses)
|
||||||
// addAll(FactorioEventUpdateData::class.sealedSubclasses)
|
|
||||||
|
|
||||||
},
|
},
|
||||||
mappings = mapOf(
|
mappings = mapOf(
|
||||||
// builtin Factorio numeric types > `typed-factorio/generated/builtin-types.d.ts`
|
// builtin Factorio numeric types > `typed-factorio/generated/builtin-types.d.ts`
|
||||||
|
|
@ -74,7 +71,6 @@ private fun splitDefinitions(definitions: String): Map<String, String> {
|
||||||
.split("\n\n")
|
.split("\n\n")
|
||||||
.groupBy { def ->
|
.groupBy { def ->
|
||||||
|
|
||||||
|
|
||||||
when {
|
when {
|
||||||
|
|
||||||
"interface FactorioObjectData " in def ||
|
"interface FactorioObjectData " in def ||
|
||||||
|
|
|
||||||
1
modules/events-server/.gitignore
vendored
Normal file
1
modules/events-server/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
kafka-state/
|
||||||
|
|
@ -66,7 +66,7 @@ tasks.withType<KotlinCompile> {
|
||||||
val kafkaStateDirDelete by tasks.registering(Delete::class) {
|
val kafkaStateDirDelete by tasks.registering(Delete::class) {
|
||||||
group = project.name
|
group = project.name
|
||||||
mustRunAfter(kafkaConsumersDelete)
|
mustRunAfter(kafkaConsumersDelete)
|
||||||
delete(layout.buildDirectory.dir("kafka-state"))
|
delete(layout.projectDirectory.dir("kafka-state"))
|
||||||
}
|
}
|
||||||
|
|
||||||
val kafkaConsumersDelete by tasks.registering(KafkaConsumerGroupsTask.DeleteAll::class) {
|
val kafkaConsumersDelete by tasks.registering(KafkaConsumerGroupsTask.DeleteAll::class) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@ package dev.adamko.kafkatorio.processor
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.events.schema.ColourHex
|
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.FactorioEventUpdate
|
||||||
|
import dev.adamko.kafkatorio.events.schema.FactorioEventUpdatePacket
|
||||||
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
|
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
|
||||||
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
|
||||||
|
|
@ -33,6 +35,7 @@ import org.apache.kafka.streams.KafkaStreams
|
||||||
import org.apache.kafka.streams.StreamsBuilder
|
import org.apache.kafka.streams.StreamsBuilder
|
||||||
import org.apache.kafka.streams.StreamsConfig
|
import org.apache.kafka.streams.StreamsConfig
|
||||||
import org.apache.kafka.streams.Topology
|
import org.apache.kafka.streams.Topology
|
||||||
|
import org.apache.kafka.streams.TopologyDescription
|
||||||
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
|
||||||
|
|
||||||
|
|
@ -69,20 +72,29 @@ internal class KafkatorioTopology(
|
||||||
val tileProtoColourDict = tileProtoColourDictionary(protosStream)
|
val tileProtoColourDict = tileProtoColourDictionary(protosStream)
|
||||||
|
|
||||||
|
|
||||||
val mapTilesStream: KStream<FactorioServerId, FactorioEvent> =
|
// val mapTilesStream: KStream<FactorioServerId, FactorioEvent> =
|
||||||
|
// builder.stream(
|
||||||
|
// "kafkatorio.${KafkatorioPacket.PacketType.EVENT}.${FactorioObjectData.ObjectName.LuaTiles}",
|
||||||
|
// consumedAs("consume.map-tiles", jsonMapper.serde(), jsonMapper.serde())
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// val mapChunksStream: KStream<FactorioServerId, FactorioEvent> =
|
||||||
|
// builder.stream(
|
||||||
|
// "kafkatorio.${KafkatorioPacket.PacketType.EVENT}.${FactorioObjectData.ObjectName.MapChunk}",
|
||||||
|
// consumedAs("consume.map-chunks", jsonMapper.serde(), jsonMapper.serde())
|
||||||
|
// )
|
||||||
|
|
||||||
|
val mapChunksUpdatesStream: KStream<FactorioServerId, FactorioEventUpdatePacket> =
|
||||||
builder.stream(
|
builder.stream(
|
||||||
"kafkatorio.${KafkatorioPacket.PacketType.EVENT}.${FactorioObjectData.ObjectName.LuaTiles}",
|
"kafkatorio.${KafkatorioPacket.PacketType.UPDATE}.${FactorioEventUpdate.FactorioEventUpdateType.MAP_CHUNK}",
|
||||||
consumedAs("consume.map-tiles", jsonMapper.serde(), jsonMapper.serde())
|
consumedAs("consume.map-chunk-updates", jsonMapper.serde(), jsonMapper.serde())
|
||||||
)
|
|
||||||
val mapChunksStream: KStream<FactorioServerId, FactorioEvent> =
|
|
||||||
builder.stream(
|
|
||||||
"kafkatorio.${KafkatorioPacket.PacketType.EVENT}.${FactorioObjectData.ObjectName.MapChunk}",
|
|
||||||
consumedAs("consume.map-chunks", jsonMapper.serde(), jsonMapper.serde())
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val groupedMapChunkTiles: KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> =
|
val groupedMapChunkTiles: KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> =
|
||||||
groupTilesIntoChunksWithColours(
|
groupTilesIntoChunksWithColours(
|
||||||
mapTilesStream,
|
// mapTilesStream,
|
||||||
mapChunksStream,
|
// mapChunksStream,
|
||||||
|
mapChunksUpdatesStream,
|
||||||
tileProtoColourDict,
|
tileProtoColourDict,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -90,7 +102,11 @@ internal class KafkatorioTopology(
|
||||||
.toStream("stream-grouped-map-tiles")
|
.toStream("stream-grouped-map-tiles")
|
||||||
.to(
|
.to(
|
||||||
TOPIC_GROUPED_MAP_CHUNKS,
|
TOPIC_GROUPED_MAP_CHUNKS,
|
||||||
producedAs("produce.grouped-map-chunks", kxsBinary.serde(), kxsBinary.serde())
|
producedAs(
|
||||||
|
"produce.grouped-map-chunks",
|
||||||
|
kxsBinary.serde<ServerMapChunkId?>(),
|
||||||
|
kxsBinary.serde<ServerMapChunkTiles<ColourHex>?>()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -140,10 +156,19 @@ internal class KafkatorioTopology(
|
||||||
val appId = props.compute(StreamsConfig.APPLICATION_ID_CONFIG) { _, v -> "$v.$id" } as? String
|
val appId = props.compute(StreamsConfig.APPLICATION_ID_CONFIG) { _, v -> "$v.$id" } as? String
|
||||||
props.setProperty(StreamsConfig.APPLICATION_ID_CONFIG, appId)
|
props.setProperty(StreamsConfig.APPLICATION_ID_CONFIG, appId)
|
||||||
|
|
||||||
|
|
||||||
val streams = KafkaStreams(topology, props)
|
val streams = KafkaStreams(topology, props)
|
||||||
streams.setUncaughtExceptionHandler(StreamsExceptionHandler())
|
streams.setUncaughtExceptionHandler(StreamsExceptionHandler())
|
||||||
|
|
||||||
|
val description: TopologyDescription = topology.describe()
|
||||||
|
println(
|
||||||
|
"""
|
||||||
|
|----------------
|
||||||
|
|$appId
|
||||||
|
|$description
|
||||||
|
|----------------
|
||||||
|
""".trimMargin()
|
||||||
|
)
|
||||||
|
|
||||||
coroutineContext.job.invokeOnCompletion {
|
coroutineContext.job.invokeOnCompletion {
|
||||||
println("closing $id KafkaStreams")
|
println("closing $id KafkaStreams")
|
||||||
streams.close(Duration.ofSeconds(1))
|
streams.close(Duration.ofSeconds(1))
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package dev.adamko.kafkatorio.processor.topology
|
package dev.adamko.kafkatorio.processor.topology
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.events.schema.ColourHex
|
import dev.adamko.kafkatorio.events.schema.ColourHex
|
||||||
import dev.adamko.kafkatorio.events.schema.FactorioEvent
|
import dev.adamko.kafkatorio.events.schema.FactorioEventUpdatePacket
|
||||||
import dev.adamko.kafkatorio.events.schema.MapChunk
|
|
||||||
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
|
import dev.adamko.kafkatorio.events.schema.MapChunkPosition
|
||||||
|
import dev.adamko.kafkatorio.events.schema.MapChunkUpdate
|
||||||
import dev.adamko.kafkatorio.events.schema.MapTile
|
import dev.adamko.kafkatorio.events.schema.MapTile
|
||||||
|
import dev.adamko.kafkatorio.events.schema.MapTileDictionary.Companion.isNullOrEmpty
|
||||||
import dev.adamko.kafkatorio.events.schema.MapTilePosition
|
import dev.adamko.kafkatorio.events.schema.MapTilePosition
|
||||||
import dev.adamko.kafkatorio.events.schema.MapTiles
|
import dev.adamko.kafkatorio.events.schema.MapTiles
|
||||||
import dev.adamko.kafkatorio.events.schema.SurfaceIndex
|
import dev.adamko.kafkatorio.events.schema.SurfaceIndex
|
||||||
|
|
@ -13,10 +14,8 @@ 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.filter
|
|
||||||
import dev.adamko.kotka.extensions.streams.flatMap
|
import dev.adamko.kotka.extensions.streams.flatMap
|
||||||
import dev.adamko.kotka.extensions.streams.mapValues
|
import dev.adamko.kotka.extensions.streams.mapValues
|
||||||
import dev.adamko.kotka.extensions.streams.merge
|
|
||||||
import dev.adamko.kotka.extensions.streams.reduce
|
import dev.adamko.kotka.extensions.streams.reduce
|
||||||
import dev.adamko.kotka.extensions.tables.join
|
import dev.adamko.kotka.extensions.tables.join
|
||||||
import dev.adamko.kotka.kxs.serde
|
import dev.adamko.kotka.kxs.serde
|
||||||
|
|
@ -44,39 +43,70 @@ data class ServerMapChunkTiles<Data>(
|
||||||
|
|
||||||
|
|
||||||
fun groupTilesIntoChunksWithColours(
|
fun groupTilesIntoChunksWithColours(
|
||||||
mapTilePacketsStream: KStream<FactorioServerId, FactorioEvent>,
|
// mapTilePacketsStream: KStream<FactorioServerId, FactorioEvent>,
|
||||||
mapChunksPacketsStream: KStream<FactorioServerId, FactorioEvent>,
|
// mapChunksPacketsStream: KStream<FactorioServerId, FactorioEvent>,
|
||||||
|
mapChunkUpdatePacketsStream: KStream<FactorioServerId, FactorioEventUpdatePacket>,
|
||||||
tileProtoColourDict: KTable<FactorioServerId, TileColourDict>,
|
tileProtoColourDict: KTable<FactorioServerId, TileColourDict>,
|
||||||
): KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> {
|
): KTable<ServerMapChunkId, ServerMapChunkTiles<ColourHex>> {
|
||||||
|
|
||||||
|
|
||||||
val mapChunkTilesStream: KStream<FactorioServerId, MapTiles> =
|
// val mapChunkTilesStream: KStream<FactorioServerId, MapTiles> =
|
||||||
mapChunksPacketsStream
|
// mapChunksPacketsStream
|
||||||
.filter { _, packet: FactorioEvent? ->
|
// .filter { _, packet: FactorioEvent? ->
|
||||||
packet != null
|
// when (val data = packet?.data) {
|
||||||
&& packet.data is MapChunk
|
// is MapChunk -> data.tiles.tiles.isNotEmpty()
|
||||||
&& !((packet.data as? MapChunk)?.tiles?.tiles.isNullOrEmpty())
|
// else -> false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .mapValues("map-chunk-packets.convert-to-map-tiles") { _, packet: FactorioEvent ->
|
||||||
|
// (packet.data as MapChunk).tiles
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// val mapTilesStream: KStream<FactorioServerId, MapTiles> =
|
||||||
|
// mapTilePacketsStream
|
||||||
|
// .filter { _, packet: FactorioEvent? ->
|
||||||
|
// when (val data = packet?.data) {
|
||||||
|
// is MapTiles -> data.tiles.isNotEmpty()
|
||||||
|
// else -> false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// .mapValues("map-tiles-packets.convert-to-map-tiles") { _, packet: FactorioEvent ->
|
||||||
|
// packet.data as MapTiles
|
||||||
|
// }
|
||||||
|
|
||||||
|
val mapChunkUpdatesStream: KStream<FactorioServerId, MapTiles> =
|
||||||
|
mapChunkUpdatePacketsStream
|
||||||
|
.filter { _, packet: FactorioEventUpdatePacket? ->
|
||||||
|
when (val data = packet?.update) {
|
||||||
|
is MapChunkUpdate -> !data.tileDictionary.isNullOrEmpty()
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.mapValues("map-chunk-packets.convert-to-map-tiles") { _, packet: FactorioEvent ->
|
.mapValues("map-chunk-update-packets.convert-to-map-tiles") { _, packet: FactorioEventUpdatePacket ->
|
||||||
(packet.data as MapChunk).tiles
|
|
||||||
|
val mapChunkUpdate: MapChunkUpdate = requireNotNull(packet.update as? MapChunkUpdate) {
|
||||||
|
"invalid UpdatePacket type $packet"
|
||||||
|
}
|
||||||
|
|
||||||
|
val tiles: List<MapTile> = requireNotNull(mapChunkUpdate.tileDictionary?.toMapTileList()) {
|
||||||
|
"invalid MapChunkUpdate packet, tiles must not be null"
|
||||||
|
}
|
||||||
|
require(tiles.isNotEmpty()) {
|
||||||
|
"invalid MapChunkUpdate packet, tiles must not be empty"
|
||||||
|
}
|
||||||
|
|
||||||
|
MapTiles(mapChunkUpdate.surfaceIndex, tiles)
|
||||||
}
|
}
|
||||||
|
|
||||||
val mapTilesStream: KStream<FactorioServerId, MapTiles> =
|
|
||||||
mapTilePacketsStream
|
|
||||||
.filter { _, packet: FactorioEvent? ->
|
|
||||||
packet != null
|
|
||||||
&& packet.data is MapTiles
|
|
||||||
&& !((packet.data as? MapTiles)?.tiles.isNullOrEmpty())
|
|
||||||
}
|
|
||||||
.mapValues("map-tiles-packets.convert-to-map-tiles") { _, packet: FactorioEvent ->
|
|
||||||
packet.data as MapTiles
|
|
||||||
}
|
|
||||||
|
|
||||||
// group tiles by server & surface & chunk
|
// group tiles by server & surface & chunk
|
||||||
val chunkedTilesTable: KTable<ServerMapChunkId, ServerMapChunkTiles<TileProtoHashCode>> =
|
val chunkedTilesTable: KTable<ServerMapChunkId, ServerMapChunkTiles<TileProtoHashCode>> =
|
||||||
mapTilesStream
|
mapChunkUpdatesStream
|
||||||
.merge("merge-map-chunk-tiles-with-map-tiles", mapChunkTilesStream)
|
// mapTilesStream
|
||||||
.filter { _, value -> value != null && value.tiles.isNotEmpty() }
|
// .merge("merge-mapTilesStream-with-mapChunkTilesStream", mapChunkTilesStream)
|
||||||
|
// .merge("merge-mapTilesStream-with-mapChunkUpdatesStream", mapChunkUpdatesStream)
|
||||||
|
// .filter { _, value -> !value?.tiles.isNullOrEmpty() }
|
||||||
.flatMap("server-map-data.tiles.flatMapByChunk") { key: FactorioServerId, mapTiles: MapTiles ->
|
.flatMap("server-map-data.tiles.flatMapByChunk") { key: FactorioServerId, mapTiles: MapTiles ->
|
||||||
|
|
||||||
val standardChunkSize = ChunkSize.MAX
|
val standardChunkSize = ChunkSize.MAX
|
||||||
|
|
@ -102,11 +132,15 @@ fun groupTilesIntoChunksWithColours(
|
||||||
chunkId to chunkTiles
|
chunkId to chunkTiles
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.filter("server-map-data.tiles.filter-not-empty") { _, value ->
|
// .filter("server-map-data.tiles.filter-not-empty") { _, value ->
|
||||||
value.map.isNotEmpty()
|
// value.map.isNotEmpty()
|
||||||
}
|
// }
|
||||||
.groupByKey(
|
.groupByKey(
|
||||||
groupedAs("server-map-data.tiles.group", kxsBinary.serde(), kxsBinary.serde())
|
groupedAs(
|
||||||
|
"server-map-data.tiles.group",
|
||||||
|
kxsBinary.serde<ServerMapChunkId>(),
|
||||||
|
kxsBinary.serde<ServerMapChunkTiles<TileProtoHashCode>>()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.reduce(
|
.reduce(
|
||||||
"server-map-data.tiles.reduce",
|
"server-map-data.tiles.reduce",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package dev.adamko.kafkatorio.processor.topology
|
package dev.adamko.kafkatorio.processor.topology
|
||||||
|
|
||||||
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
|
import dev.adamko.kafkatorio.events.schema.FactorioEventUpdate
|
||||||
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
|
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
|
||||||
import dev.adamko.kafkatorio.processor.WebsocketServer
|
import dev.adamko.kafkatorio.processor.WebsocketServer
|
||||||
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
import dev.adamko.kafkatorio.processor.serdes.jsonMapper
|
||||||
|
|
@ -15,8 +15,8 @@ fun playerUpdatesToWsServer(
|
||||||
builder: StreamsBuilder = StreamsBuilder(),
|
builder: StreamsBuilder = StreamsBuilder(),
|
||||||
) {
|
) {
|
||||||
builder.stream<FactorioServerId, KafkatorioPacket>(
|
builder.stream<FactorioServerId, KafkatorioPacket>(
|
||||||
"kafkatorio.${KafkatorioPacket.PacketType.EVENT}.${FactorioObjectData.ObjectName.LuaPlayer}",
|
"kafkatorio.${KafkatorioPacket.PacketType.UPDATE}.${FactorioEventUpdate.FactorioEventUpdateType.PLAYER}",
|
||||||
consumedAs("stream-player-updates-for-ws-server", jsonMapper.serde(), jsonMapper.serde())
|
consumedAs("EventUpdate.Player-updates-for-ws-server", jsonMapper.serde(), jsonMapper.serde()),
|
||||||
)
|
)
|
||||||
.foreach { _, value ->
|
.foreach { _, value ->
|
||||||
// println("sending ${value.packetType} packet to websocket")
|
// println("sending ${value.packetType} packet to websocket")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue