diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt index 8999c61c5..9dd407898 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Setting.kt @@ -11,15 +11,20 @@ package net.mamoe.mirai.console.setting -import kotlinx.serialization.KSerializer +import kotlinx.serialization.* +import kotlinx.serialization.builtins.ListSerializer +import kotlinx.serialization.builtins.MapEntrySerializer +import kotlinx.serialization.builtins.serializer import net.mamoe.mirai.console.setting.internal.cast import net.mamoe.mirai.console.setting.internal.valueFromKTypeImpl import net.mamoe.mirai.console.setting.internal.valueImpl import net.mamoe.mirai.utils.MiraiExperimentalAPI +import net.mamoe.yamlkt.Yaml import java.util.* import kotlin.internal.LowPriorityInOverloadResolution import kotlin.reflect.KProperty import kotlin.reflect.KType +import kotlin.reflect.full.findAnnotation import kotlin.reflect.typeOf @@ -32,11 +37,15 @@ abstract class Setting : SettingImpl() { property: KProperty<*> ): SerializerAwareValue { @Suppress("UNCHECKED_CAST") - valueNodes.add(Node(property as KProperty, this, this.serializer)) + valueNodes.add(Node(property.serialName, this, this.serializer)) return this } + + public override val updaterSerializer: KSerializer get() = super.updaterSerializer } +internal val KProperty<*>.serialName: String get() = this.findAnnotation()?.value ?: this.name + /** * Internal implementation for [Setting] including: * - Reflection on Kotlin properties and Java fields @@ -44,14 +53,91 @@ abstract class Setting : SettingImpl() { */ // TODO move to internal package. internal abstract class SettingImpl { + internal fun findNodeInstance(name: String): Node<*>? = valueNodes.firstOrNull { it.serialName == name } + internal class Node( - val property: KProperty, + val serialName: String, val value: Value, val updaterSerializer: KSerializer ) + /** + * Serializing like a [Map.Entry] but with higher performance + */ + internal inner class NodeSerializer : KSerializer?> { + override val descriptor: SerialDescriptor + get() = MapEntrySerializer(String.serializer(), String.serializer()).descriptor + + private val keySerializer = String.serializer() + private val valueSerializer = String.serializer() + + override fun deserialize(decoder: Decoder): Node<*>? = + with(decoder.beginStructure(descriptor, keySerializer, valueSerializer)) { + if (decodeSequentially()) { + val name = decodeStringElement(descriptor, 0) + val value = decodeStringElement(descriptor, 1) + + val node = findNodeInstance(name) ?: return@with null + Yaml.nonStrict.parse(node.updaterSerializer, value) + return@with node + } + + var name: String? = null + var value: String? = null + + loop@ while (true) { + when (decodeElementIndex(descriptor)) { + 0 -> name = decodeStringElement(descriptor, 0) + 1 -> value = decodeStringElement(descriptor, 1) + CompositeDecoder.READ_DONE -> break@loop + } + } + + requireNotNull(name) { throw MissingFieldException("name") } + requireNotNull(value) { throw MissingFieldException("value") } + + endStructure(descriptor) + + val node = findNodeInstance(name) ?: return@with null + Yaml.nonStrict.parse(node.updaterSerializer, value) + return@with node + } + + override fun serialize(encoder: Encoder, value: Node<*>?) { + if (value == null) { + encoder.encodeNull() + } else { + val structuredEncoder = encoder.beginStructure(descriptor, keySerializer, valueSerializer) + structuredEncoder.encodeStringElement(descriptor, 0, value.serialName) + structuredEncoder.encodeStringElement( + descriptor, 1, + Yaml.nonStrict.stringify(value.updaterSerializer, Unit) + ) + structuredEncoder.endStructure(descriptor) + } + } + } + internal val valueNodes: MutableList> = Collections.synchronizedList(mutableListOf()) + internal open val updaterSerializer: KSerializer by lazy { + val actual = ListSerializer(NodeSerializer()) + + object : KSerializer { + override val descriptor: SerialDescriptor + get() = actual.descriptor + + override fun deserialize(decoder: Decoder) { + actual.deserialize(decoder) + } + + override fun serialize(encoder: Encoder, value: Unit) { + actual.serialize(encoder, valueNodes) + } + + } + } + /** * flatten */ diff --git a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt index 7a3d7b6e4..241cd2060 100644 --- a/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt +++ b/backend/mirai-console/src/main/kotlin/net/mamoe/mirai/console/setting/Value.kt @@ -11,7 +11,9 @@ package net.mamoe.mirai.console.setting +import kotlinx.serialization.BinaryFormat import kotlinx.serialization.KSerializer +import kotlinx.serialization.StringFormat import net.mamoe.mirai.console.setting.internal.map import net.mamoe.mirai.utils.MiraiExperimentalAPI import kotlin.reflect.KProperty @@ -54,6 +56,14 @@ interface SerializerAwareValue : Value { val serializer: KSerializer } +fun SerializerAwareValue.serialize(format: StringFormat): String { + return format.stringify(this.serializer, Unit) +} + +fun SerializerAwareValue.serialize(format: BinaryFormat): ByteArray { + return format.dump(this.serializer, Unit) +} + inline operator fun Value.getValue(mySetting: Any?, property: KProperty<*>): T = value inline operator fun Value.setValue(mySetting: Any?, property: KProperty<*>, value: T) { this.value = value