Revisit enumvaluecodec

This commit is contained in:
DBotThePony 2023-03-06 14:47:42 +07:00
parent b8837d4312
commit 3e4d6dcc11
Signed by: DBot
GPG Key ID: DCC23B5715498507
6 changed files with 76 additions and 58 deletions

View File

@ -77,7 +77,7 @@ class SynchronizedRedstoneControl(
val state = isBlockedByRedstone val state = isBlockedByRedstone
valueChanges.invoke(state, old) 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 -> override var redstoneSignal: Int by synchronizer.int(0, setter = { value, access, setByRemote ->
if (access.read() == value) return@int if (access.read() == value) return@int

View File

@ -1,7 +1,14 @@
package ru.dbotthepony.mc.otm.core.util 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.nbt.NbtAccounter
import net.minecraft.util.StringRepresentable
import net.minecraft.world.item.ItemStack 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.readDecimal
import ru.dbotthepony.mc.otm.core.math.writeDecimal import ru.dbotthepony.mc.otm.core.math.writeDecimal
import java.io.DataInput import java.io.DataInput
@ -14,6 +21,7 @@ import java.util.*
import java.util.function.Predicate import java.util.function.Predicate
import kotlin.NoSuchElementException import kotlin.NoSuchElementException
import kotlin.math.absoluteValue import kotlin.math.absoluteValue
import kotlin.reflect.KClass
/** /**
* Represents value which can be encoded onto or decoded from stream. * 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 VarLongValueCodec = StreamCodec(DataInputStream::readVarLongLE, DataOutputStream::writeVarLongLE)
val BinaryStringCodec = StreamCodec(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString) val BinaryStringCodec = StreamCodec(DataInputStream::readBinaryString, DataOutputStream::writeBinaryString)
class EnumValueCodec<V : Enum<V>>(clazz: Class<out V>, val writeByIndices: Boolean = false) : IStreamCodec<V> { class EnumValueCodec<V : Enum<V>> private constructor(clazz: Class<out V>) : IStreamCodec<V>, Codec<V> {
val clazz = searchClass(clazz) val clazz = searchClass(clazz)
private val values = searchClass(clazz).enumConstants!! val values: ImmutableList<V> = ImmutableList.copyOf(this.clazz.enumConstants!!)
val valuesMap = immutableMap {
for (v in values) put(v.name, v)
}
override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V { override fun read(stream: DataInputStream, sizeLimit: NbtAccounter): V {
if (writeByIndices) { val id = stream.readVarIntLE(sizeLimit)
val id = stream.readVarIntLE(sizeLimit) return values.getOrNull(id) ?: throw NoSuchElementException("No such enum with index $id")
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")
} }
override fun write(stream: DataOutputStream, value: V) { override fun write(stream: DataOutputStream, value: V) {
if (writeByIndices) stream.writeVarIntLE(value.ordinal)
stream.writeVarIntLE(value.ordinal)
else
stream.writeBinaryString(value.name)
} }
override fun copy(value: V): V { override fun copy(value: V): V {
@ -115,7 +118,41 @@ class EnumValueCodec<V : Enum<V>>(clazz: Class<out V>, val writeByIndices: Boole
return a === b return a === b
} }
override fun <T : Any> encode(input: V, ops: DynamicOps<T>, prefix: T): DataResult<T> {
if (ops.compressMaps()) {
return DataResult.success(ops.createInt(input.ordinal))
}
return DataResult.success(ops.createString(input.name))
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<V, T>> {
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 { companion object {
private val codecs = WeakHashMap<Class<*>, EnumValueCodec<*>>()
fun <T : Enum<T>> of(clazz: Class<out T>): EnumValueCodec<T> {
synchronized(codecs) {
val search = searchClass(clazz)
if (search !== clazz) {
val result = codecs.computeIfAbsent(search) { EnumValueCodec(it as Class<T>) } as EnumValueCodec<T>
codecs.putIfAbsent(clazz, result)
return result
}
return codecs.computeIfAbsent(clazz) { EnumValueCodec(it as Class<T>) } as EnumValueCodec<T>
}
}
/** /**
* FIXME: enums with abstract methods which get compiled to subclasses, whose DO NOT expose "parent's" enum constants array * 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<V : Enum<V>>(clazz: Class<out V>, val writeByIndices: Boole
} }
} }
fun <E : Enum<E>> Class<E>.codec() = EnumValueCodec.of(this)
fun <E : Enum<E>> KClass<E>.codec() = EnumValueCodec.of(this.java)
fun OutputStream.writeInt(value: Int) { fun OutputStream.writeInt(value: Int) {
if (this is DataOutput) { if (this is DataOutput) {
writeInt(value) writeInt(value)

View File

@ -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<E : Enum<E>>(val type: Class<E>) : Codec<E> {
private val values: Map<String, E> by lazy {
val builder = ImmutableMap.Builder<String, E>()
for (value in type.enumConstants) builder.put(value.name, value)
builder.build()
}
override fun <T : Any> encode(input: E, ops: DynamicOps<T>, prefix: T): DataResult<T> {
require(prefix == ops.empty()) { "Non-empty prefix: $prefix" }
return DataResult.success(ops.createString(input.name))
}
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<E, T>> {
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 <E : Enum<E>> Class<E>.codec() = EnumCodec(this)
fun <E : Enum<E>> KClass<E>.codec() = EnumCodec(this.java)

View File

@ -10,8 +10,8 @@ inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state:
inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter<E>) = EnumInputWithFeedback(menu, E::class.java, state) inline fun <reified E : Enum<E>> EnumInputWithFeedback(menu: MatteryMenu, state: GetterSetter<E>) = EnumInputWithFeedback(menu, E::class.java, state)
class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>) : AbstractPlayerInputWithFeedback<E>() { class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>) : AbstractPlayerInputWithFeedback<E>() {
val codec = EnumValueCodec(clazz) val codec = EnumValueCodec.of(clazz)
private val default = codec.clazz.enumConstants!![0] private val default = codec.values.first()
override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) } override val input = menu.PlayerInput(codec, false) { consumer?.invoke(it) }
override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec) override val value by menu.mSynchronizer.ComputedField(getter = { supplier?.invoke() ?: default }, codec)

View File

@ -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.BooleanValueCodec
import ru.dbotthepony.mc.otm.core.util.EnumValueCodec import ru.dbotthepony.mc.otm.core.util.EnumValueCodec
import ru.dbotthepony.mc.otm.core.util.ItemSorter 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.IMatterGraphListener
import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph import ru.dbotthepony.mc.otm.graph.matter.MatterNetworkGraph
import ru.dbotthepony.mc.otm.menu.MatteryMenu import ru.dbotthepony.mc.otm.menu.MatteryMenu
@ -153,7 +154,7 @@ class MatterPanelMenu @JvmOverloads constructor(
val sorting: ItemSorter by mSynchronizer.ComputedField( val sorting: ItemSorter by mSynchronizer.ComputedField(
getter = { tile?.getPlayerSettings(ply)?.sorter ?: ItemSorter.DEFAULT }, getter = { tile?.getPlayerSettings(ply)?.sorter ?: ItemSorter.DEFAULT },
codec = EnumValueCodec(ItemSorter::class.java), codec = ItemSorter::class.codec(),
observer = { observer = {
patterns.sortWith(actualComparator) patterns.sortWith(actualComparator)
tasks.sortWith(actualTaskComparator) tasks.sortWith(actualTaskComparator)
@ -168,7 +169,7 @@ class MatterPanelMenu @JvmOverloads constructor(
}) })
val changeIsAscending = booleanInput(allowSpectators = true) { tile?.getPlayerSettings(ply)?.ascending = it } 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 sortingGS = GetterSetter.of(::sorting, changeSorting::input)
val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input) val isAscendingGS = GetterSetter.of(::isAscending, changeIsAscending::input)

View File

@ -202,14 +202,14 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
@JvmOverloads fun item(getter: Supplier<ItemStack>, name: String? = nextFieldName()) = ComputedField(getter::get, ItemStackValueCodec, name ?: nextFieldName()) @JvmOverloads fun item(getter: Supplier<ItemStack>, name: String? = nextFieldName()) = ComputedField(getter::get, ItemStackValueCodec, name ?: nextFieldName())
@JvmOverloads fun string(getter: Supplier<String>, name: String? = nextFieldName()) = ComputedField(getter::get, BinaryStringCodec, name ?: nextFieldName()) @JvmOverloads fun string(getter: Supplier<String>, name: String? = nextFieldName()) = ComputedField(getter::get, BinaryStringCodec, name ?: nextFieldName())
@JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name ?: nextFieldName()) @JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(type), name ?: nextFieldName())
inline fun <reified T : Enum<T>> enum(noinline getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name ?: nextFieldName()) inline fun <reified T : Enum<T>> enum(noinline getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(T::class.java), name ?: nextFieldName())
@JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: KProperty0<T>, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(type), name ?: nextFieldName()) @JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: KProperty0<T>, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(type), name ?: nextFieldName())
inline fun <reified T : Enum<T>> enum(getter: KProperty0<T>, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), name ?: nextFieldName()) inline fun <reified T : Enum<T>> enum(getter: KProperty0<T>, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec.of(T::class.java), name ?: nextFieldName())
@JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: Supplier<T>, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(type), name ?: nextFieldName()) @JvmOverloads fun <T : Enum<T>> enum(type: Class<T>, getter: Supplier<T>, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec.of(type), name ?: nextFieldName())
inline fun <reified T : Enum<T>> enum(getter: Supplier<T>, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(T::class.java), name ?: nextFieldName()) inline fun <reified T : Enum<T>> enum(getter: Supplier<T>, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec.of(T::class.java), name ?: nextFieldName())
@JvmOverloads @JvmOverloads
fun byte( fun byte(
@ -347,10 +347,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
value: T = type.enumConstants[0], value: T = type.enumConstants[0],
getter: FieldGetter<T>? = null, getter: FieldGetter<T>? = null,
setter: FieldSetter<T>? = null, setter: FieldSetter<T>? = null,
writeByIndices: Boolean = false,
name: String? = nextFieldName(), name: String? = nextFieldName(),
): Field<T> { ): Field<T> {
return Field(value, EnumValueCodec(type, writeByIndices = writeByIndices), getter, setter, name = name ?: nextFieldName()) return Field(value, EnumValueCodec.of(type), getter, setter, name = name ?: nextFieldName())
} }
@JvmOverloads @JvmOverloads
@ -358,10 +357,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
value: T, value: T,
getter: FieldGetter<T>? = null, getter: FieldGetter<T>? = null,
setter: FieldSetter<T>? = null, setter: FieldSetter<T>? = null,
writeByIndices: Boolean = false,
name: String? = nextFieldName(), name: String? = nextFieldName(),
): Field<T> { ): Field<T> {
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 @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<V>( inner class Field<V>(
private var field: V, private var field: V,
private val codec: IStreamCodec<V>, private val codec: IStreamCodec<V>,
@ -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<V>( inner class ComputedField<V>(
private val getter: () -> V, private val getter: () -> V,
private val codec: IStreamCodec<V>, private val codec: IStreamCodec<V>,
@ -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<V> : AbstractField<V>, IMutableField<V> { inner class ObservedField<V> : AbstractField<V>, IMutableField<V> {
private val codec: IStreamCodec<V> private val codec: IStreamCodec<V>
private val getter: () -> V private val getter: () -> V