diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/AssetListSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/AssetListSerde.kt index 84f8f22..dee488e 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/AssetListSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/AssetListSerde.kt @@ -14,10 +14,10 @@ internal class AssetListSerde( private val postProcessor: PostProcessor> ) : Serde> { - override fun collectDataSections(data: List): DataSection { + override fun calculateDataSection(data: List): DataSection { return DataSectionHolder( containingData = data.map { - entrySerde.collectDataSections(it) + entrySerde.calculateDataSection(it) } ) } @@ -26,7 +26,7 @@ internal class AssetListSerde( preProcessor.preProcess(data, serializationContext).let { list -> list.forEach { entry -> - // TODO: serialize asset header + MapFileWriter.writeAsset(outputStream, serializationContext, entry) entrySerde.serialize(outputStream, entry) } } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BlendCountSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BlendCountSerde.kt index e75ef95..32f0739 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BlendCountSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BlendCountSerde.kt @@ -16,8 +16,8 @@ internal class BlendCountSerde( private val uIntSerde = UIntSerde(context, NoopPreProcessor(), NoopPostProcessor()) - override fun collectDataSections(data: UInt): DataSection { - return uIntSerde.collectDataSections(data) + override fun calculateDataSection(data: UInt): DataSection { + return uIntSerde.calculateDataSection(data) } override fun serialize(outputStream: OutputStream, data: UInt) { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BooleanSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BooleanSerde.kt index 4913b36..ec95ee6 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BooleanSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/BooleanSerde.kt @@ -21,5 +21,5 @@ internal class BooleanSerde( postProcessor ) { - override fun collectDataSections(data: Boolean): DataSection = DataSectionLeaf.BOOLEAN + override fun calculateDataSection(data: Boolean): DataSection = DataSectionLeaf.BOOLEAN } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ByteSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ByteSerde.kt index 433b4e3..573c817 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ByteSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ByteSerde.kt @@ -21,5 +21,5 @@ internal class ByteSerde( postProcessor ) { - override fun collectDataSections(data: Byte): DataSection = DataSectionLeaf.BYTE + override fun calculateDataSection(data: Byte): DataSection = DataSectionLeaf.BYTE } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ColorSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ColorSerde.kt index ce878bd..b3e206b 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ColorSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ColorSerde.kt @@ -17,7 +17,7 @@ internal class ColorSerde( private val uIntSerde = UIntSerde(context, NoopPreProcessor(), NoopPostProcessor()) - override fun collectDataSections(data: Color): DataSection = uIntSerde.collectDataSections(data.rgba) + override fun calculateDataSection(data: Color): DataSection = uIntSerde.calculateDataSection(data.rgba) override fun serialize(outputStream: OutputStream, data: Color) { preProcessor.preProcess(data, context).let { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ConditionalSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ConditionalSerde.kt index dd3e4e9..3ec12c7 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ConditionalSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ConditionalSerde.kt @@ -38,12 +38,12 @@ internal class ConditionalSerde( Pair(assetName.name, serdeFactory.getSerde(assetType)) } - override fun collectDataSections(data: Any): DataSection { + override fun calculateDataSection(data: Any): DataSection { val asset = data::class.findAnnotation() ?: throw IllegalStateException("Could not find asset annotation for $currentElementName. Expected one of: ${serdes.keys}") val serde = serdes[asset.name] ?: throw IllegalStateException("Could not find serde for '${asset.name}' calculating byte count for $currentElementName. Expected one of: ${serdes.keys}") - return serde.collectDataSections(data) + return serde.calculateDataSection(data) } override fun serialize(outputStream: OutputStream, data: Any) { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/EnumSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/EnumSerde.kt index 3e3288b..fbbb354 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/EnumSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/EnumSerde.kt @@ -34,8 +34,8 @@ internal class EnumSerde>( } } - override fun collectDataSections(data: T): DataSection { - return serde.collectDataSections(enumValueGetter.call(data)!!) + override fun calculateDataSection(data: T): DataSection { + return serde.calculateDataSection(enumValueGetter.call(data)!!) } override fun serialize(outputStream: OutputStream, data: T) { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FloatSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FloatSerde.kt index a89a4e9..e1d87f2 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FloatSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FloatSerde.kt @@ -21,5 +21,5 @@ internal class FloatSerde( postProcessor ) { - override fun collectDataSections(data: Float): DataSection = DataSectionLeaf.FLOAT + override fun calculateDataSection(data: Float): DataSection = DataSectionLeaf.FLOAT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteColorSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteColorSerde.kt index 27beb01..8b1d96d 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteColorSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteColorSerde.kt @@ -17,7 +17,7 @@ internal class FourByteColorSerde( private val postProcessor: PostProcessor ) : Serde { - override fun collectDataSections(data: Color): DataSection = DataSectionLeaf(4) + override fun calculateDataSection(data: Color): DataSection = DataSectionLeaf(4) override fun serialize(outputStream: OutputStream, data: Color) { preProcessor.preProcess(data, context).let { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteStringSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteStringSerde.kt index cb32125..4b4820e 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteStringSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/FourByteStringSerde.kt @@ -15,7 +15,7 @@ internal class FourByteStringSerde( private val postProcessor: PostProcessor ) : Serde { - override fun collectDataSections(data: String): DataSection = DataSectionLeaf(4) + override fun calculateDataSection(data: String): DataSection = DataSectionLeaf(4) override fun serialize(outputStream: OutputStream, data: String) { preProcessor.preProcess(data, context).let { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/HeightMapDependentMapSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/HeightMapDependentMapSerde.kt index 2691a30..f5f8263 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/HeightMapDependentMapSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/HeightMapDependentMapSerde.kt @@ -39,7 +39,7 @@ internal class HeightMapDependentMapSerde( SAGE_BOOLEAN } - override fun collectDataSections(data: Table): DataSection { + override fun calculateDataSection(data: Table): DataSection { val width = data.rowKeySet().size.toUInt() val height = data.columnKeySet().size.toUInt() @@ -50,7 +50,7 @@ internal class HeightMapDependentMapSerde( containingData = buildList { (0u until height step 1).forEach { y -> when { - mode == Mode.DEFAULT -> add(valueSerde.collectDataSections(data[x, y]!!)) + mode == Mode.DEFAULT -> add(valueSerde.calculateDataSection(data[x, y]!!)) x % 8u == 0u -> add(DataSectionLeaf(1)) } } @@ -73,8 +73,8 @@ internal class HeightMapDependentMapSerde( when (mode) { Mode.DEFAULT -> valueSerde.serialize(outputStream, map[x, y]!!) else -> { - // FIXME: this if statement does look weird - if (x > 0u && x % 8u == 0u) { + // TODO: check if the if statement logic is correct + if (x % 8u == 0u) { outputStream.writeByte(temp) } temp = temp or ((if (map[x, y] as Boolean) 1 else 0) shl (x % 8u).toInt()).toByte() diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/IntSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/IntSerde.kt index 6d70484..3feeef3 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/IntSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/IntSerde.kt @@ -21,5 +21,5 @@ internal class IntSerde( postProcessor ) { - override fun collectDataSections(data: Int): DataSection = DataSectionLeaf.INT + override fun calculateDataSection(data: Int): DataSection = DataSectionLeaf.INT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ListSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ListSerde.kt index 622a47f..8521114 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ListSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ListSerde.kt @@ -16,6 +16,7 @@ import java.io.OutputStream @UseSerdeProperties(ListSerde.Properties::class) internal class ListSerde( + annotationProcessingContext: AnnotationProcessingContext, private val context: SerializationContext, private val entrySerde: Serde, private val preProcessor: PreProcessor>, @@ -27,6 +28,8 @@ internal class ListSerde( private val sharedDataKey: String ) : Serde> { + private val currentElementName = annotationProcessingContext.getCurrentElement().getName() + init { if (mode == Mode.FIXED && size == 0u) { error("${ListSerde::class.simpleName} requires 'size' to be set via ${Properties::class.qualifiedName} when 'mode' is '${Mode.FIXED}'.") @@ -60,7 +63,7 @@ internal class ListSerde( BYTE } - override fun collectDataSections(data: List): DataSection { + override fun calculateDataSection(data: List): DataSection { return DataSectionHolder( containingData = buildList { if (mode == Mode.DEFAULT) { @@ -72,7 +75,7 @@ internal class ListSerde( } ) } - addAll(data.map { entrySerde.collectDataSections(it) }) + addAll(data.map { entrySerde.calculateDataSection(it) }) } ) } @@ -82,20 +85,12 @@ internal class ListSerde( preProcessor.preProcess(data, context).let { list -> val numberOfListEntries = list.size.toUInt() - when (mode) { - Mode.DEFAULT -> when (sizeType) { + if (mode == Mode.DEFAULT) { + when (sizeType) { SizeType.UINT -> outputStream.writeUInt(numberOfListEntries) SizeType.USHORT -> outputStream.writeUShort(numberOfListEntries.toUShort()) SizeType.BYTE -> outputStream.writeByte(numberOfListEntries.toByte()) } - - Mode.FIXED -> if (numberOfListEntries != size) { - throw IllegalStateException("Expected '' to have size '$size'.") - } - - Mode.SHARED_DATA -> if (numberOfListEntries != size) { - throw IllegalStateException("Expected '' to have size '${context.sharedData[sharedDataKey]}'.") - } } list.forEach { entry -> diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerde.kt index fb12666..23d2389 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerde.kt @@ -3,9 +3,7 @@ package de.darkatra.bfme2.map.serialization import com.google.common.io.CountingInputStream import de.darkatra.bfme2.map.Asset import de.darkatra.bfme2.map.MapFile -import de.darkatra.bfme2.map.serialization.model.DataSection -import de.darkatra.bfme2.map.serialization.model.MapFileDataSectionHolder -import de.darkatra.bfme2.map.serialization.postprocessing.PostProcessor +import de.darkatra.bfme2.map.serialization.model.DataSectionHolder import de.darkatra.bfme2.map.toKClass import java.io.OutputStream import kotlin.reflect.KProperty @@ -18,37 +16,54 @@ import kotlin.time.measureTime internal class MapFileSerde( private val serializationContext: SerializationContext, - private val serdes: List>, - private val postProcessor: PostProcessor + private val serdes: List> ) : Serde { private val primaryConstructor = MapFile::class.primaryConstructor ?: error("${MapFile::class.simpleName} is required to have a primary constructor.") private val parameters = primaryConstructor.valueParameters + private val parameterToField = parameters.associateWith { parameter -> + val fieldForParameter = MapFile::class.members + .filterIsInstance>() + .find { field -> field.name == parameter.name }!! + if (fieldForParameter.getter.visibility != KVisibility.PUBLIC && fieldForParameter.getter.visibility != KVisibility.INTERNAL) { + throw IllegalStateException("Field for parameter '${parameter.name}' is not public or internal.") + } + fieldForParameter + } - override fun collectDataSections(data: MapFile): DataSection { - - return MapFileDataSectionHolder( - containingData = parameters.mapIndexed { index, parameter -> - val fieldForParameter = MapFile::class.members - .filterIsInstance>() - .first { field -> field.name == parameter.name } + override fun calculateDataSection(data: MapFile): DataSectionHolder { - if (fieldForParameter.getter.visibility == KVisibility.PUBLIC || fieldForParameter.getter.visibility == KVisibility.INTERNAL) { - @Suppress("UNCHECKED_CAST") - val serde = serdes[index] as Serde - val fieldData = fieldForParameter.getter.call(data)!! - serde.collectDataSections(fieldData) - } else { - throw IllegalStateException("Could not collect data sections for parameter '${parameter.name}' because it's getter is not public or internal.") - } - } + return DataSectionHolder( + containingData = parameterToField.entries.mapIndexed { index, (p, fieldForParameter) -> + @Suppress("UNCHECKED_CAST") + val serde = serdes[index] as Serde + val fieldData = fieldForParameter.getter.call(data)!! + serde.calculateDataSection(fieldData) + }, + assetName = "MapFile" ) } + @OptIn(ExperimentalTime::class) override fun serialize(outputStream: OutputStream, data: MapFile) { - TODO("Not yet implemented") + + // TODO: check if we need to preserve the order in which we write the data + parameterToField.entries.forEachIndexed { index, (parameter, fieldForParameter) -> + @Suppress("UNCHECKED_CAST") + val serde = serdes[index] as Serde + val fieldData = fieldForParameter.getter.call(data)!! + + measureTime { + MapFileWriter.writeAsset(outputStream, serializationContext, fieldData) + serde.serialize(outputStream, fieldData) + }.also { elapsedTime -> + if (serializationContext.debugMode) { + println("Deserialization of '${parameter.name}' took $elapsedTime.") + } + } + } } @OptIn(ExperimentalTime::class) @@ -80,8 +95,6 @@ internal class MapFileSerde( } } - return primaryConstructor.call(*values).also { - postProcessor.postProcess(it, serializationContext) - } + return primaryConstructor.call(*values) } } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriter.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriter.kt index 35eb813..6ac1b8c 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriter.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriter.kt @@ -1,10 +1,12 @@ package de.darkatra.bfme2.map.serialization import com.google.common.io.CountingOutputStream +import de.darkatra.bfme2.map.Asset import de.darkatra.bfme2.map.MapFile import de.darkatra.bfme2.map.MapFileCompression import de.darkatra.bfme2.write7BitIntPrefixedString import de.darkatra.bfme2.writeUInt +import de.darkatra.bfme2.writeUShort import java.io.BufferedOutputStream import java.io.OutputStream import java.io.UnsupportedEncodingException @@ -15,11 +17,28 @@ import java.util.zip.DeflaterOutputStream import kotlin.io.path.absolutePathString import kotlin.io.path.exists import kotlin.io.path.outputStream +import kotlin.reflect.full.findAnnotation import kotlin.time.ExperimentalTime import kotlin.time.measureTime class MapFileWriter { + companion object { + + internal fun writeAsset(outputStream: OutputStream, serializationContext: SerializationContext, data: Any) { + + val asset = data::class.findAnnotation() + ?: throw IllegalStateException("'${data::class.qualifiedName}' must be annotated with '${Asset::class.simpleName}'.") + + val assetIndex = serializationContext.getAssetIndex(asset.name) + val assetVersion = asset.version + + outputStream.writeUInt(assetIndex) + outputStream.writeUShort(assetVersion) + outputStream.writeUInt(serializationContext.getAssetDataSection(asset.name).size.toUInt()) + } + } + @Suppress("unused") // public api fun write(file: Path, mapFile: MapFile) { @@ -49,14 +68,22 @@ class MapFileWriter { val annotationProcessingContext = AnnotationProcessingContext(false) val serdeFactory = SerdeFactory(annotationProcessingContext, serializationContext) - val mapFileSerde = serdeFactory.getSerde(MapFile::class) + val mapFileSerde: MapFileSerde = serdeFactory.getSerde(MapFile::class) as MapFileSerde annotationProcessingContext.invalidate() - // FIXME: correctly calculate asset names from data sections - val dataSections = mapFileSerde.collectDataSections(mapFile) + val assetDataSections = mapFileSerde.calculateDataSection(mapFile).flatten() + .filter { it.isAsset } + .distinctBy { it.assetName } + serializationContext.setAssetDataSections(assetDataSections.associateBy { it.assetName!! }) + + val assetNames = assetDataSections + .reversed() + .mapIndexed { index, dataSectionHolder -> Pair(index.toUInt() + 1u, dataSectionHolder.assetName!!) } + .toMap() + serializationContext.setAssetNames(assetNames) measureTime { - writeAssetNames(mapOf(), countingOutputStream) + writeAssetNames(assetNames, countingOutputStream) }.also { elapsedTime -> if (serializationContext.debugMode) { println("Writing asset names took $elapsedTime.") @@ -65,8 +92,6 @@ class MapFileWriter { mapFileSerde.serialize(bufferedOutputStream, mapFile) bufferedOutputStream.flush() - - serializationContext.pop() } } } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapSerde.kt index 489402b..e7dcd4e 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/MapSerde.kt @@ -48,20 +48,20 @@ internal class MapSerde( VALUE_FIRST } - override fun collectDataSections(data: Map): DataSection { + override fun calculateDataSection(data: Map): DataSection { return DataSectionHolder( containingData = buildList { add(DataSectionLeaf.INT) data.entries.map { (key, value) -> when (order) { DeserializationOrder.KEY_FIRST -> { - add(keySerde.collectDataSections(key)) - add(valueSerde.collectDataSections(value)) + add(keySerde.calculateDataSection(key)) + add(valueSerde.calculateDataSection(value)) } DeserializationOrder.VALUE_FIRST -> { - add(valueSerde.collectDataSections(value)) - add(keySerde.collectDataSections(key)) + add(valueSerde.calculateDataSection(value)) + add(keySerde.calculateDataSection(key)) } } } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ObjectSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ObjectSerde.kt index 7c27885..c386292 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ObjectSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ObjectSerde.kt @@ -25,27 +25,28 @@ internal class ObjectSerde( private val primaryConstructor = classOfT.primaryConstructor ?: error("${classOfT.simpleName} is required to have a primary constructor.") - private val parameters = primaryConstructor.valueParameters private val assetAnnotation = classOfT.findAnnotation() + private val parameters = primaryConstructor.valueParameters + private val parameterToField = parameters.associateWith { parameter -> + val fieldForParameter = classOfT.members + .filterIsInstance>() + .find { field -> field.name == parameter.name }!! + if (fieldForParameter.getter.visibility != KVisibility.PUBLIC && fieldForParameter.getter.visibility != KVisibility.INTERNAL) { + throw IllegalStateException("Field for parameter '${parameter.name}' is not public or internal.") + } + fieldForParameter + } private val currentElementName = annotationProcessingContext.getCurrentElement().getName() - override fun collectDataSections(data: T): DataSection { - return DataSectionHolder( - containingData = parameters.mapIndexed { index, parameter -> - - val fieldForParameter = classOfT.members - .filterIsInstance>() - .first { field -> field.name == parameter.name } + override fun calculateDataSection(data: T): DataSection { - if (fieldForParameter.getter.visibility == KVisibility.PUBLIC || fieldForParameter.getter.visibility == KVisibility.INTERNAL) { - @Suppress("UNCHECKED_CAST") - val serde = serdes[index] as Serde - val fieldData = fieldForParameter.getter.call(data)!! - serde.collectDataSections(fieldData) - } else { - throw IllegalStateException("Could not collect data for parameter '${parameter.name}' because it's getter is not public or internal.") - } + return DataSectionHolder( + containingData = parameterToField.entries.mapIndexed { index, (_, fieldForParameter) -> + @Suppress("UNCHECKED_CAST") + val serde = serdes[index] as Serde + val fieldData = fieldForParameter.getter.call(data)!! + serde.calculateDataSection(fieldData) }, assetName = assetAnnotation?.name, assetVersion = assetAnnotation?.version @@ -53,7 +54,12 @@ internal class ObjectSerde( } override fun serialize(outputStream: OutputStream, data: T) { - TODO("Not yet implemented") + parameterToField.entries.forEachIndexed { index, (_, fieldForParameter) -> + @Suppress("UNCHECKED_CAST") + val serde = serdes[index] as Serde + val fieldData = fieldForParameter.getter.call(data)!! + serde.serialize(outputStream, fieldData) + } } override fun deserialize(inputStream: CountingInputStream): T { diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertyKeySerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertyKeySerde.kt index a4f4344..f3b6860 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertyKeySerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertyKeySerde.kt @@ -8,6 +8,7 @@ import de.darkatra.bfme2.map.serialization.model.DataSectionHolder import de.darkatra.bfme2.map.serialization.model.DataSectionLeaf import de.darkatra.bfme2.map.serialization.postprocessing.PostProcessor import de.darkatra.bfme2.map.serialization.preprocessing.PreProcessor +import de.darkatra.bfme2.toLittleEndianBytes import de.darkatra.bfme2.toLittleEndianUInt import java.io.OutputStream @@ -20,12 +21,13 @@ internal class PropertyKeySerde( private val propertyTypeSerde: Serde = serdeFactory.getSerde(PropertyType::class) - override fun collectDataSections(data: PropertyKey): DataSection { + override fun calculateDataSection(data: PropertyKey): DataSection { return DataSectionHolder( containingData = listOf( DataSectionLeaf(3), - propertyTypeSerde.collectDataSections(data.propertyType) - ) + propertyTypeSerde.calculateDataSection(data.propertyType) + ), + assetName = data.name ) } @@ -35,7 +37,8 @@ internal class PropertyKeySerde( propertyTypeSerde.serialize(outputStream, propertyKey.propertyType) - // TODO: serialize assetNameIndex + val assetIndex = serializationContext.getAssetIndex(data.name) + outputStream.write(assetIndex.toLittleEndianBytes().take(3).toByteArray()) } } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertySerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertySerde.kt index 1a63cfc..dc4f0b2 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertySerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/PropertySerde.kt @@ -30,10 +30,10 @@ internal class PropertySerde( private val propertyKeySerde = PropertyKeySerde(serdeFactory, serializationContext, NoopPreProcessor(), NoopPostProcessor()) - override fun collectDataSections(data: Property): DataSection { + override fun calculateDataSection(data: Property): DataSection { return DataSectionHolder( containingData = listOf( - propertyKeySerde.collectDataSections(data.key), + propertyKeySerde.calculateDataSection(data.key), when (data.key.propertyType) { PropertyType.BOOLEAN -> DataSectionLeaf.BOOLEAN PropertyType.INTEGER -> DataSectionLeaf.INT diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptArgumentSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptArgumentSerde.kt index 8f8996d..9e570d5 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptArgumentSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptArgumentSerde.kt @@ -26,10 +26,10 @@ internal class ScriptArgumentSerde( private val scriptArgumentTypeSerde: Serde = serdeFactory.getSerde(ScriptArgumentType::class) - override fun collectDataSections(data: ScriptArgument): DataSection { + override fun calculateDataSection(data: ScriptArgument): DataSection { return DataSectionHolder( containingData = listOf( - scriptArgumentTypeSerde.collectDataSections(data.argumentType), + scriptArgumentTypeSerde.calculateDataSection(data.argumentType), when (data.argumentType) { ScriptArgumentType.POSITION_COORDINATE -> DataSectionLeaf(4 * 3) else -> DataSectionLeaf(4L + 4L + 2L + data.stringValue!!.length) diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptListEntrySerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptListEntrySerde.kt index 48770d3..2ef8c86 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptListEntrySerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ScriptListEntrySerde.kt @@ -34,15 +34,15 @@ internal class ScriptListEntrySerde( private val assetListSerde = AssetListSerde(serializationContext, this, NoopPreProcessor(), NoopPostProcessor()) private val scriptSerde = serdeFactory.getSerde(Script::class) - override fun collectDataSections(data: ScriptListEntry): DataSection { + override fun calculateDataSection(data: ScriptListEntry): DataSection { return when (data) { - is Script -> scriptSerde.collectDataSections(data) + is Script -> scriptSerde.calculateDataSection(data) is ScriptFolder -> DataSectionHolder( containingData = listOf( DataSectionLeaf(2L + data.name.length), DataSectionLeaf.BOOLEAN, DataSectionLeaf.BOOLEAN, - assetListSerde.collectDataSections(data.scriptListEntries) + assetListSerde.calculateDataSection(data.scriptListEntries) ) ) } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/SerializationContext.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/SerializationContext.kt index 0fc0952..5f97c5c 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/SerializationContext.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/SerializationContext.kt @@ -1,6 +1,7 @@ package de.darkatra.bfme2.map.serialization import de.darkatra.bfme2.map.serialization.model.AssetEntry +import de.darkatra.bfme2.map.serialization.model.DataSectionHolder import java.util.Stack internal class SerializationContext( @@ -19,6 +20,22 @@ internal class SerializationContext( ?: throw IllegalArgumentException("Could not find assetName for assetIndex '$assetIndex'.") } + internal fun getAssetIndex(assetName: String): UInt { + return assetNames!!.entries.find { it.value == assetName }?.key + ?: throw IllegalArgumentException("Could not find assetIndex for assetName '$assetName'.") + } + + private var assetDataSections: Map? = null + + internal fun setAssetDataSections(assetDataSections: Map) { + this.assetDataSections = assetDataSections + } + + internal fun getAssetDataSection(assetName: String): DataSectionHolder { + return assetDataSections!![assetName] + ?: throw IllegalArgumentException("Could not find assetIndex for assetName '$assetName'.") + } + private val parsingStack: Stack = Stack() internal val currentEndPosition: Long @@ -36,5 +53,4 @@ internal class SerializationContext( internal fun pop() { parsingStack.pop() } - } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/Serializer.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/Serializer.kt index 06efd4a..6a5c3aa 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/Serializer.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/Serializer.kt @@ -5,7 +5,7 @@ import java.io.OutputStream internal interface Serializer { - fun collectDataSections(data: T): DataSection + fun calculateDataSection(data: T): DataSection fun serialize(outputStream: OutputStream, data: T) } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ShortSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ShortSerde.kt index d8d187e..6381942 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ShortSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/ShortSerde.kt @@ -21,5 +21,5 @@ internal class ShortSerde( postProcessor ) { - override fun collectDataSections(data: Short): DataSection = DataSectionLeaf.SHORT + override fun calculateDataSection(data: Short): DataSection = DataSectionLeaf.SHORT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntBooleanSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntBooleanSerde.kt index 668e09d..61594ee 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntBooleanSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntBooleanSerde.kt @@ -21,5 +21,5 @@ internal class UIntBooleanSerde( postProcessor ) { - override fun collectDataSections(data: Boolean): DataSection = DataSectionLeaf.INT + override fun calculateDataSection(data: Boolean): DataSection = DataSectionLeaf.INT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntSerde.kt index cb97085..53d9c2c 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UIntSerde.kt @@ -21,5 +21,5 @@ internal class UIntSerde( postProcessor ) { - override fun collectDataSections(data: UInt): DataSection = DataSectionLeaf.INT + override fun calculateDataSection(data: UInt): DataSection = DataSectionLeaf.INT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortPrefixedStringSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortPrefixedStringSerde.kt index 784939f..ae62832 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortPrefixedStringSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortPrefixedStringSerde.kt @@ -21,5 +21,5 @@ internal class UShortPrefixedStringSerde( postProcessor ) { - override fun collectDataSections(data: String): DataSection = DataSectionLeaf(2L + data.length) + override fun calculateDataSection(data: String): DataSection = DataSectionLeaf(2L + data.length) } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortSerde.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortSerde.kt index b49916d..f2c3fe3 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortSerde.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/UShortSerde.kt @@ -21,5 +21,5 @@ internal class UShortSerde( postProcessor ) { - override fun collectDataSections(data: UShort): DataSection = DataSectionLeaf.SHORT + override fun calculateDataSection(data: UShort): DataSection = DataSectionLeaf.SHORT } diff --git a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/model/DataSectionHolder.kt b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/model/DataSectionHolder.kt index 33ef65a..8f27588 100644 --- a/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/model/DataSectionHolder.kt +++ b/map/src/main/kotlin/de/darkatra/bfme2/map/serialization/model/DataSectionHolder.kt @@ -29,33 +29,35 @@ internal data class DataSectionLeaf( * Represents multiple sections of data in a map file that somewhat belong together. * Think of it as a class, it's basically a container for multiple [DataSection] objects. */ -internal open class DataSectionHolder( - internal open val containingData: List, - internal open val assetName: String? = null, - internal open val assetVersion: UShort? = null +internal data class DataSectionHolder( + internal val containingData: List, + internal val assetName: String? = null, + internal val assetVersion: UShort? = null ) : DataSection { override val size: Long - get() = when (isAsset) { + get() = when (isVersionedAsset) { // each asset has a header of 4 bytes for the assetIndex, 2 bytes for the assetVersion and 4 bytes for the assetSize true -> 4 + 2 + 4 + containingData.sumOf(DataSection::size) false -> containingData.sumOf(DataSection::size) } - internal open val isAsset: Boolean - get() = assetName != null && assetVersion != null -} + internal val isAsset: Boolean + get() = assetName != null -/** - * A special [DataSectionHolder] that represents the root of a map file. - * The only difference to a regular [DataSectionHolder] is that it's an asset but does not require the 10 byte asset header. - */ -internal class MapFileDataSectionHolder( - containingData: List -) : DataSectionHolder(containingData, "MapFile", 0u) { + internal val isVersionedAsset: Boolean + get() = isAsset && assetVersion != null - override val size: Long - get() = containingData.sumOf(DataSection::size) + internal fun flatten(): List { + return flatten(containingData.filterIsInstance()) + } - override val isAsset: Boolean = true + private fun flatten(list: List): List { + return list.flatMap { + buildList { + add(it) + addAll(flatten(it.containingData.filterIsInstance())) + } + } + } } diff --git a/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerdeTest.kt b/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerdeTest.kt deleted file mode 100644 index bc258dc..0000000 --- a/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileSerdeTest.kt +++ /dev/null @@ -1,28 +0,0 @@ -package de.darkatra.bfme2.map.serialization - -import com.google.common.io.ByteStreams -import de.darkatra.bfme2.map.MapFile -import org.assertj.core.api.Assertions.assertThat -import org.junit.jupiter.api.Test - -class MapFileSerdeTest { - - @Test - fun `should calculate the correct map file size`() { - - val expectedMapFileSize = ByteStreams.exhaust(TestUtils.getInputStream(TestUtils.UNCOMPRESSED_MAP_PATH)) - - val map = MapFileReader().read(TestUtils.getInputStream(TestUtils.UNCOMPRESSED_MAP_PATH)) - - val serializationContext = SerializationContext(true) - val annotationProcessingContext = AnnotationProcessingContext(false) - val serdeFactory = SerdeFactory(annotationProcessingContext, serializationContext) - val mapFileSerde = serdeFactory.getSerde(MapFile::class) - - val actualMapFileDataSection = mapFileSerde.collectDataSections(map) - - // TODO: write the MapFile via MapFileWriter, then compare the file sizes (this makes it include the assetNames) - // 1382 is the byte count of the assetNames for this particular map - assertThat(actualMapFileDataSection.size + 1382).isEqualTo(expectedMapFileSize) - } -} diff --git a/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriterTest.kt b/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriterTest.kt index 1bb1dad..3948281 100644 --- a/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriterTest.kt +++ b/map/src/test/kotlin/de/darkatra/bfme2/map/serialization/MapFileWriterTest.kt @@ -1,9 +1,9 @@ package de.darkatra.bfme2.map.serialization +import com.google.common.io.ByteStreams import de.darkatra.bfme2.map.MapFileCompression import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test -import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream class MapFileWriterTest { @@ -11,15 +11,14 @@ class MapFileWriterTest { @Test fun `should write map`() { + val expectedMapFileSize = ByteStreams.exhaust(TestUtils.getInputStream(TestUtils.UNCOMPRESSED_MAP_PATH)) + val inputMapFile = TestUtils.getInputStream(TestUtils.UNCOMPRESSED_MAP_PATH) val parsedMapFile = MapFileReader().read(inputMapFile) val writtenMapFile = ByteArrayOutputStream() MapFileWriter().write(writtenMapFile, parsedMapFile, MapFileCompression.UNCOMPRESSED) - ByteArrayInputStream(writtenMapFile.toByteArray()).use { - // TODO: checking for same size is probably sufficient, this allows us to reorder assets if we wanted to - assertThat(it).hasSameContentAs(inputMapFile) - } + assertThat(writtenMapFile.size()).isEqualTo(expectedMapFileSize) } }