QuiltMC / quilt-kotlin-libraries

Quilt's official Kotlin libraries, which provide wrappers for QSL and Minecraft to ease development in Kotlin
Apache License 2.0
52 stars 18 forks source link

[Question] How to implement `KeyDispatchCodec` like registry codec with codec factory? #85

Open SettingDust opened 10 months ago

SettingDust commented 10 months ago

I will implement a KeyDispatchCodec for my custom registry with the Kotlin library codec factory. How can I make the codec factory handle the polymorphic and get codec from the registry?

For example: BuiltInRegistries.RECIPE_SERIALIZER.byNameCodec().dispatch(Recipe::getSerializer, RecipeSerializer::codec)

SettingDust commented 10 months ago

Achieve with below. Open for advice

@OptIn(InternalSerializationApi::class, ExperimentalSerializationApi::class)
open class DispatchedCodecSerializer<Out : Any, Type>(
    val targetCodec: Codec<Type>,
    val type: Function<Out, Type>,
    val codec: Function<Type, Codec<Out>>,
    private val serialName: String
) : KSerializer<Out> {
    override val descriptor =
        buildSerialDescriptor(serialName, PolymorphicKind.OPEN) {
            element("type", String.serializer().descriptor)
            element(
                "value",
                buildSerialDescriptor(
                    "DispatchedCodecSerializer<$serialName>",
                    SerialKind.CONTEXTUAL
                )
            )
        }

    override fun deserialize(decoder: Decoder): Out {
        if (decoder !is DynamicDecoder<*>) {
            throw UnsupportedOperationException(
                "Codec serializers can only be used in Dynamic serialization"
            )
        }

        val compositeDecoder = decoder.beginStructure(descriptor)

        var currentType: Type
        var elementCodec: Codec<Out>? = null
        var out: Out? = null
        while (true) {
            when (val index = compositeDecoder.decodeElementIndex(descriptor)) {
                0 -> {
                    currentType =
                        compositeDecoder.decodeSerializableElement(
                            descriptor,
                            0,
                            CodecSerializerAdapter(targetCodec, serialName)
                        )
                    elementCodec = codec.apply(currentType)
                }
                1 ->
                    out =
                        compositeDecoder.decodeSerializableElement(
                            descriptor,
                            1,
                            CodecSerializerAdapter(elementCodec!!, serialName)
                        )
                CompositeDecoder.DECODE_DONE -> break
                else -> error("Unexpected index: $index")
            }
        }

        decoder.endStructure(descriptor)
        return out!!
    }

    override fun serialize(encoder: Encoder, value: Out) {
        if (encoder !is DynamicEncoder<*>) {
            throw UnsupportedOperationException(
                "Codec serializers can only be used in Dynamic serialization"
            )
        }

        val compositeEncoder = encoder.beginStructure(descriptor)

        val currentType = type.apply(value)
        compositeEncoder.encodeSerializableElement(
            descriptor,
            0,
            CodecSerializerAdapter(targetCodec, serialName),
            currentType
        )
        val elementCodec = codec.apply(currentType)
        compositeEncoder.encodeSerializableElement(
            descriptor,
            1,
            CodecSerializerAdapter(elementCodec, serialName),
            value
        )

        encoder.endStructure(descriptor)
    }
}