start simplifying schema packets

This commit is contained in:
Adam 2022-03-16 23:27:37 +01:00
parent 303734daaa
commit db14865ca9
8 changed files with 152 additions and 357 deletions

View file

@ -87,6 +87,13 @@ data class ColourHex(
} }
@Serializable
data class MapTiles(
val surfaceIndex: SurfaceIndex,
val tiles: List<MapTile>,
)
@Serializable @Serializable
data class MapTile( data class MapTile(
val x: Int, val x: Int,

View file

@ -1,29 +0,0 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
@Serializable
data class FactorioConfigurationUpdate(
override val modVersion: String,
override val tick: Tick,
val factorioData: FactorioGameDataUpdate,
val allMods: List<FactorioModInfo>,
) : KafkatorioPacket() {
@EncodeDefault
override val packetType = PacketType.CONFIG
}
@Serializable
data class FactorioGameDataUpdate(
val oldVersion: String? = null,
val newVersion: String? = null,
)
@Serializable
data class FactorioModInfo(
val modName: String,
val currentVersion: String? = null,
val previousVersion: String? = null,
)

View file

@ -1,7 +1,6 @@
package dev.adamko.kafkatorio.events.schema package dev.adamko.kafkatorio.events.schema
import dev.adamko.kafkatorio.events.schema.FactorioEventUpdate.FactorioEventUpdateType import dev.adamko.kafkatorio.events.schema.FactorioUpdateData.FactorioUpdateDataType
import kotlinx.serialization.Contextual
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -13,71 +12,77 @@ import kotlinx.serialization.json.jsonPrimitive
@Serializable @Serializable
data class FactorioEventUpdatePacket( sealed class KafkatorioPacket {
override val modVersion: String, /** Schema versioning */
override val tick: Tick, abstract val modVersion: String
val update: FactorioEventUpdate, abstract val tick: Tick
) : KafkatorioPacket() { abstract val update: FactorioUpdateData
@EncodeDefault
override val packetType: PacketType = PacketType.UPDATE
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
sealed interface FactorioEventUpdateKey { sealed interface FactorioUpdateDataKey {
val updateType: FactorioEventUpdateType val updateType: FactorioUpdateDataType
} }
@Serializable(with = FactorioEventUpdate.Companion.JsonSerializer::class) @Serializable(with = FactorioUpdateData.JsonSerializer::class)
sealed class FactorioEventUpdate : FactorioEventUpdateKey { sealed class FactorioUpdateData : FactorioUpdateDataKey {
/** Discriminator for [FactorioUpdateData] */
@EncodeDefault @EncodeDefault
abstract override val updateType: FactorioEventUpdateType abstract override val updateType: FactorioUpdateDataType
/** the initial Factorio event (`defines.events`) trigger */
abstract val eventCounts: Map<String, UInt>? abstract val eventCounts: Map<String, UInt>?
@Serializable @Serializable
enum class FactorioEventUpdateType { enum class FactorioUpdateDataType {
PLAYER, PLAYER,
MAP_CHUNK, MAP_CHUNK,
ENTITY, ENTITY,
CONSOLE_CHAT,
CONSOLE_COMMAND,
SURFACE,
CONFIG,
PROTOTYPES,
; ;
companion object { companion object {
val values: List<FactorioEventUpdateType> = values().toList() val values: List<FactorioUpdateDataType> = values().toList()
} }
} }
companion object { object JsonSerializer : JsonContentPolymorphicSerializer<FactorioUpdateData>(
FactorioUpdateData::class
) {
private val key = FactorioUpdateData::updateType.name
object JsonSerializer : JsonContentPolymorphicSerializer<FactorioEventUpdate>( override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioUpdateData> {
FactorioEventUpdate::class
) {
private val key = FactorioEventUpdate::updateType.name
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioEventUpdate> { val type = element
.jsonObject[key]
val type = element ?.jsonPrimitive
.jsonObject[key] ?.contentOrNull
?.jsonPrimitive ?.let { json ->
?.contentOrNull FactorioUpdateDataType.values.firstOrNull { it.name == json }
?.let { json ->
FactorioEventUpdateType.values.firstOrNull { it.name == json }
}
requireNotNull(type) { "Unknown FactorioEventUpdate $key: $element" }
return when (type) {
FactorioEventUpdateType.PLAYER -> PlayerUpdate.serializer()
FactorioEventUpdateType.MAP_CHUNK -> MapChunkUpdate.serializer()
FactorioEventUpdateType.ENTITY -> EntityUpdate.serializer()
} }
requireNotNull(type) { "Unknown FactorioEventUpdate $key: $element" }
return when (type) {
FactorioUpdateDataType.PLAYER -> PlayerUpdate.serializer()
FactorioUpdateDataType.MAP_CHUNK -> MapChunkUpdate.serializer()
FactorioUpdateDataType.ENTITY -> EntityUpdate.serializer()
FactorioUpdateDataType.CONSOLE_CHAT -> ConsoleChatUpdate.serializer()
FactorioUpdateDataType.CONSOLE_COMMAND -> ConsoleCommandUpdate.serializer()
FactorioUpdateDataType.SURFACE -> SurfaceUpdate.serializer()
FactorioUpdateDataType.CONFIG -> ConfigurationUpdate.serializer()
FactorioUpdateDataType.PROTOTYPES -> PrototypesUpdate.serializer()
} }
} }
} }
} }
@ -85,7 +90,7 @@ sealed class FactorioEventUpdate : FactorioEventUpdateKey {
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
sealed interface PlayerUpdateKey : FactorioEventUpdateKey { sealed interface PlayerUpdateKey : FactorioUpdateDataKey {
val index: PlayerIndex val index: PlayerIndex
} }
@ -120,16 +125,16 @@ data class PlayerUpdate(
val disconnectReason: String? = null, val disconnectReason: String? = null,
/** `true` when a player is removed (deleted) from the game */ /** `true` when a player is removed (deleted) from the game */
val isRemoved: Boolean? = null, val isRemoved: Boolean? = null,
) : FactorioEventUpdate(), PlayerUpdateKey { ) : FactorioUpdateData(), PlayerUpdateKey {
@EncodeDefault @EncodeDefault
override val updateType: FactorioEventUpdateType = FactorioEventUpdateType.PLAYER override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.PLAYER
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
sealed interface EntityUpdateKey : EntityIdentifiers, FactorioEventUpdateKey { sealed interface EntityUpdateKey : EntityIdentifiers, FactorioUpdateDataKey {
/** A [unitNumber] is required for caching */ /** A [unitNumber] is required for caching */
override val unitNumber: UnitNumber override val unitNumber: UnitNumber
override val name: String override val name: String
@ -154,16 +159,16 @@ data class EntityUpdate(
val localisedDescription: String? = null, val localisedDescription: String? = null,
val localisedName: String? = null, val localisedName: String? = null,
val prototype: PrototypeName? = null, val prototype: PrototypeName? = null,
) : FactorioEventUpdate(), EntityUpdateKey { ) : FactorioUpdateData(), EntityUpdateKey {
@EncodeDefault @EncodeDefault
override val updateType: FactorioEventUpdateType = FactorioEventUpdateType.ENTITY override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.ENTITY
} }
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
sealed interface MapChunkUpdateKey : FactorioEventUpdateKey { sealed interface MapChunkUpdateKey : FactorioUpdateDataKey {
val chunkPosition: MapChunkPosition val chunkPosition: MapChunkPosition
val surfaceIndex: SurfaceIndex val surfaceIndex: SurfaceIndex
} }
@ -182,9 +187,100 @@ data class MapChunkUpdate(
/** updated tiles - might be partial and not all tiles in the chunk */ /** updated tiles - might be partial and not all tiles in the chunk */
val tileDictionary: MapTileDictionary? = null, val tileDictionary: MapTileDictionary? = null,
val isDeleted: Boolean? = null, val isDeleted: Boolean? = null,
) : FactorioEventUpdate(), MapChunkUpdateKey { ) : FactorioUpdateData(), MapChunkUpdateKey {
@EncodeDefault @EncodeDefault
override val updateType: FactorioEventUpdateType = FactorioEventUpdateType.MAP_CHUNK override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.MAP_CHUNK
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Serializable
data class ConsoleChatUpdate(
override val eventCounts: Map<String, UInt>? = null,
val authorPlayerIndex: PlayerIndex?,
val content: String,
) : FactorioUpdateData() {
@EncodeDefault
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONSOLE_CHAT
}
@Serializable
data class ConsoleCommandUpdate(
override val eventCounts: Map<String, UInt>? = null,
val authorPlayerIndex: PlayerIndex?,
val command: String,
val parameters: String,
) : FactorioUpdateData() {
@EncodeDefault
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONSOLE_COMMAND
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
sealed interface SurfaceUpdateKey : FactorioUpdateDataKey {
val index: SurfaceIndex
}
@Serializable
data class SurfaceUpdate(
override val index: SurfaceIndex,
override val eventCounts: Map<String, UInt>? = null,
val daytime: Double,
val name: String,
) : FactorioUpdateData(), SurfaceUpdateKey {
@EncodeDefault
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.SURFACE
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Serializable
data class ConfigurationUpdate(
override val eventCounts: Map<String, UInt>? = null,
val factorioData: GameData,
val allMods: List<ModData>,
) : FactorioUpdateData() {
@EncodeDefault
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.CONFIG
@Serializable
data class GameData(
val oldVersion: String? = null,
val newVersion: String? = null,
)
@Serializable
data class ModData(
val modName: String,
val currentVersion: String? = null,
val previousVersion: String? = null,
)
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
@Serializable
data class PrototypesUpdate(
override val eventCounts: Map<String, UInt>? = null,
val prototypes: List<FactorioPrototype>,
) : FactorioUpdateData() {
@EncodeDefault
override val updateType: FactorioUpdateDataType = FactorioUpdateDataType.PROTOTYPES
} }

View file

@ -1,17 +0,0 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
@Serializable
data class FactorioEvent(
override val modVersion: String,
override val tick: Tick,
/** the initial Factorio event (`defines.events`) trigger */
val eventType: String,
val data: FactorioObjectData,
) : KafkatorioPacket() {
@EncodeDefault
override val packetType: PacketType = PacketType.EVENT
}

View file

@ -1,171 +0,0 @@
package dev.adamko.kafkatorio.events.schema
import kotlinx.serialization.Contextual
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
@Serializable(with = FactorioObjectDataJsonSerializer::class)
sealed class FactorioObjectData {
@EncodeDefault
abstract val objectName: ObjectName
// workaround - https://github.com/Kotlin/kotlinx.serialization/issues/1664
// val objectName: String by lazy {
// serializer().descriptor.serialName
// }
// val objectName: String by lazy {
// kotlinx.serialization.serializer(this::class.starProjectedType).descriptor.serialName
// }
// alternative: find the @SerialName annotation
// Again it must be delegated so there's no backing field and kxs ignores it
// val objectName: String by lazy {
// requireNotNull(this::class.findAnnotation<SerialName>()?.value) {
// "Couldn't find @SerialName for ${this::class}!"
// }
// }
enum class ObjectName {
// LuaPlayer,
// LuaEntity,
LuaSurface,
// MapChunk,
// LuaTile,
// LuaTiles,
ConsoleChatMessage,
;
companion object {
val values: List<ObjectName> = values().toList()
}
}
}
object FactorioObjectDataJsonSerializer : JsonContentPolymorphicSerializer<FactorioObjectData>(
FactorioObjectData::class
) {
private val key = FactorioObjectData::objectName.name
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out FactorioObjectData> {
val type = element
.jsonObject[key]
?.jsonPrimitive
?.contentOrNull
?.let { json ->
FactorioObjectData.ObjectName.values.firstOrNull { it.name == json }
}
requireNotNull(type) { "Unknown FactorioObjectData $key: $element" }
return when (type) {
// FactorioObjectData.ObjectName.LuaPlayer -> PlayerData.serializer()
// FactorioObjectData.ObjectName.LuaEntity -> EntityData.serializer()
FactorioObjectData.ObjectName.LuaSurface -> SurfaceData.serializer()
// FactorioObjectData.ObjectName.MapChunk -> MapChunk.serializer()
// FactorioObjectData.ObjectName.LuaTile -> MapTile.serializer()
// FactorioObjectData.ObjectName.LuaTiles -> MapTiles.serializer()
FactorioObjectData.ObjectName.ConsoleChatMessage -> ConsoleChatMessage.serializer()
}
}
}
///** [`LuaControl`](https://lua-api.factorio.com/latest/LuaControl.html) */
//interface FactorioLuaControl {
// val position: MapEntityPosition
// val surface: SurfaceIndex
// val force: ForceIndex
//}
//@Serializable
//data class PlayerData(
// override val force: ForceIndex,
// override val position: MapEntityPosition,
// override val surface: SurfaceIndex,
// @Contextual
// val associatedCharactersUnitNumbers: List<UnitNumber>,
// val characterUnitNumber: UnitNumber?,
// val chatColour: Colour,
// val colour: Colour,
// val lastOnline: Tick,
// val name: String,
//) : FactorioObjectData(), FactorioLuaControl {
// @EncodeDefault
// override val objectName = ObjectName.LuaPlayer
//}
//@Serializable
//data class EntityData(
// override val force: ForceIndex,
// override val position: MapEntityPosition,
// override val surface: SurfaceIndex,
// val active: Boolean,
// val health: Double?,
// val healthRatio: Double?,
// val name: String,
// val playerIndex: PlayerIndex? = null,
// val type: String,
// val unitNumber: UnitNumber? = null,
//) : FactorioObjectData(), FactorioLuaControl {
// @EncodeDefault
// override val objectName = ObjectName.LuaEntity
//}
@Serializable
data class SurfaceData(
val daytime: Double,
val index: SurfaceIndex,
val name: String,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.LuaSurface
}
///** This isn't a Factorio Lua type, but a helpful collection of Chunk info */
//@Serializable
//data class MapChunk(
// val tiles: MapTiles,
// val position: MapChunkPosition,
// val area: MapBoundingBox,
//) : FactorioObjectData() {
// @EncodeDefault
// override val objectName = ObjectName.MapChunk
//}
/** This isn't a Factorio Lua type, but a helpful collection of [MapTile]s */
@Serializable
data class MapTiles(
val surfaceIndex: SurfaceIndex,
val tiles: List<MapTile>,
)
// : FactorioObjectData()
//{
// @EncodeDefault
// override val objectName = ObjectName.LuaTiles
//}
@Serializable
data class ConsoleChatMessage(
val authorPlayerIndex: PlayerIndex?,
val content: String,
) : FactorioObjectData() {
@EncodeDefault
override val objectName = ObjectName.ConsoleChatMessage
}

View file

@ -12,16 +12,6 @@ import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
@Serializable
data class FactorioPrototypes(
override val modVersion: String,
override val tick: Tick,
val prototypes: List<FactorioPrototype>,
) : KafkatorioPacket() {
@EncodeDefault
override val packetType = PacketType.PROTOTYPES
}
@Serializable(with = FactorioPrototypeJsonSerializer::class) @Serializable(with = FactorioPrototypeJsonSerializer::class)
sealed class FactorioPrototype { sealed class FactorioPrototype {

View file

@ -10,6 +10,7 @@ import kotlinx.serialization.json.JsonTransformingSerializer
import kotlinx.serialization.json.buildJsonObject import kotlinx.serialization.json.buildJsonObject
import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModule
val jsonMapperKafkatorio = Json { val jsonMapperKafkatorio = Json {
prettyPrint = true prettyPrint = true
prettyPrintIndent = " " prettyPrintIndent = " "
@ -23,7 +24,7 @@ val jsonMapperKafkatorio = Json {
/** /**
* Factorio outputs an *empty* list as an *empty* Json object. This serde performs the same * Factorio outputs an *empty* list as an *empty* Json object. This serde performs the same
* conversion * conversion.
*/ */
class FactorioJsonListSerializer<T>(dataSerializer: KSerializer<T>) : class FactorioJsonListSerializer<T>(dataSerializer: KSerializer<T>) :
JsonTransformingSerializer<List<T>>(ListSerializer(dataSerializer)) { JsonTransformingSerializer<List<T>>(ListSerializer(dataSerializer)) {
@ -43,26 +44,3 @@ class FactorioJsonListSerializer<T>(dataSerializer: KSerializer<T>) :
} }
} }
//internal class FactorioListSerializer<T : Any>(
// private val tSerializer: KSerializer<T>
//) : KSerializer<List<T>> {
//
// override val descriptor: SerialDescriptor get() = tSerializer.descriptor
// private val tListSerializer = ListSerializer(tSerializer)
// private val jsonTransformer = FactorioJsonListSerializer(tSerializer)
//
// override fun serialize(encoder: Encoder, value: List<T>) {
// when (encoder) {
// is JsonEncoder -> jsonTransformer.serialize(encoder, value)
// else -> tListSerializer.serialize(encoder, value)
// }
// }
//
// override fun deserialize(decoder: Decoder): List<T> {
// return when (decoder) {
// is JsonDecoder -> jsonTransformer.deserialize(decoder)
// else -> tListSerializer.deserialize(decoder)
// }
// }
//}

View file

@ -1,59 +0,0 @@
package dev.adamko.kafkatorio.events.schema;
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.EncodeDefault
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonContentPolymorphicSerializer
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.contentOrNull
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
@Serializable(with = KafkatorioPacketJsonSerializer::class)
sealed class KafkatorioPacket {
/** Schema versioning */
abstract val modVersion: String
abstract val tick: Tick
@EncodeDefault
abstract val packetType: PacketType
enum class PacketType {
EVENT,
CONFIG,
PROTOTYPES,
UPDATE,
;
companion object {
val values: List<PacketType> = values().toList()
}
}
}
object KafkatorioPacketJsonSerializer : JsonContentPolymorphicSerializer<KafkatorioPacket>(
KafkatorioPacket::class
) {
private val key = KafkatorioPacket::packetType.name
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<out KafkatorioPacket> {
val type: KafkatorioPacket.PacketType? = element
.jsonObject[key]
?.jsonPrimitive
?.contentOrNull
?.let { json ->
KafkatorioPacket.PacketType.values.firstOrNull { it.name == json }
}
requireNotNull(type) { "Unknown KafkatorioPacket ${key}: $element" }
return when (type) {
KafkatorioPacket.PacketType.EVENT -> FactorioEvent.serializer()
KafkatorioPacket.PacketType.CONFIG -> FactorioConfigurationUpdate.serializer()
KafkatorioPacket.PacketType.PROTOTYPES -> FactorioPrototypes.serializer()
KafkatorioPacket.PacketType.UPDATE -> FactorioEventUpdatePacket.serializer()
}
}
}