From 7b5faaa61e9ef77f196d0c1dd0ca7d6800fcd633 Mon Sep 17 00:00:00 2001 From: Alexander Nozik Date: Mon, 30 Aug 2021 12:58:41 +0300 Subject: [PATCH] dynamic root scheme parser --- .../kotlin/ru/mipt/npm/root/TGeoManager.kt | 22 -- .../kotlin/ru/mipt/npm/root/TObjectScheme.kt | 140 ++++++++++ .../kotlin/ru/mipt/npm/root/jsonToRoot.kt | 261 ------------------ .../ru/mipt/npm/root/rootSchemeToSolid.kt | 217 +++++++++++++++ .../npm/root/serialization/TGeoManager.kt | 17 ++ .../root/{ => serialization}/TGeoMaterial.kt | 2 +- .../root/{ => serialization}/TGeoMatrix.kt | 2 +- .../root/{ => serialization}/TGeoMedium.kt | 2 +- .../npm/root/{ => serialization}/TGeoNode.kt | 4 +- .../npm/root/{ => serialization}/TGeoShape.kt | 2 +- .../root/{ => serialization}/TGeoVolume.kt | 4 +- .../npm/root/{ => serialization}/TObject.kt | 2 +- .../mipt/npm/root/serialization/jsonToRoot.kt | 235 ++++++++++++++++ .../root/{ => serialization}/rootToSolid.kt | 2 +- .../kotlin/ru/mipt/npm/root/loadBMN.kt | 32 ++- gradle.properties | 2 - 16 files changed, 641 insertions(+), 305 deletions(-) delete mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt create mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObjectScheme.kt delete mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/jsonToRoot.kt create mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootSchemeToSolid.kt create mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoManager.kt rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoMaterial.kt (72%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoMatrix.kt (94%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoMedium.kt (85%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoNode.kt (89%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoShape.kt (98%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TGeoVolume.kt (91%) rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/TObject.kt (94%) create mode 100644 cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt rename cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/{ => serialization}/rootToSolid.kt (99%) diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt deleted file mode 100644 index 8b2c3c26..00000000 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoManager.kt +++ /dev/null @@ -1,22 +0,0 @@ -package ru.mipt.npm.root - -import kotlinx.serialization.Contextual -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable - -@Serializable -@SerialName("TGeoManager") -public class TGeoManager : TNamed() { - - @Contextual - public val fMatrices: TObjArray<@Contextual TGeoMatrix> = TObjArray.getEmpty() - - @Contextual - public val fShapes: TObjArray<@Contextual TGeoShape> = TObjArray.getEmpty() - - @Contextual - public val fVolumes: TObjArray<@Contextual TGeoVolume> = TObjArray.getEmpty() - - @Contextual - public val fNodes: TObjArray<@Contextual TGeoNode> = TObjArray.getEmpty() -} diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObjectScheme.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObjectScheme.kt new file mode 100644 index 00000000..0ec699bd --- /dev/null +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObjectScheme.kt @@ -0,0 +1,140 @@ +package ru.mipt.npm.root + +import kotlinx.serialization.json.Json +import space.kscience.dataforge.meta.* +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName +import kotlin.properties.ReadOnlyProperty + +public typealias RefCache = List + +public interface ObjectRef { + public fun resolve(refCache: RefCache): T? +} + +private class ChildObjectRef( + val spec: Specification, + val metaProvider: () -> Meta? +) : ObjectRef { + override fun resolve(refCache: RefCache): T? { + val meta = metaProvider() ?: return null + meta["\$ref"]?.int?.let { refId -> + return spec.read(refCache[refId]) + } + return spec.read(meta) + } +} + +public fun List>.resolve(refCache: RefCache): List = map { it.resolve(refCache)!! } + + +public open class TObjectScheme : Scheme() { + + public val typename: String by string(key = "_typename".asName()) { error("Type is not defined") } + + internal fun tObjectArray( + spec: Specification + ): ReadOnlyProperty>> = ReadOnlyProperty { _, property -> + meta.getIndexed(Name.of(property.name, "arr")).values.map { ChildObjectRef(spec){it} } + } + + internal fun refSpec( + spec: Specification, + key: Name? = null + ): ReadOnlyProperty> = ReadOnlyProperty { _, property -> + ChildObjectRef(spec) { meta[key ?: property.name.asName()] } + } + + public companion object : SchemeSpec(::TObjectScheme) +} + +public open class TNamedScheme : TObjectScheme() { + public val fName: String by string("") + public val fTitle: String by string("") + + public companion object : SchemeSpec(::TNamedScheme) +} + +public class TGeoMaterialScheme : TNamedScheme() { + + public companion object : SchemeSpec(::TGeoMaterialScheme) +} + +public class TGeoMediumScheme : TNamedScheme() { + public val fMaterial: ObjectRef by refSpec(TGeoMaterialScheme) + public val fParams: DoubleArray by doubleArray() + + public companion object : SchemeSpec(::TGeoMediumScheme) +} + +public class TGeoShapeScheme : TNamedScheme() { + public val fDX: Double by double(0.0) + public val fDY: Double by double(0.0) + public val fDZ: Double by double(0.0) + + public companion object : SchemeSpec(::TGeoShapeScheme) +} + +public class TGeoVolumeScheme : TNamedScheme() { + public val fNodes: List> by tObjectArray(TGeoNodeScheme) + public val fShape: ObjectRef by refSpec(TGeoShapeScheme) + public val fMedium: ObjectRef by refSpec(TGeoMediumScheme) + + public companion object : SchemeSpec(::TGeoVolumeScheme) +} + +public class TGeoNodeScheme : TNamedScheme() { + public val fVolume: ObjectRef by refSpec(TGeoVolumeScheme) + + public companion object : SchemeSpec(::TGeoNodeScheme) +} + +public class TGeoMatrixScheme : TNamedScheme() { + public companion object : SchemeSpec(::TGeoMatrixScheme) +} + + +public class TGeoBoolNodeScheme : TObjectScheme() { + public val fLeft: ObjectRef by refSpec(TGeoShapeScheme) + public val fLeftMat: ObjectRef by refSpec(TGeoMatrixScheme) + + public val fRight: ObjectRef by refSpec(TGeoShapeScheme) + public val fRightMat: ObjectRef by refSpec(TGeoMatrixScheme) + + public companion object : SchemeSpec(::TGeoBoolNodeScheme) +} + + +public class TGeoManagerScheme : TNamedScheme() { + public val fMatrices: List> by tObjectArray(TGeoMatrixScheme) + + public val fShapes: List> by tObjectArray(TGeoShapeScheme) + + public val fVolumes: List> by tObjectArray(TGeoVolumeScheme) + + public val fNodes: List> by tObjectArray(TGeoNodeScheme) + + public val refCache: List by lazy { + val res = ArrayList(4096) + fun fillCache(element: Meta) { + if(element["\$ref"] == null) { + res.add(element) + element.items.values.forEach { + if (!it.isLeaf) { + fillCache(it) + } + } + } + } + fillCache(meta) + res + } + + public companion object : SchemeSpec(::TGeoManagerScheme) { + + public fun parse(string: String): TGeoManagerScheme { + val meta = Json.decodeFromString(MetaSerializer, string) + return read(meta) + } + } +} diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/jsonToRoot.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/jsonToRoot.kt deleted file mode 100644 index 1b7fe798..00000000 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/jsonToRoot.kt +++ /dev/null @@ -1,261 +0,0 @@ -package ru.mipt.npm.root - -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.encoding.Decoder -import kotlinx.serialization.json.* -import kotlinx.serialization.modules.* - - -private fun jsonRootDeserializer(tSerializer: KSerializer, builder: (JsonElement) -> T): DeserializationStrategy = object : - DeserializationStrategy { - private val jsonElementSerializer = JsonElement.serializer() - - override val descriptor: SerialDescriptor - get() = jsonElementSerializer.descriptor - - override fun deserialize(decoder: Decoder): T { - val json = decoder.decodeSerializableValue(jsonElementSerializer) - return builder(json) - } -} - -/** - * Load Json encoded TObject - */ -public fun TObject.Companion.decodeFromJson(serializer: KSerializer, jsonElement: JsonElement): T = - RootDecoder.decode(serializer, jsonElement) - -public fun TObject.Companion.decodeFromString(serializer: KSerializer, string: String): T { - val json = RootDecoder.json.parseToJsonElement(string) - return RootDecoder.decode(serializer, json) -} - -private object RootDecoder { - - private class RootUnrefSerializer( - private val tSerializer: KSerializer, - private val refCache: List, - ) : KSerializer by tSerializer { - - override fun deserialize(decoder: Decoder): T { - val input = decoder as JsonDecoder - val element = input.decodeJsonElement() - val refId = (element as? JsonObject)?.get("\$ref")?.jsonPrimitive?.int - val ref = if (refId != null) { - //println("Substituting ${tSerializer.descriptor.serialName} ref $refId") - //Forward ref for shapes - when (tSerializer.descriptor.serialName) { - "TGeoShape" -> return TGeoShapeRef { - refCache[refId].getOrPutValue { - input.json.decodeFromJsonElement(tSerializer, it) as TGeoShape - } - } as T - "TGeoVolumeAssembly" -> return TGeoVolumeAssemblyRef { - refCache[refId].getOrPutValue { - input.json.decodeFromJsonElement(tSerializer, it) as TGeoVolumeAssembly - } - } as T - //Do unref - else -> refCache[refId] - } - } else { - refCache.find { it.element == element } ?: error("Element '$element' not found in the cache") - } - - return ref.getOrPutValue { -// val actualTypeName = it.jsonObject["_typename"]?.jsonPrimitive?.content - input.json.decodeFromJsonElement(tSerializer, it) - } - } - } - - private fun KSerializer.unref(refCache: List): KSerializer = - RootUnrefSerializer(this, refCache) - - @OptIn(ExperimentalSerializationApi::class) - fun unrefSerializersModule( - refCache: List - ): SerializersModule = SerializersModule { - include(serializersModule) - - //contextual(TGeoManager.serializer().unref(refCache)) - contextual(TObjArray::class) { TObjArray.serializer(it[0]).unref(refCache) } - //contextual(TGeoVolumeAssembly.serializer().unref(refCache)) - //contextual(TGeoShapeAssembly.serializer().unref(refCache)) - contextual(TGeoRotation.serializer().unref(refCache)) - contextual(TGeoMedium.serializer().unref(refCache)) - //contextual(TGeoVolume.serializer().unref(refCache)) - //contextual(TGeoMatrix.serializer().unref(refCache)) - //contextual(TGeoNode.serializer().unref(refCache)) - //contextual(TGeoNodeOffset.serializer().unref(refCache)) - //contextual(TGeoNodeMatrix.serializer().unref(refCache)) - //contextual(TGeoShape.serializer().unref(refCache)) - //contextual(TObject.serializer().unref(refCache)) - - - polymorphicDefault(TGeoShape::class) { - if (it == null) { - TGeoShape.serializer().unref(refCache) - } else { - error("Unrecognized shape $it") - } - } - - polymorphicDefault(TGeoMatrix::class) { - if (it == null) { - TGeoMatrix.serializer().unref(refCache) - } else { - error("Unrecognized matrix $it") - } - } - - polymorphicDefault(TGeoVolume::class) { - if (it == null) { - TGeoVolume.serializer().unref(refCache) - } else { - error("Unrecognized volume $it") - } - } - - polymorphicDefault(TGeoNode::class) { - if (it == null) { - TGeoNode.serializer().unref(refCache) - } else { - error("Unrecognized node $it") - } - } - } - - /** - * Create an instance of Json with unfolding Root references. This instance could not be reused because of the cache. - */ - private fun unrefJson(refCache: MutableList): Json = Json { - encodeDefaults = true - ignoreUnknownKeys = true - classDiscriminator = "_typename" - serializersModule = unrefSerializersModule(refCache) - } - - - fun decode(sourceDeserializer: KSerializer, source: JsonElement): T { - val refCache = ArrayList() - - fun fillCache(element: JsonElement) { - when (element) { - is JsonObject -> { - if (element["_typename"] != null) { - refCache.add(RefEntry(element)) - } - element.values.forEach { - fillCache(it) - } - } - is JsonArray -> { - element.forEach { - fillCache(it) - } - } - else -> { - //ignore primitives - } - } - } - fillCache(source) - - return unrefJson(refCache).decodeFromJsonElement(sourceDeserializer.unref(refCache), source) - } - - class RefEntry(val element: JsonElement) { - - var value: Any? = null - - fun getOrPutValue(builder: (JsonElement) -> T): T { - if (value == null) { - value = builder(element) - } - return value as T - } - - override fun toString(): String = element.toString() - } - - private fun PolymorphicModuleBuilder.shapes() { - subclass(TGeoBBox.serializer()) - subclass(TGeoCompositeShape.serializer()) - subclass(TGeoXtru.serializer()) - subclass(TGeoTube.serializer()) - subclass(TGeoTubeSeg.serializer()) - subclass(TGeoPcon.serializer()) - subclass(TGeoPgon.serializer()) - subclass(TGeoShapeAssembly.serializer()) - } - - private fun PolymorphicModuleBuilder.matrices() { - subclass(TGeoIdentity.serializer()) - subclass(TGeoHMatrix.serializer()) - subclass(TGeoTranslation.serializer()) - subclass(TGeoRotation.serializer()) - subclass(TGeoCombiTrans.serializer()) - } - - private fun PolymorphicModuleBuilder.boolNodes() { - subclass(TGeoIntersection.serializer()) - subclass(TGeoUnion.serializer()) - subclass(TGeoSubtraction.serializer()) - } - - private val serializersModule = SerializersModule { - -// polymorphic(TObject::class) { -// default { JsonRootSerializer } -// -// shapes() -// matrices() -// boolNodes() -// -// subclass(TGeoMaterial.serializer()) -// subclass(TGeoMixture.serializer()) -// -// subclass(TGeoMedium.serializer()) -// -// //subclass(TGeoNode.serializer()) -// subclass(TGeoNodeMatrix.serializer()) -// subclass(TGeoVolume.serializer()) -// subclass(TGeoVolumeAssembly.serializer()) -// subclass(TGeoManager.serializer()) -// } - - polymorphic(TGeoShape::class) { - shapes() - } - - polymorphic(TGeoMatrix::class) { - matrices() - } - - polymorphic(TGeoBoolNode::class) { - boolNodes() - } - - polymorphic(TGeoNode::class) { - subclass(TGeoNodeMatrix.serializer()) - subclass(TGeoNodeOffset.serializer()) - } - - polymorphic(TGeoVolume::class) { - subclass(TGeoVolume.serializer()) - subclass(TGeoVolumeAssembly.serializer()) - } - } - - val json = Json { - encodeDefaults = true - ignoreUnknownKeys = true - classDiscriminator = "_typename" - serializersModule = this@RootDecoder.serializersModule - } - -} \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootSchemeToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootSchemeToSolid.kt new file mode 100644 index 00000000..89849613 --- /dev/null +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootSchemeToSolid.kt @@ -0,0 +1,217 @@ +package ru.mipt.npm.root + +import space.kscience.dataforge.meta.* +import space.kscience.dataforge.names.Name +import space.kscience.dataforge.names.asName +import space.kscience.dataforge.names.plus +import space.kscience.visionforge.solid.* +import kotlin.math.PI +import kotlin.math.atan2 +import kotlin.math.pow +import kotlin.math.sqrt + +private val solidsName = "solids".asName() +private val volumesName = "volumes".asName() + +private operator fun Number.times(d: Double) = toDouble() * d + +private operator fun Number.times(f: Float) = toFloat() * f + +private fun degToRad(d: Double) = d * PI / 180.0 + +// converting to XYZ to Tait–Bryan angles according to https://en.wikipedia.org/wiki/Euler_angles#Rotation_matrix +private fun Solid.rotate(rot: DoubleArray) { + val xAngle = atan2(-rot[5], rot[8]) + val yAngle = atan2(rot[2], sqrt(1.0 - rot[2].pow(2))) + val zAngle = atan2(-rot[1], rot[0]) + rotation = Point3D(xAngle, yAngle, zAngle) +} + +private fun Solid.translate(trans: DoubleArray) { + val (x, y, z) = trans + position = Point3D(x, y, z) +} + +private fun Solid.useMatrix(matrix: TGeoMatrixScheme?) { + if (matrix == null) return + when (matrix.typename) { + "TGeoIdentity" -> { + //do nothing + } + "TGeoTranslation" -> { + val fTranslation by matrix.doubleArray() + translate(fTranslation) + } + "TGeoRotation" -> { + val fRotationMatrix by matrix.doubleArray() + rotate(fRotationMatrix) + } + "TGeoCombiTrans" -> { + val fTranslation by matrix.doubleArray() + + translate(fTranslation) + if (matrix.meta["fRotationMatrix"] != null) { + val fRotationMatrix by matrix.doubleArray() + rotate(fRotationMatrix) + } + } + "TGeoHMatrix" -> { + val fTranslation by matrix.doubleArray() + val fRotationMatrix by matrix.doubleArray() + val fScale by matrix.doubleArray() + translate(fTranslation) + rotate(fRotationMatrix) + scale = Point3D(fScale[0], fScale[1], fScale[2]) + } + } +} + +private fun SolidGroup.addShape(shape: TGeoShapeScheme, refCache: RefCache) { + when (shape.typename) { + "TGeoCompositeShape" -> { + val bool by shape.spec(TGeoBoolNodeScheme) + val compositeType = when (bool.typename) { + "TGeoIntersection" -> CompositeType.INTERSECT + "TGeoSubtraction" -> CompositeType.SUBTRACT + "TGeoUnion" -> CompositeType.UNION + else -> error("Unknown bool node type ${bool.typename}") + } + composite(compositeType, name = shape.fName) { + addShape(bool.fLeft.resolve(refCache)!!, refCache).apply { + useMatrix(bool.fLeftMat.resolve(refCache)) + } + addShape(bool.fRight.resolve(refCache)!!, refCache).apply { + useMatrix(bool.fRightMat.resolve(refCache)) + } + } + } + "TGeoXtru" -> { + val fNvert by shape.meta.int(0) + val fX by shape.meta.doubleArray() + val fY by shape.meta.doubleArray() + val fNz by shape.meta.int(0) + val fZ by shape.meta.doubleArray() + val fX0 by shape.meta.doubleArray() + val fY0 by shape.meta.doubleArray() + val fScale by shape.meta.doubleArray() + + extruded(name = shape.fName) { + (0 until fNvert).forEach { index -> + shape { + point(fX[index], fY[index]) + } + } + + (0 until fNz).forEach { index -> + layer( + fZ[index], + fX0[index], + fY0[index], + fScale[index] + ) + } + } + } + "TGeoTube" -> { + val fRmax by shape.meta.double(0.0) + val fDz by shape.meta.double(0.0) + val fRmin by shape.meta.double(0.0) + + tube( + radius = fRmax, + height = fDz * 2, + innerRadius = fRmin, + name = shape.fName + ) + } + "TGeoTubeSeg" -> { + val fRmax by shape.meta.double(0.0) + val fDz by shape.meta.double(0.0) + val fRmin by shape.meta.double(0.0) + val fPhi1 by shape.meta.double(0.0) + val fPhi2 by shape.meta.double(0.0) + + tube( + radius = fRmax, + height = fDz * 2, + innerRadius = fRmin, + startAngle = degToRad(fPhi1), + angle = degToRad(fPhi2 - fPhi1), + name = shape.fName + ) + } + "TGeoPcon" -> { + TODO() + } + "TGeoPgon" -> { + TODO() + } + "TGeoShapeAssembly" -> { + val fVolume by shape.refSpec(TGeoVolumeScheme) + volume(fVolume.resolve(refCache)!!, refCache) + } + "TGeoBBox" -> { + box(shape.fDX * 2, shape.fDY * 2, shape.fDZ * 2, name = shape.fName) + } + } +} + +private fun SolidGroup.node(obj: TGeoNodeScheme, refCache: RefCache) { + val volume = obj.fVolume.resolve(refCache) + if (volume != null) { + volume(volume, refCache, obj.fName).apply { + when (obj.typename) { + "TGeoNodeMatrix" -> { + val fMatrix by obj.refSpec(TGeoMatrixScheme) + useMatrix(fMatrix.resolve(refCache)) + } + "TGeoNodeOffset" -> { + val fOffset by obj.meta.double(0.0) + x = fOffset + } + } + } + } +} + +private fun buildGroup(volume: TGeoVolumeScheme, refCache: RefCache): SolidGroup = SolidGroup { + volume.fShape.resolve(refCache)?.let { addShape(it, refCache) } + volume.fNodes.let { + it.forEach { obj -> + node(obj.resolve(refCache)!!, refCache) + } + } +} + +private val SolidGroup.rootPrototypes: SolidGroup get() = (parent as? SolidGroup)?.rootPrototypes ?: this + +private fun SolidGroup.volume( + volume: TGeoVolumeScheme, + refCache: RefCache, + name: String? = null, + cache: Boolean = true +): Solid { + val group = buildGroup(volume, refCache) + val combinedName = if (volume.fName.isEmpty()) { + name + } else if (name == null) { + volume.fName + } else { + "${name}_${volume.fName}" + } + return if (!cache) { + set(combinedName?.let { Name.parse(it)},group) + group + } else newRef( + name = combinedName, + obj = group, + prototypeHolder = rootPrototypes, + templateName = volumesName + Name.parse(combinedName ?: "volume[${group.hashCode()}]") + ) +} + +public fun TGeoManagerScheme.toSolid(): SolidGroup = SolidGroup { + fNodes.forEach { + node(it.resolve(refCache)!!, refCache) + } +} \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoManager.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoManager.kt new file mode 100644 index 00000000..59957364 --- /dev/null +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoManager.kt @@ -0,0 +1,17 @@ +package ru.mipt.npm.root.serialization + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +@SerialName("TGeoManager") +public class TGeoManager : TNamed() { + + public val fMatrices: TObjArray = TObjArray.getEmpty() + + public val fShapes: TObjArray = TObjArray.getEmpty() + + public val fVolumes: TObjArray = TObjArray.getEmpty() + + public val fNodes: TObjArray = TObjArray.getEmpty() +} diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMaterial.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMaterial.kt similarity index 72% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMaterial.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMaterial.kt index f4883e0e..e13d30b4 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMaterial.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMaterial.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMatrix.kt similarity index 94% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMatrix.kt index 0c6ec7ab..63ee38a7 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMatrix.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMatrix.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMedium.kt similarity index 85% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMedium.kt index 72b72c4c..630826e8 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoMedium.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoMedium.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoNode.kt similarity index 89% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoNode.kt index 75a8ac05..1babbbfc 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoNode.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoNode.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("TGeoNode") -public sealed class TGeoNode : TNamed() { +public open class TGeoNode : TNamed() { public val fGeoAtt: UInt = 0u @Contextual diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoShape.kt similarity index 98% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoShape.kt index 7cf6876a..faf47121 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoShape.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoShape.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoVolume.kt similarity index 91% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoVolume.kt index 9823eb1f..77c99e54 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TGeoVolume.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TGeoVolume.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName @@ -15,7 +15,7 @@ public open class TGeoVolume : TNamed() { public val fFillStyle: Int? = null @Contextual - public val fNodes: TObjArray? = null + public val fNodes: TObjArray<@Contextual TGeoNode>? = null @Contextual public val fShape: TGeoShape? = null diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TObject.kt similarity index 94% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TObject.kt index 063cadfe..644c05a4 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/TObject.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/TObject.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import kotlinx.serialization.Contextual import kotlinx.serialization.SerialName diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt new file mode 100644 index 00000000..94079ccd --- /dev/null +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/jsonToRoot.kt @@ -0,0 +1,235 @@ +package ru.mipt.npm.root.serialization + +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.json.* +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.contextual +import kotlinx.serialization.modules.polymorphic +import kotlinx.serialization.modules.subclass + + +private fun jsonRootDeserializer( + tSerializer: KSerializer, + builder: (JsonElement) -> T +): DeserializationStrategy = object : + DeserializationStrategy { + private val jsonElementSerializer = JsonElement.serializer() + + override val descriptor: SerialDescriptor + get() = jsonElementSerializer.descriptor + + override fun deserialize(decoder: Decoder): T { + val json = decoder.decodeSerializableValue(jsonElementSerializer) + return builder(json) + } +} + +/** + * Load Json encoded TObject + */ +public fun TObject.decodeFromJson(serializer: KSerializer, jsonElement: JsonElement): T = + RootDecoder.decode(serializer, jsonElement) + +public fun TObject.decodeFromString(serializer: KSerializer, string: String): T { + val json = Json.parseToJsonElement(string) + return RootDecoder.decode(serializer, json) +} + +private object RootDecoder { + + private class RootUnrefSerializer( + private val tSerializer: KSerializer, + private val refCache: List, + ) : KSerializer by tSerializer { + + override fun deserialize(decoder: Decoder): T { + val input = decoder as JsonDecoder + val element = input.decodeJsonElement() + val refId = (element as? JsonObject)?.get("\$ref")?.jsonPrimitive?.int + val ref = if (refId != null) { + println("Substituting ${tSerializer.descriptor.serialName} ref $refId") + //Forward ref for shapes + when (tSerializer.descriptor.serialName) { + "TGeoShape" -> return TGeoShapeRef { + refCache[refId].getOrPutValue { + input.json.decodeFromJsonElement(tSerializer, it) as TGeoShape + } + } as T + + "TGeoVolumeAssembly" -> return TGeoVolumeAssemblyRef { + refCache[refId].getOrPutValue { + input.json.decodeFromJsonElement(tSerializer, it) as TGeoVolumeAssembly + } + } as T + + "TGeoVolume" -> return TGeoVolumeRef { + refCache[refId].getOrPutValue { + input.json.decodeFromJsonElement(tSerializer, it) as TGeoVolume + } + } as T + + //Do unref + else -> refCache[refId] + } + } else { + refCache.find { it.element == element } ?: error("Element '$element' not found in the cache") + } + + return ref.getOrPutValue { +// println("Decoding $it") + val actualTypeName = it.jsonObject["_typename"]?.jsonPrimitive?.content + input.json.decodeFromJsonElement(tSerializer, it) + } + } + } + + private fun KSerializer.unref(refCache: List): KSerializer = RootUnrefSerializer(this, refCache) + + @OptIn(ExperimentalSerializationApi::class) + fun unrefSerializersModule( + refCache: List + ): SerializersModule = SerializersModule { + + contextual(TObjArray::class) { + TObjArray.serializer(it[0]).unref(refCache) + } + + contextual(TGeoMedium.serializer().unref(refCache)) + + polymorphic(TGeoBoolNode::class) { + subclass(TGeoIntersection.serializer().unref(refCache)) + subclass(TGeoUnion.serializer().unref(refCache)) + subclass(TGeoSubtraction.serializer().unref(refCache)) + } + + polymorphic(TGeoShape::class) { + subclass(TGeoBBox.serializer()) + subclass(TGeoXtru.serializer()) + subclass(TGeoTube.serializer()) + subclass(TGeoTubeSeg.serializer()) + subclass(TGeoPcon.serializer()) + subclass(TGeoPgon.serializer()) + + subclass(TGeoCompositeShape.serializer().unref(refCache)) + subclass(TGeoShapeAssembly.serializer().unref(refCache)) + + default { + if (it == null) { + TGeoShape.serializer().unref(refCache) + } else { + error("Unrecognized shape $it") + } + } + } + + polymorphic(TGeoMatrix::class) { + subclass(TGeoIdentity.serializer()) + subclass(TGeoHMatrix.serializer().unref(refCache)) + subclass(TGeoTranslation.serializer()) + subclass(TGeoRotation.serializer()) + subclass(TGeoCombiTrans.serializer().unref(refCache)) + + + val unrefed = TGeoMatrix.serializer().unref(refCache) + default { + if (it == null) { + unrefed + } else { + error("Unrecognized matrix $it") + } + } + } + + polymorphic(TGeoVolume::class, TGeoVolume.serializer().unref(refCache)) { + subclass(TGeoVolumeAssembly.serializer().unref(refCache)) + + val unrefed = TGeoVolume.serializer().unref(refCache) + default { + if (it == null) { + unrefed + } else { + error("Unrecognized volume $it") + } + } + } + + polymorphic(TGeoNode::class, TGeoNode.serializer().unref(refCache)) { + subclass(TGeoNodeMatrix.serializer().unref(refCache)) + subclass(TGeoNodeOffset.serializer().unref(refCache)) + + val unrefed = TGeoNode.serializer().unref(refCache) + default { + if (it == null) { + unrefed + } else { + error("Unrecognized node $it") + } + } + } + } + + /** + * Create an instance of Json with unfolding Root references. This instance could not be reused because of the cache. + */ + private fun unrefJson(refCache: MutableList): Json = Json { + encodeDefaults = true + ignoreUnknownKeys = true + classDiscriminator = "_typename" + serializersModule = unrefSerializersModule(refCache) + } + + + fun decode(sourceDeserializer: KSerializer, source: JsonElement): T { + val refCache = ArrayList() + + fun fillCache(element: JsonElement) { + when (element) { + is JsonObject -> { + if (element["_typename"] != null) { + refCache.add(RefEntry(element)) + } + element.values.forEach { + fillCache(it) + } + } + is JsonArray -> { + element.forEach { + fillCache(it) + } + } + else -> { + //ignore primitives + } + } + } + fillCache(source) + + return unrefJson(refCache).decodeFromJsonElement(sourceDeserializer.unref(refCache), source) + } + + class RefEntry(val element: JsonElement) { + + var value: Any? = null + + fun getOrPutValue(builder: (JsonElement) -> T): T { + if (value == null) { + value = builder(element) + } + return value as T + } + + override fun toString(): String = element.toString() + } + +// val json = Json { +// encodeDefaults = true +// ignoreUnknownKeys = true +// classDiscriminator = "_typename" +// serializersModule = this@RootDecoder.serializersModule +// } + +} \ No newline at end of file diff --git a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootToSolid.kt b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt similarity index 99% rename from cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootToSolid.kt rename to cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt index ee14324d..117e51b3 100644 --- a/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/rootToSolid.kt +++ b/cern-root-loader/src/commonMain/kotlin/ru/mipt/npm/root/serialization/rootToSolid.kt @@ -1,4 +1,4 @@ -package ru.mipt.npm.root +package ru.mipt.npm.root.serialization import space.kscience.dataforge.names.Name import space.kscience.dataforge.names.asName diff --git a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt b/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt index 7fcd1e22..fbd7482e 100644 --- a/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt +++ b/cern-root-loader/src/jvmTest/kotlin/ru/mipt/npm/root/loadBMN.kt @@ -1,12 +1,16 @@ package ru.mipt.npm.root -import kotlinx.serialization.json.* +import kotlinx.serialization.json.JsonArray +import kotlinx.serialization.json.JsonElement +import kotlinx.serialization.json.JsonObject +import kotlinx.serialization.json.jsonPrimitive +import ru.mipt.npm.root.serialization.TGeoManager import space.kscience.visionforge.solid.Solids import java.time.Duration import kotlin.system.measureTimeMillis private fun JsonElement.countTypes(): Sequence = sequence { - when (val json = this@countTypes){ + when (val json = this@countTypes) { is JsonObject -> { json["_typename"]?.let { yield(it.jsonPrimitive.content) } json.values.forEach { yieldAll(it.countTypes()) } @@ -16,25 +20,33 @@ private fun JsonElement.countTypes(): Sequence = sequence { yieldAll(it.countTypes()) } } - else -> {} + else -> { + } } } fun main() { val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!! .readAllBytes().decodeToString() - val json = Json.parseToJsonElement(string) - val sizes = json.countTypes().groupBy { it }.mapValues { it.value.size } - sizes.forEach { - println(it) - } - val time = measureTimeMillis { - val geo = TObject.decodeFromString(TGeoManager.serializer(), string) + val geo = TGeoManagerScheme.parse(string) val solid = geo.toSolid() println(Solids.encodeToString(solid)) } +// val json = Json.parseToJsonElement(string) +// val sizes = json.countTypes().groupBy { it }.mapValues { it.value.size } +// sizes.forEach { +// println(it) +// } +// +// val time = measureTimeMillis { +// val geo = TObject.decodeFromString(TGeoManager.serializer(), string) +// val solid = geo.toSolid() +// +// println(Solids.encodeToString(solid)) +// } +// println(Duration.ofMillis(time)) } \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 5bce5b02..0ed8858a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,5 @@ kotlin.code.style=official -kotlin.mpp.enableGranularSourceSetsMetadata=true kotlin.mpp.stability.nowarn=true -kotlin.native.enableDependencyPropagation=false #kotlin.jupyter.add.scanner=false