dynamic root scheme parser

This commit is contained in:
Alexander Nozik 2021-08-30 12:58:41 +03:00
parent 68704086e9
commit 7b5faaa61e
16 changed files with 641 additions and 305 deletions

View File

@ -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()
}

View File

@ -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<Meta>
public interface ObjectRef<T : TObjectScheme> {
public fun resolve(refCache: RefCache): T?
}
private class ChildObjectRef<T : TObjectScheme>(
val spec: Specification<T>,
val metaProvider: () -> Meta?
) : ObjectRef<T> {
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 <T: TObjectScheme> List<ObjectRef<T>>.resolve(refCache: RefCache): List<T> = 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 <T : TObjectScheme> tObjectArray(
spec: Specification<T>
): ReadOnlyProperty<Any?, List<ObjectRef<T>>> = ReadOnlyProperty { _, property ->
meta.getIndexed(Name.of(property.name, "arr")).values.map { ChildObjectRef(spec){it} }
}
internal fun <T : TObjectScheme> refSpec(
spec: Specification<T>,
key: Name? = null
): ReadOnlyProperty<Any?, ObjectRef<T>> = ReadOnlyProperty { _, property ->
ChildObjectRef(spec) { meta[key ?: property.name.asName()] }
}
public companion object : SchemeSpec<TObjectScheme>(::TObjectScheme)
}
public open class TNamedScheme : TObjectScheme() {
public val fName: String by string("")
public val fTitle: String by string("")
public companion object : SchemeSpec<TNamedScheme>(::TNamedScheme)
}
public class TGeoMaterialScheme : TNamedScheme() {
public companion object : SchemeSpec<TGeoMaterialScheme>(::TGeoMaterialScheme)
}
public class TGeoMediumScheme : TNamedScheme() {
public val fMaterial: ObjectRef<TGeoMaterialScheme> by refSpec(TGeoMaterialScheme)
public val fParams: DoubleArray by doubleArray()
public companion object : SchemeSpec<TGeoMediumScheme>(::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>(::TGeoShapeScheme)
}
public class TGeoVolumeScheme : TNamedScheme() {
public val fNodes: List<ObjectRef<TGeoNodeScheme>> by tObjectArray(TGeoNodeScheme)
public val fShape: ObjectRef<TGeoShapeScheme> by refSpec(TGeoShapeScheme)
public val fMedium: ObjectRef<TGeoMediumScheme> by refSpec(TGeoMediumScheme)
public companion object : SchemeSpec<TGeoVolumeScheme>(::TGeoVolumeScheme)
}
public class TGeoNodeScheme : TNamedScheme() {
public val fVolume: ObjectRef<TGeoVolumeScheme> by refSpec(TGeoVolumeScheme)
public companion object : SchemeSpec<TGeoNodeScheme>(::TGeoNodeScheme)
}
public class TGeoMatrixScheme : TNamedScheme() {
public companion object : SchemeSpec<TGeoMatrixScheme>(::TGeoMatrixScheme)
}
public class TGeoBoolNodeScheme : TObjectScheme() {
public val fLeft: ObjectRef<TGeoShapeScheme> by refSpec(TGeoShapeScheme)
public val fLeftMat: ObjectRef<TGeoMatrixScheme> by refSpec(TGeoMatrixScheme)
public val fRight: ObjectRef<TGeoShapeScheme> by refSpec(TGeoShapeScheme)
public val fRightMat: ObjectRef<TGeoMatrixScheme> by refSpec(TGeoMatrixScheme)
public companion object : SchemeSpec<TGeoBoolNodeScheme>(::TGeoBoolNodeScheme)
}
public class TGeoManagerScheme : TNamedScheme() {
public val fMatrices: List<ObjectRef<TGeoMatrixScheme>> by tObjectArray(TGeoMatrixScheme)
public val fShapes: List<ObjectRef<TGeoShapeScheme>> by tObjectArray(TGeoShapeScheme)
public val fVolumes: List<ObjectRef<TGeoVolumeScheme>> by tObjectArray(TGeoVolumeScheme)
public val fNodes: List<ObjectRef<TGeoNodeScheme>> by tObjectArray(TGeoNodeScheme)
public val refCache: List<Meta> by lazy {
val res = ArrayList<Meta>(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>(::TGeoManagerScheme) {
public fun parse(string: String): TGeoManagerScheme {
val meta = Json.decodeFromString(MetaSerializer, string)
return read(meta)
}
}
}

View File

@ -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 <T> jsonRootDeserializer(tSerializer: KSerializer<T>, builder: (JsonElement) -> T): DeserializationStrategy<T> = object :
DeserializationStrategy<T> {
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 <T: TObject> TObject.Companion.decodeFromJson(serializer: KSerializer<T>, jsonElement: JsonElement): T =
RootDecoder.decode(serializer, jsonElement)
public fun <T: TObject> TObject.Companion.decodeFromString(serializer: KSerializer<T>, string: String): T {
val json = RootDecoder.json.parseToJsonElement(string)
return RootDecoder.decode(serializer, json)
}
private object RootDecoder {
private class RootUnrefSerializer<T>(
private val tSerializer: KSerializer<T>,
private val refCache: List<RefEntry>,
) : KSerializer<T> 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 <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> =
RootUnrefSerializer(this, refCache)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule(
refCache: List<RefEntry>
): 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<RefEntry>): Json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
classDiscriminator = "_typename"
serializersModule = unrefSerializersModule(refCache)
}
fun <T: TObject> decode(sourceDeserializer: KSerializer<T>, source: JsonElement): T {
val refCache = ArrayList<RefEntry>()
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 <T> getOrPutValue(builder: (JsonElement) -> T): T {
if (value == null) {
value = builder(element)
}
return value as T
}
override fun toString(): String = element.toString()
}
private fun PolymorphicModuleBuilder<TGeoShape>.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<TGeoMatrix>.matrices() {
subclass(TGeoIdentity.serializer())
subclass(TGeoHMatrix.serializer())
subclass(TGeoTranslation.serializer())
subclass(TGeoRotation.serializer())
subclass(TGeoCombiTrans.serializer())
}
private fun PolymorphicModuleBuilder<TGeoBoolNode>.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
}
}

View File

@ -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 TaitBryan 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)
}
}

View File

@ -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<TGeoMatrix> = TObjArray.getEmpty()
public val fShapes: TObjArray<TGeoShape> = TObjArray.getEmpty()
public val fVolumes: TObjArray<TGeoVolume> = TObjArray.getEmpty()
public val fNodes: TObjArray<TGeoNode> = TObjArray.getEmpty()
}

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
@Serializable @Serializable
@SerialName("TGeoNode") @SerialName("TGeoNode")
public sealed class TGeoNode : TNamed() { public open class TGeoNode : TNamed() {
public val fGeoAtt: UInt = 0u public val fGeoAtt: UInt = 0u
@Contextual @Contextual

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName
@ -15,7 +15,7 @@ public open class TGeoVolume : TNamed() {
public val fFillStyle: Int? = null public val fFillStyle: Int? = null
@Contextual @Contextual
public val fNodes: TObjArray<TGeoNode>? = null public val fNodes: TObjArray<@Contextual TGeoNode>? = null
@Contextual @Contextual
public val fShape: TGeoShape? = null public val fShape: TGeoShape? = null

View File

@ -1,4 +1,4 @@
package ru.mipt.npm.root package ru.mipt.npm.root.serialization
import kotlinx.serialization.Contextual import kotlinx.serialization.Contextual
import kotlinx.serialization.SerialName import kotlinx.serialization.SerialName

View File

@ -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 <T> jsonRootDeserializer(
tSerializer: KSerializer<T>,
builder: (JsonElement) -> T
): DeserializationStrategy<T> = object :
DeserializationStrategy<T> {
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 <T : TObject> TObject.decodeFromJson(serializer: KSerializer<T>, jsonElement: JsonElement): T =
RootDecoder.decode(serializer, jsonElement)
public fun <T : TObject> TObject.decodeFromString(serializer: KSerializer<T>, string: String): T {
val json = Json.parseToJsonElement(string)
return RootDecoder.decode(serializer, json)
}
private object RootDecoder {
private class RootUnrefSerializer<T>(
private val tSerializer: KSerializer<T>,
private val refCache: List<RefEntry>,
) : KSerializer<T> 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 <T> KSerializer<T>.unref(refCache: List<RefEntry>): KSerializer<T> = RootUnrefSerializer(this, refCache)
@OptIn(ExperimentalSerializationApi::class)
fun unrefSerializersModule(
refCache: List<RefEntry>
): 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<RefEntry>): Json = Json {
encodeDefaults = true
ignoreUnknownKeys = true
classDiscriminator = "_typename"
serializersModule = unrefSerializersModule(refCache)
}
fun <T : TObject> decode(sourceDeserializer: KSerializer<T>, source: JsonElement): T {
val refCache = ArrayList<RefEntry>()
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 <T> 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
// }
}

View File

@ -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.Name
import space.kscience.dataforge.names.asName import space.kscience.dataforge.names.asName

View File

@ -1,6 +1,10 @@
package ru.mipt.npm.root 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 space.kscience.visionforge.solid.Solids
import java.time.Duration import java.time.Duration
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -16,25 +20,33 @@ private fun JsonElement.countTypes(): Sequence<String> = sequence {
yieldAll(it.countTypes()) yieldAll(it.countTypes())
} }
} }
else -> {} else -> {
}
} }
} }
fun main() { fun main() {
val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!! val string = TGeoManager::class.java.getResourceAsStream("/BM@N.root.json")!!
.readAllBytes().decodeToString() .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 time = measureTimeMillis {
val geo = TObject.decodeFromString(TGeoManager.serializer(), string) val geo = TGeoManagerScheme.parse(string)
val solid = geo.toSolid() val solid = geo.toSolid()
println(Solids.encodeToString(solid)) 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)) println(Duration.ofMillis(time))
} }

View File

@ -1,7 +1,5 @@
kotlin.code.style=official kotlin.code.style=official
kotlin.mpp.enableGranularSourceSetsMetadata=true
kotlin.mpp.stability.nowarn=true kotlin.mpp.stability.nowarn=true
kotlin.native.enableDependencyPropagation=false
#kotlin.jupyter.add.scanner=false #kotlin.jupyter.add.scanner=false