improve event-schema polymorphism with enums

This commit is contained in:
Adam 2022-01-08 23:09:27 +01:00
parent 3875b9c309
commit 51e0aff823
18 changed files with 120 additions and 67 deletions

View file

@ -15,5 +15,6 @@ interface FactorioConfigurationUpdate extends KafkatorioPacket {
allMods: FactorioModInfo[];
factorioData: FactorioGameDataUpdate;
modVersion: string;
packetType: PacketType;
prototypes: FactorioPrototype[];
}

View file

@ -1,13 +1,16 @@
// Generated by TypeScriptGenerator - do not edit this file manually
type ObjectName = "LuaPlayer" | "LuaEntity" | "LuaSurface" | "FactorioMapChunk" | "LuaTile" | "ConsoleChatMessage";
interface FactorioObjectData {
objectName: string;
objectName: ObjectName;
}
interface FactorioEvent<T extends FactorioObjectData> extends KafkatorioPacket {
data: T;
eventType: string;
modVersion: string;
packetType: PacketType;
tick: uint;
}
@ -18,7 +21,7 @@ interface PlayerData extends FactorioObjectData {
colour: Colour;
lastOnline: uint;
name: string;
objectName: string;
objectName: ObjectName;
position: PositionData;
}
@ -27,7 +30,7 @@ interface EntityData extends FactorioObjectData {
health: double | null;
healthRatio: double;
name: string;
objectName: string;
objectName: ObjectName;
playerIndex: uint | null;
position: PositionData;
surfaceIndex: int;
@ -39,23 +42,23 @@ interface SurfaceData extends FactorioObjectData {
daytime: double;
index: uint;
name: string;
objectName: string;
objectName: ObjectName;
}
interface FactorioMapTile extends FactorioObjectData {
objectName: string;
objectName: ObjectName;
position: PositionData;
prototypeName: string;
surfaceIndex: int;
}
interface FactorioMapChunk extends FactorioObjectData {
objectName: string;
objectName: ObjectName;
tiles: FactorioMapTile[];
}
interface ConsoleChatMessage extends FactorioObjectData {
authorPlayerIndex: uint | null;
content: string;
objectName: string;
objectName: ObjectName;
}

View file

@ -1,12 +1,9 @@
// Generated by TypeScriptGenerator - do not edit this file manually
interface FactorioPrototype {
objectName: string;
}
type PrototypeObjectName = "LuaTilePrototype";
interface FactorioPrototypes<T extends FactorioPrototype> extends KafkatorioPacket {
modVersion: string;
prototypes: T[];
interface FactorioPrototype {
objectName: PrototypeObjectName;
}
interface FactorioMapTilePrototype extends FactorioPrototype {
@ -15,6 +12,6 @@ interface FactorioMapTilePrototype extends FactorioPrototype {
layer: uint;
mapColor: Colour;
name: string;
objectName: string;
objectName: PrototypeObjectName;
order: string;
}

View file

@ -1,7 +1,10 @@
// Generated by TypeScriptGenerator - do not edit this file manually
type PacketType = "EVENT" | "CONFIG";
interface KafkatorioPacket {
modVersion: string;
packetType: PacketType;
}
interface Colour {

View file

@ -7,6 +7,7 @@ export function handleConfigurationUpdate(changeData: ConfigurationChangedData)
emitPacket<FactorioConfigurationUpdate>({
modVersion: global.MOD_VERSION,
packetType: "CONFIG",
allMods: allMods(changeData),
factorioData: factorioData(changeData),
prototypes: convertPrototypes()

View file

@ -7,6 +7,7 @@ import {emitPacket} from "../emitKafkatorioPacket";
export function emitEvent<T extends FactorioObjectData>(eventData: T, tick: uint, eventType: string) {
emitPacket({
data: eventData,
packetType: "EVENT",
eventType: eventType,
modVersion: global.MOD_VERSION,
tick: tick

View file

@ -1,6 +1,7 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
@ -9,7 +10,10 @@ data class FactorioConfigurationUpdate(
val factorioData: FactorioGameDataUpdate,
val allMods: List<FactorioModInfo>,
val prototypes: List<FactorioPrototype>,
) : KafkatorioPacket
) : KafkatorioPacket() {
@Transient
override val packetType = PacketType.CONFIG
}
@Serializable
data class FactorioGameDataUpdate(

View file

@ -2,15 +2,18 @@ package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.Serializable
import kotlinx.serialization.Transient
@Serializable
data class FactorioEvent<out T : FactorioObjectData>(
override val modVersion: String,
/** the initial Factorio event (`defines.events`) trigger */
val eventType: String,
/** game time */
val tick: UInt,
val data: T,
) : KafkatorioPacket
) : KafkatorioPacket() {
@Transient
override val packetType: PacketType = PacketType.EVENT
}

View file

@ -9,7 +9,8 @@ import kotlinx.serialization.json.JsonClassDiscriminator
@JsonClassDiscriminator(FactorioObjectData.discriminatorKey)
sealed class FactorioObjectData {
abstract val objectName: String
/** @see FactorioObjectData.discriminatorKey */
abstract val objectName: ObjectName
// workaround - https://github.com/Kotlin/kotlinx.serialization/issues/1664
// val objectName: String by lazy {
@ -28,12 +29,22 @@ sealed class FactorioObjectData {
// }
// }
enum class ObjectName {
LuaPlayer,
LuaEntity,
LuaSurface,
FactorioMapChunk,
LuaTile,
ConsoleChatMessage,
}
companion object {
/** The [JsonClassDiscriminator] for [FactorioEvent] */
const val discriminatorKey: String = "objectName"
}
}
@Serializable
@SerialName("LuaPlayer")
data class PlayerData(
@ -47,7 +58,7 @@ data class PlayerData(
val lastOnline: UInt,
) : FactorioObjectData() {
@Transient
override val objectName: String = "LuaPlayer"
override val objectName = ObjectName.LuaPlayer
}
@Serializable
@ -64,7 +75,7 @@ data class EntityData(
val playerIndex: UInt? = null,
) : FactorioObjectData() {
@Transient
override val objectName: String = "LuaEntity"
override val objectName = ObjectName.LuaEntity
}
@Serializable
@ -75,16 +86,17 @@ data class SurfaceData(
val name: String,
) : FactorioObjectData() {
@Transient
override val objectName: String = "LuaSurface"
override val objectName = ObjectName.LuaSurface
}
/** Quasi-object. This isn't a Factorio Lua type, but a helpful collection of [FactorioMapTile]s */
@Serializable
@SerialName("FactorioMapChunk")
data class FactorioMapChunk(
val tiles: List<FactorioMapTile>,
) : FactorioObjectData() {
@Transient
override val objectName: String = "FactorioMapChunk"
override val objectName = ObjectName.FactorioMapChunk
}
@Serializable
@ -95,7 +107,7 @@ data class FactorioMapTile(
val surfaceIndex: Int,
) : FactorioObjectData() {
@Transient
override val objectName: String = "LuaTile"
override val objectName = ObjectName.LuaTile
}
@Serializable
@ -104,5 +116,5 @@ data class ConsoleChatMessage(
val content: String,
) : FactorioObjectData() {
@Transient
override val objectName: String = "ConsoleChatMessage"
override val objectName = ObjectName.ConsoleChatMessage
}

View file

@ -7,14 +7,17 @@ import kotlinx.serialization.json.JsonClassDiscriminator
@Serializable
class FactorioPrototypes<T : FactorioPrototype>(
override val modVersion: String,
val prototypes: List<T>,
) : KafkatorioPacket
)
@Serializable
@JsonClassDiscriminator(FactorioPrototype.discriminatorKey)
sealed class FactorioPrototype {
abstract val objectName: String
abstract val objectName: PrototypeObjectName
enum class PrototypeObjectName {
LuaTilePrototype
}
companion object {
const val discriminatorKey: String = "objectName"
@ -34,5 +37,5 @@ data class FactorioMapTilePrototype(
val canBeMined: Boolean,
) : FactorioPrototype() {
@Transient
override val objectName: String = "LuaTilePrototype"
override val objectName: PrototypeObjectName = PrototypeObjectName.LuaTilePrototype
}

View file

@ -1,19 +1,22 @@
package dev.adamko.kafkatorio.events.schema;
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonClassDiscriminator
//@Serializable
sealed interface KafkatorioPacket {
@Serializable
@JsonClassDiscriminator("packetType")
sealed class KafkatorioPacket {
/** Schema versioning */
val modVersion: String
abstract val modVersion: String
abstract val packetType: PacketType
enum class PacketType {
EVENT, CONFIG,
}
}
//enum class PacketDataType {
// EVENT, PROTOTYPE, CONFIG_DATA,
// ;
//}
//@Serializable
//@JsonClassDiscriminator(KafkatorioPacketData.discriminatorKey)
//sealed class KafkatorioPacketData(

View file

@ -48,7 +48,7 @@ fun main(args: Array<String>) {
.forEach { (defFile, def) ->
val outFile = outPath.resolve(defFile).toFile()
outFile.printWriter().use {
it.print(header)
it.println(header)
it.print(def)
}
println("Generated file: $outFile")
@ -65,14 +65,16 @@ private fun splitDefinitions(definitions: String): Map<String, String> {
.groupBy { def ->
when {
"extends FactorioObjectData" in def ||
"interface FactorioObjectData" in def -> "object-data.d.ts"
"interface FactorioObjectData" in def ||
"type ObjectName" in def -> "object-data.d.ts"
"extends FactorioPrototype" in def ||
"interface FactorioPrototype" in def -> "prototype.d.ts"
"interface FactorioPrototype" in def ||
"type PrototypeObjectName" in def -> "prototype.d.ts"
"extends FactorioConfigurationUpdate" in def ||
"interface FactorioConfigurationUpdate" in def ||
"FactorioGameDataUpdate" in def ||
"FactorioModInfo" in def -> "config-update.d.ts"
else -> "schema.d.ts"
"FactorioModInfo" in def -> "config-update.d.ts"
else -> "schema.d.ts"
}
}
.mapValues { (_, def) ->
@ -86,7 +88,7 @@ private fun splitDefinitions(definitions: String): Map<String, String> {
val header = """
|// Generated by TypeScriptGenerator - do not edit this file manually
|
""".trimMargin() + "\n"
""".trimMargin()
//object PropertyNameTransformer : ClassTransformer {
// override fun transformPropertyName(

View file

@ -1,7 +1,8 @@
package dev.adamko.kafkatorio.processor
import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import java.time.Duration
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
@ -27,7 +28,7 @@ class FactorioEventsTopology(
)
.mapValues { readOnlyKey, value ->
println("Mapping $readOnlyKey:$value")
jsonMapper.decodeFromString<FactorioEvent<FactorioObjectData>>(value)
jsonMapper.decodeFromString<KafkatorioPacket>(value)
}
.peek { _, value ->
websocketServer.sendMessage(jsonMapper.encodeToString(value))
@ -35,9 +36,14 @@ class FactorioEventsTopology(
.to(
{ _, value, _ ->
// println("[$key] sending event:${value.eventType} to topic:${value.data.objectName()}")
value.data.objectName
when (value) {
is FactorioEvent<*> ->
"kafkatorio.${value.packetType.name}.${value.data.objectName.name}"
is FactorioConfigurationUpdate ->
"kafkatorio.${value.packetType.name}"
}
},
Produced.with(Serdes.String(), JsonSerdes)
Produced.with(Serdes.String(), KafkatorioPacketSerde)
)
val topology = builder.build()

View file

@ -16,4 +16,5 @@ fun main() = runBlocking {
topology.build()
wsServer.build().asServer(Undertow(9073)).start().block()
}

View file

@ -1,7 +1,6 @@
package dev.adamko.kafkatorio.processor
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
@ -14,12 +13,12 @@ import org.http4k.format.asConfigurable
import org.http4k.format.withStandardMappings
object JsonSerdes : Serde<FactorioEvent<FactorioObjectData>> {
override fun serializer() = Serializer<FactorioEvent<FactorioObjectData>> { _, message ->
jsonMapper.encodeToString<FactorioEvent<FactorioObjectData>>(message).encodeToByteArray()
object KafkatorioPacketSerde : Serde<KafkatorioPacket> {
override fun serializer() = Serializer<KafkatorioPacket> { _, message ->
jsonMapper.encodeToString<KafkatorioPacket>(message).encodeToByteArray()
}
override fun deserializer() = Deserializer<FactorioEvent<FactorioObjectData>> { _, bytes ->
override fun deserializer() = Deserializer<KafkatorioPacket> { _, bytes ->
jsonMapper.decodeFromString(bytes.decodeToString())
}
}

View file

@ -56,7 +56,6 @@ fun main() {
"ws://localhost:9073/ws",
reduxStore,
)
App(
reduxStore = reduxStore,
wsService = wsService

View file

@ -2,9 +2,8 @@ package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.PlayerData
import externals.leaflet.geo.LatLng
import externals.leaflet.layer.overlay.Tooltip
import externals.leaflet.layer.vector.CircleMarker
import io.kvision.maps.externals.leaflet.layer.overlay.Tooltip
import io.kvision.maps.externals.leaflet.layer.vector.CircleMarker
import io.kvision.maps.Maps
import io.kvision.maps.Maps.Companion.L
import io.kvision.redux.ReduxStore
@ -12,6 +11,7 @@ import io.kvision.redux.createReduxStore
import io.kvision.utils.obj
import redux.RAction
fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
createReduxStore(
::factorioGameReducer,
@ -21,6 +21,7 @@ fun createFactorioReduxStore(): ReduxStore<FactorioGameState, FactorioUpdate> =
)
)
data class FactorioGameState(
val kvMaps: Maps,
val players: Map<String, PlayerState>,
@ -32,6 +33,7 @@ sealed class FactorioUpdate : RAction {
FactorioUpdate()
}
fun factorioGameReducer(state: FactorioGameState, update: FactorioUpdate): FactorioGameState =
when (update) {
is FactorioUpdate.PlayerUpdate -> {
@ -77,7 +79,7 @@ data class PlayerState(
fillColor = color
fillOpacity = 1f
radius = 10
className = playerData.objectName
className = playerData.objectName.name
// interactive = false
}

View file

@ -1,13 +1,20 @@
package dev.adamko.kafkatorio.webmap
import dev.adamko.kafkatorio.events.schema.ConsoleChatMessage
import dev.adamko.kafkatorio.events.schema.EntityData
import dev.adamko.kafkatorio.events.schema.FactorioConfigurationUpdate
import dev.adamko.kafkatorio.events.schema.FactorioEvent
import dev.adamko.kafkatorio.events.schema.FactorioObjectData
import dev.adamko.kafkatorio.events.schema.FactorioMapChunk
import dev.adamko.kafkatorio.events.schema.FactorioMapTile
import dev.adamko.kafkatorio.events.schema.KafkatorioPacket
import dev.adamko.kafkatorio.events.schema.PlayerData
import dev.adamko.kafkatorio.events.schema.SurfaceData
import io.kvision.redux.ReduxStore
import kotlinx.serialization.decodeFromString
import org.w3c.dom.MessageEvent
import org.w3c.dom.WebSocket
class WebsocketService(
private val wsUrl: String,
private val reduxStore: ReduxStore<FactorioGameState, FactorioUpdate>,
@ -24,22 +31,28 @@ class WebsocketService(
if (
data != null
&& data.trim().startsWith("{")
&& data.trim().endsWith("}")
&& data.trim().run { startsWith("{") && endsWith("}") }
) {
println(data.replace('\n', ' '))
val event: FactorioEvent<FactorioObjectData> = jsonMapper.decodeFromString(data)
when (val eventData = event.data) {
is PlayerData -> {
reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData))
when (val event: KafkatorioPacket = jsonMapper.decodeFromString(data)) {
is FactorioEvent<*> -> {
when (val eventData = event.data) {
is PlayerData ->
reduxStore.dispatch(FactorioUpdate.PlayerUpdate(event, eventData))
is ConsoleChatMessage -> {}
is EntityData -> {}
is FactorioMapChunk -> {}
is FactorioMapTile -> {}
is SurfaceData -> {}
}
}
else -> {}
is FactorioConfigurationUpdate -> {}
}
} else {
println(data)
}
}