From 3e4d6dcc1155f9b3431b9a7dfd44690b0147fd4b Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Mon, 6 Mar 2023 14:47:42 +0700 Subject: [PATCH] Revisit enumvaluecodec --- .../mc/otm/block/entity/RedstoneControl.kt | 2 +- .../mc/otm/core/util/DataStreams.kt | 66 +++++++++++++++---- .../ru/dbotthepony/mc/otm/data/EnumCodec.kt | 30 --------- .../otm/menu/input/EnumInputWithFeedback.kt | 4 +- .../mc/otm/menu/matter/MatterPanelMenu.kt | 5 +- .../mc/otm/network/FieldSynchronizer.kt | 27 +++++--- 6 files changed, 76 insertions(+), 58 deletions(-) delete mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt index 4650d6263..8b3a74510 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/RedstoneControl.kt @@ -77,7 +77,7 @@ class SynchronizedRedstoneControl( val state = isBlockedByRedstone valueChanges.invoke(state, old) } - }, name = if (fieldNamePrefix != null) "${fieldNamePrefix}_redstoneSetting" else null, writeByIndices = true) + }, name = if (fieldNamePrefix != null) "${fieldNamePrefix}_redstoneSetting" else null) override var redstoneSignal: Int by synchronizer.int(0, setter = { value, access, setByRemote -> if (access.read() == value) return@int diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt index b4ea5972d..6916c5589 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/DataStreams.kt @@ -1,7 +1,14 @@ package ru.dbotthepony.mc.otm.core.util +import com.google.common.collect.ImmutableList +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps import net.minecraft.nbt.NbtAccounter +import net.minecraft.util.StringRepresentable import net.minecraft.world.item.ItemStack +import ru.dbotthepony.mc.otm.core.immutableMap import ru.dbotthepony.mc.otm.core.math.readDecimal import ru.dbotthepony.mc.otm.core.math.writeDecimal import java.io.DataInput @@ -14,6 +21,7 @@ import java.util.* import java.util.function.Predicate import kotlin.NoSuchElementException import kotlin.math.absoluteValue +import kotlin.reflect.KClass /** * Represents value which can be encoded onto or decoded from stream. @@ -86,25 +94,20 @@ val VarIntValueCodec = StreamCodec(DataInputStream::readVarIntLE, DataOutputStre val VarLongValueCodec = StreamCodec(DataInputStream::readVarLongLE, DataOutputStream::writeVarLongLE) val BinaryStringCodec = StreamCodec(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString) -class EnumValueCodec>(clazz: Class, val writeByIndices: Boolean = false) : IStreamCodec { +class EnumValueCodec> private constructor(clazz: Class) : IStreamCodec, Codec { val clazz = searchClass(clazz) - private val values = searchClass(clazz).enumConstants!! + val values: ImmutableList = ImmutableList.copyOf(this.clazz.enumConstants!!) + val valuesMap = immutableMap { + for (v in values) put(v.name, v) + } override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V { - if (writeByIndices) { - val id = stream.readVarIntLE(sizeLimit) - return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id") - } - - val id = stream.readBinaryString(sizeLimit) - return values.firstOrNull { id == it.name } ?: throw NoSuchElementException("No such enum $id") + val id = stream.readVarIntLE(sizeLimit) + return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id") } override fun write(stream: DataOutputStream, value: V) { - if (writeByIndices) - stream.writeVarIntLE(value.ordinal) - else - stream.writeBinaryString(value.name) + stream.writeVarIntLE(value.ordinal) } override fun copy(value: V): V { @@ -115,7 +118,41 @@ class EnumValueCodec>(clazz: Class, val writeByIndices: Boole return a === b } + override fun encode(input: V, ops: DynamicOps, prefix: T): DataResult { + if (ops.compressMaps()) { + return DataResult.success(ops.createInt(input.ordinal)) + } + + return DataResult.success(ops.createString(input.name)) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + if (ops.compressMaps()) { + return ops.getNumberValue(input) + .flatMap { values.getOrNull(it.toInt())?.let { DataResult.success(Pair(it, ops.empty())) } ?: DataResult.error("No such enum with ordinal index $it") } + } + + return ops.getStringValue(input) + .flatMap { valuesMap[it]?.let { DataResult.success(Pair(it, ops.empty())) } ?: DataResult.error("No such enum value $it") } + } + companion object { + private val codecs = WeakHashMap, EnumValueCodec<*>>() + + fun > of(clazz: Class): EnumValueCodec { + synchronized(codecs) { + val search = searchClass(clazz) + + if (search !== clazz) { + val result = codecs.computeIfAbsent(search) { EnumValueCodec(it as Class) } as EnumValueCodec + codecs.putIfAbsent(clazz, result) + return result + } + + return codecs.computeIfAbsent(clazz) { EnumValueCodec(it as Class) } as EnumValueCodec + } + } + /** * FIXME: enums with abstract methods which get compiled to subclasses, whose DO NOT expose "parent's" enum constants array * @@ -137,6 +174,9 @@ class EnumValueCodec>(clazz: Class, val writeByIndices: Boole } } +fun > Class.codec() = EnumValueCodec.of(this) +fun > KClass.codec() = EnumValueCodec.of(this.java) + fun OutputStream.writeInt(value: Int) { if (this is DataOutput) { writeInt(value) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt deleted file mode 100644 index e760a5a69..000000000 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/EnumCodec.kt +++ /dev/null @@ -1,30 +0,0 @@ -package ru.dbotthepony.mc.otm.data - -import com.google.common.collect.ImmutableMap -import com.mojang.datafixers.util.Pair -import com.mojang.serialization.Codec -import com.mojang.serialization.DataResult -import com.mojang.serialization.DynamicOps -import kotlin.reflect.KClass - -class EnumCodec>(val type: Class) : Codec { - private val values: Map by lazy { - val builder = ImmutableMap.Builder() - for (value in type.enumConstants) builder.put(value.name, value) - builder.build() - } - - override fun encode(input: E, ops: DynamicOps, prefix: T): DataResult { - require(prefix == ops.empty()) { "Non-empty prefix: $prefix" } - return DataResult.success(ops.createString(input.name)) - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - return ops.getStringValue(input).flatMap { - DataResult.success(values[it] ?: return@flatMap DataResult.error("No such enum $it (valid ones are: ${values.keys.joinToString(", ")})")) - }.map { Pair.of(it, ops.empty()) } - } -} - -fun > Class.codec() = EnumCodec(this) -fun > KClass.codec() = EnumCodec(this.java) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt index fe9dfabfd..d066f388c 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/input/EnumInputWithFeedback.kt @@ -10,8 +10,8 @@ inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: inline fun > EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter) = EnumInputWithFeedback(menu, E::class.java, state) class EnumInputWithFeedback>(menu: MatteryMenu, clazz: Class) : AbstractPlayerInputWithFeedback() { - val codec = EnumValueCodec(clazz) - private val default = codec.clazz.enumConstants!![0] + val codec = EnumValueCodec.of(clazz) + private val default = codec.values.first() override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) } override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt index f04e9f742..1fc56d8d8 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/menu/matter/MatterPanelMenu.kt @@ -15,6 +15,7 @@ import ru.dbotthepony.mc.otm.core.addSorted import ru.dbotthepony.mc.otm.core.util.BooleanValueCodec import ru.dbotthepony.mc.otm.core.util.EnumValueCodec import ru.dbotthepony.mc.otm.core.util.ItemSorter +import ru.dbotthepony.mc.otm.core.util.codec import ru.dbotthepony.mc.otm.graph.matter.IMatterGraphListener import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph import ru.dbotthepony.mc.otm.menu.MatteryMenu @@ -153,7 +154,7 @@ class MatterPanelMenu @JvmOverloads constructor( val sorting: ItemSorter by mSynchronizer.ComputedField( getter = { tile?.getPlayerSettings(ply)?.sorter ?: ItemSorter.DEFAULT }, - codec = EnumValueCodec(ItemSorter::class.java), + codec = ItemSorter::class.codec(), observer = { patterns.sortWith(actualComparator) tasks.sortWith(actualTaskComparator) @@ -168,7 +169,7 @@ class MatterPanelMenu @JvmOverloads constructor( }) val changeIsAscending = booleanInput(allowSpectators = true) { tile?.getPlayerSettings(ply)?.ascending = it } - val changeSorting = PlayerInput(EnumValueCodec(ItemSorter::class.java), allowSpectators = true) { tile?.getPlayerSettings(ply)?.sorter = it } + val changeSorting = PlayerInput(ItemSorter::class.codec(), allowSpectators = true) { tile?.getPlayerSettings(ply)?.sorter = it } val sortingGS = GetterSetter.of(::sorting, changeSorting::input) val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt index 3b250b558..50aa0ff4e 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/network/FieldSynchronizer.kt @@ -202,14 +202,14 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa @JvmOverloads fun item(getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, ItemStackValueCodec, name ?: nextFieldName()) @JvmOverloads fun string(getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, BinaryStringCodec, name ?: nextFieldName()) - @JvmOverloads fun > enum(type: Class, getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name ?: nextFieldName()) - inline fun > enum(noinline getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name ?: nextFieldName()) + @JvmOverloads fun > enum(type: Class, getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(type), name ?: nextFieldName()) + inline fun > enum(noinline getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(T::class.java), name ?: nextFieldName()) - @JvmOverloads fun > enum(type: Class, getter: KProperty0, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name ?: nextFieldName()) - inline fun > enum(getter: KProperty0, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name ?: nextFieldName()) + @JvmOverloads fun > enum(type: Class, getter: KProperty0, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(type), name ?: nextFieldName()) + inline fun > enum(getter: KProperty0, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(T::class.java), name ?: nextFieldName()) - @JvmOverloads fun > enum(type: Class, getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(type), name ?: nextFieldName()) - inline fun > enum(getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(T::class.java), name ?: nextFieldName()) + @JvmOverloads fun > enum(type: Class, getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec.of(type), name ?: nextFieldName()) + inline fun > enum(getter: Supplier, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec.of(T::class.java), name ?: nextFieldName()) @JvmOverloads fun byte( @@ -347,10 +347,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa value: T = type.enumConstants[0], getter: FieldGetter? = null, setter: FieldSetter? = null, - writeByIndices: Boolean = false, name: String? = nextFieldName(), ): Field { - return Field(value, EnumValueCodec(type, writeByIndices = writeByIndices), getter, setter, name = name ?: nextFieldName()) + return Field(value, EnumValueCodec.of(type), getter, setter, name = name ?: nextFieldName()) } @JvmOverloads @@ -358,10 +357,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa value: T, getter: FieldGetter? = null, setter: FieldSetter? = null, - writeByIndices: Boolean = false, name: String? = nextFieldName(), ): Field { - return Field(value, EnumValueCodec(value::class.java, writeByIndices = writeByIndices), getter, setter, name = name ?: nextFieldName()) + return Field(value, EnumValueCodec.of(value::class.java), getter, setter, name = name ?: nextFieldName()) } @JvmOverloads @@ -584,6 +582,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } } + /** + * Networked variable with backing field holding immutable value + */ inner class Field( private var field: V, private val codec: IStreamCodec, @@ -684,6 +685,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } } + /** + * Networked value with backing getter which is constantly polled + */ inner class ComputedField( private val getter: () -> V, private val codec: IStreamCodec, @@ -733,6 +737,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa } } + /** + * Networked variable with backing field holding mutable value, which is constantly observed for changes + */ inner class ObservedField : AbstractField, IMutableField { private val codec: IStreamCodec private val getter: () -> V