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
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

View File

@ -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<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)
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 {
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")
}
override fun write(stream: DataOutputStream, value: V) {
if (writeByIndices)
stream.writeVarIntLE(value.ordinal)
else
stream.writeBinaryString(value.name)
}
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
}
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 {
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
*
@ -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) {
if (this is DataOutput) {
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)
class EnumInputWithFeedback<E : Enum<E>>(menu: MatteryMenu, clazz: Class<E>) : AbstractPlayerInputWithFeedback<E>() {
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)

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.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)

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 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())
inline fun <reified T : Enum<T>> enum(noinline getter: () -> T, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), 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.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())
inline fun <reified T : Enum<T>> enum(getter: KProperty0<T>, name: String? = nextFieldName()) = ComputedField(getter, EnumValueCodec(T::class.java), 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.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())
inline fun <reified T : Enum<T>> enum(getter: Supplier<T>, name: String? = nextFieldName()) = ComputedField(getter::get, EnumValueCodec(T::class.java), 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.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<T>? = null,
setter: FieldSetter<T>? = null,
writeByIndices: Boolean = false,
name: String? = nextFieldName(),
): 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
@ -358,10 +357,9 @@ class FieldSynchronizer(private val callback: Runnable, private val alwaysCallCa
value: T,
getter: FieldGetter<T>? = null,
setter: FieldSetter<T>? = null,
writeByIndices: Boolean = false,
name: String? = nextFieldName(),
): 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
@ -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>(
private var field: 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>(
private val getter: () -> 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> {
private val codec: IStreamCodec<V>
private val getter: () -> V