From 0c06d6edb87bf2cbef6318feba59b4993477ef20 Mon Sep 17 00:00:00 2001 From: DBotThePony Date: Sat, 18 Feb 2023 00:18:09 +0700 Subject: [PATCH] Move savetables logic to utility class --- .../mc/otm/block/entity/MatteryBlockEntity.kt | 91 +------ .../entity/matter/MatterBottlerBlockEntity.kt | 2 +- .../StoragePowerSupplierBlockEntity.kt | 2 +- .../entity/tech/PlatePressBlockEntity.kt | 2 +- .../mc/otm/core/util/Savetables.kt | 252 ++++++++++++++++++ 5 files changed, 260 insertions(+), 89 deletions(-) create mode 100644 src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt index a47e974c3..40bf618b5 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/MatteryBlockEntity.kt @@ -48,6 +48,7 @@ import ru.dbotthepony.mc.otm.core.math.BlockRotation import ru.dbotthepony.mc.otm.core.math.RelativeSide import ru.dbotthepony.mc.otm.core.nbt.set import ru.dbotthepony.mc.otm.core.util.EnumValueCodec +import ru.dbotthepony.mc.otm.core.util.Savetables import ru.dbotthepony.mc.otm.network.BlockEntitySyncPacket import ru.dbotthepony.mc.otm.network.FieldSynchronizer import ru.dbotthepony.mc.otm.network.WorldNetworkChannel @@ -414,68 +415,10 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc side.revive() } - private data class Savetable0(val type: Class, val property: KProperty0, val name: String, val serialize: (V) -> T, val deserialize: (V, T) -> Unit) - private data class Savetable1(val type: Class, val property: KMutableProperty0, val name: String, val serialize: (V) -> T, val deserialize: (T) -> V) + protected val savetables = Savetables() - private val savetables0 = ArrayList>() - private val savetables1 = ArrayList>() - - /** - * Save and load object state - */ - protected fun savetable(type: Class, property: KProperty0, name: String = property.name, serialize: (V) -> T, deserialize: (V, T) -> Unit) { - check(!savetables0.any { it.name == name }) { "Already has save field with name $name" } - check(!savetables1.any { it.name == name }) { "Already has save field with name $name" } - savetables0.add(Savetable0(type, property, name, serialize, deserialize)) - } - - /** - * Save and load value - */ - protected fun savetable(type: Class, property: KMutableProperty0, name: String = property.name, serialize: (V) -> T, deserialize: (T) -> V) { - check(!savetables0.any { it.name == name }) { "Already has save field with name $name" } - check(!savetables1.any { it.name == name }) { "Already has save field with name $name" } - savetables1.add(Savetable1(type, property, name, serialize, deserialize)) - } - - /** - * Save and load object state - */ - protected fun > savetable(type: Class, property: KProperty0, name: String = property.name) { - savetable(type, property, name, { it.serializeNBT()!! }, { self, it -> self.deserializeNBT(it) }) - } - - /** - * Save and load object state - */ protected inline fun > savetable(property: KProperty0, name: String = property.name) { - savetable(T::class.java, property, name) - } - - /** - * Save and load object state - */ - protected inline fun savetable(property: KProperty0, name: String = property.name, noinline serialize: (V) -> T, noinline deserialize: (V, T) -> Unit) { - savetable(T::class.java, property, name, serialize, deserialize) - } - - /** - * Save and load value - */ - protected inline fun savetable(property: KMutableProperty0, name: String = property.name, noinline serialize: (V) -> T, noinline deserialize: (T) -> V) { - savetable(T::class.java, property, name, serialize, deserialize) - } - - protected fun savetableFloat(property: KMutableProperty0, name: String = property.name) { - savetable(FloatTag::class.java, property, name, { FloatTag.valueOf(it) }, { tag -> tag.asFloat }) - } - - protected fun savetableDouble(property: KMutableProperty0, name: String = property.name) { - savetable(DoubleTag::class.java, property, name, { DoubleTag.valueOf(it) }, { tag -> tag.asDouble }) - } - - protected fun savetableBoolean(property: KMutableProperty0, name: String = property.name) { - savetable(ByteTag::class.java, property, name, { if (it) ByteTag.ONE else ByteTag.ZERO }, { tag -> tag.asInt > 0 }) + savetables.Stateful(property, name, T::class.java) } override fun saveAdditional(nbt: CompoundTag) { @@ -491,13 +434,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } - for (data in savetables0) { - nbt[data.name] = (data.serialize as (Any) -> Tag).invoke(data.property.get()) - } - - for (data in savetables1) { - nbt[data.name] = (data.serialize as (Any) -> Tag).invoke(data.property.get()) - } + savetables.serializeNBT(nbt) } override fun load(nbt: CompoundTag) { @@ -513,25 +450,7 @@ abstract class MatteryBlockEntity(p_155228_: BlockEntityType<*>, p_155229_: Bloc } } - for (data in savetables0) { - val value = nbt[data.name] - - if (value != null && data.type.isAssignableFrom(value.javaClass)) { - (data.deserialize as (Any, Tag) -> Unit).invoke(data.property.get(), value) - } else if (value != null) { - throw ClassCastException("Expected ${data.type.canonicalName}, got ${value.javaClass.canonicalName}") - } - } - - for (data in savetables1) { - val value = nbt[data.name] - - if (value != null && data.type.isAssignableFrom(value.javaClass)) { - (data.property as KMutableProperty0).set((data.deserialize as (Tag) -> Any).invoke(value)) - } else if (value != null) { - throw ClassCastException("Expected ${data.type.canonicalName}, got ${value.javaClass.canonicalName}") - } - } + savetables.deserializeNBT(nbt) } protected fun tickOnce(func: Runnable) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt index 0113e1a2a..7c73c414a 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/matter/MatterBottlerBlockEntity.kt @@ -118,7 +118,7 @@ class MatterBottlerBlockEntity(p_155229_: BlockPos, p_155230_: BlockState) : exposeGlobally(MatteryCapability.MATTER_NODE, this) exposeItemsGlobally(itemHandler) - savetableBoolean(::isBottling) + savetables.bool(::isBottling) savetable(::energy, ENERGY_KEY) savetable(::matter, MATTER_STORAGE_KEY) savetable(::container, INVENTORY_KEY) diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt index 21263331f..3e7f09dcd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/storage/StoragePowerSupplierBlockEntity.kt @@ -48,7 +48,7 @@ class StoragePowerSupplierBlockEntity(blockPos: BlockPos, blockState: BlockState private set init { - savetable(::powerPassed, POWER_PASSED_KEY, Decimal::serializeNBT, Decimal.Companion::deserializeNBT) + savetables.decimal(::powerPassed, POWER_PASSED_KEY) } override fun setLevel(p_155231_: Level) { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt index 05d7939ea..551f1adc9 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/block/entity/tech/PlatePressBlockEntity.kt @@ -52,7 +52,7 @@ class PlatePressBlockEntity( init { savetable(::energy, ENERGY_KEY) savetable(::container, INVENTORY_KEY) - savetableDouble(::experience) + savetables.double(::experience) } override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu { diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt new file mode 100644 index 000000000..ea41fab76 --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/core/util/Savetables.kt @@ -0,0 +1,252 @@ +package ru.dbotthepony.mc.otm.core.util + +import net.minecraft.nbt.ByteTag +import net.minecraft.nbt.CompoundTag +import net.minecraft.nbt.DoubleTag +import net.minecraft.nbt.FloatTag +import net.minecraft.nbt.IntTag +import net.minecraft.nbt.NumericTag +import net.minecraft.nbt.Tag +import net.minecraftforge.common.util.INBTSerializable +import ru.dbotthepony.mc.otm.core.GetterSetter +import ru.dbotthepony.mc.otm.core.asGetterSetter +import ru.dbotthepony.mc.otm.core.math.Decimal +import ru.dbotthepony.mc.otm.core.nbt.set +import java.util.function.Supplier +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.KProperty0 + +/** + * Utility class to manage list of composited properties in other classes to be (de)serialized in NBT tags + */ +class Savetables : INBTSerializable { + private val entries = ArrayList>() + + interface Entry : INBTSerializable { + val name: String + val type: Class + fun validate() + } + + inline fun , reified T : Tag> stateful(getter: Supplier, name: String): Stateful { + return Stateful(getter, name, T::class.java) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + + inline fun , reified T : Tag> stateful(getter: KProperty0, name: String = getter.name): Stateful { + return Stateful(getter, name, T::class.java) + .withSerializer { it.serializeNBT() } + .withDeserializer { v, t -> v.deserializeNBT(t) } + } + + fun decimal(prop: GetterSetter, name: String, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it.serializeNBT() } + .withDefault { default } + } + + fun decimalNullable(prop: GetterSetter, name: String, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it?.serializeNBT() } + .withDefault { default } + } + + fun decimal(prop: KMutableProperty0, name: String = prop.name, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it.serializeNBT() } + .withDefault { default } + } + + fun decimalNullable(prop: KMutableProperty0, name: String = prop.name, default: Decimal = Decimal.ZERO): Stateless { + return Stateless(prop, name, Tag::class.java) + .withDeserializer { Decimal.deserializeNBT(it) } + .withSerializer { it?.serializeNBT() } + .withDefault { default } + } + + fun float(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { FloatTag.valueOf(it) } + .withDeserializer { it.asFloat } + } + + fun float(prop: KMutableProperty0, name: String = prop.name): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { FloatTag.valueOf(it) } + .withDeserializer { it.asFloat } + } + + fun double(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { DoubleTag.valueOf(it) } + .withDeserializer { it.asDouble } + } + + fun double(prop: KMutableProperty0, name: String = prop.name): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { DoubleTag.valueOf(it) } + .withDeserializer { it.asDouble } + } + + fun int(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { IntTag.valueOf(it) } + .withDeserializer { it.asInt } + } + + fun int(prop: KMutableProperty0, name: String = prop.name): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { IntTag.valueOf(it) } + .withDeserializer { it.asInt } + } + + fun bool(prop: GetterSetter, name: String): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { ByteTag.valueOf(it) } + .withDeserializer { it.asByte > 0 } + } + + fun bool(prop: KMutableProperty0, name: String = prop.name): Stateless { + return Stateless(prop, name, NumericTag::class.java) + .withSerializer { ByteTag.valueOf(it) } + .withDeserializer { it.asByte > 0 } + } + + override fun serializeNBT(): CompoundTag { + return CompoundTag().also(::serializeNBT) + } + + private var validated = false + + fun validate() { + if (validated) return + + for (entry in entries) + entry.validate() + + validated = true + } + + override fun deserializeNBT(nbt: CompoundTag) { + validate() + + for (entry in entries) { + val value = nbt[entry.name] + + if (value != null && entry.type.isAssignableFrom(value.javaClass)) { + (entry as INBTSerializable).deserializeNBT(value) + } else { + entry.deserializeNBT(null) + } + } + } + + fun serializeNBT(nbt: CompoundTag) { + validate() + + for (entry in entries) { + val value = entry.serializeNBT() + + if (value != null) + nbt[entry.name] = value + } + } + + inner class Stateful(private val prop: Supplier, override val name: String, override val type: Class) : Entry { + constructor(field: KProperty0, name: String = field.name, type: Class) : this(field::get, name, type) + + init { + check(!entries.any { it.name == name }) { "Already has entry with name $name!" } + entries.add(this) + validated = false + } + + private var serializer: ((V) -> T?)? = null + private var deserializer: ((V, T) -> Unit)? = null + private var resetter: ((V) -> Unit)? = null + + fun withSerializer(serializer: (V) -> T?): Stateful { + this.serializer = serializer + return this + } + + fun withDeserializer(deserializer: (V, T) -> Unit): Stateful { + this.deserializer = deserializer + return this + } + + fun withResetter(resetter: (V) -> Unit): Stateful { + this.resetter = resetter + return this + } + + override fun serializeNBT(): T? { + return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get()) + } + + override fun deserializeNBT(nbt: T?) { + if (nbt == null) { + resetter?.invoke(prop.get()) + } else { + checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(prop.get(), nbt) + } + } + + override fun validate() { + checkNotNull(serializer) { "No serializer specified for $name" } + checkNotNull(deserializer) { "No deserializer specified for $name" } + } + } + + inner class Stateless(private val prop: GetterSetter, override val name: String, override val type: Class) : Entry { + constructor(field: KMutableProperty0, name: String = field.name, type: Class) : this(field.asGetterSetter(), name, type) + + init { + check(!entries.any { it.name == name }) { "Already has entry with name $name!" } + entries.add(this) + validated = false + } + + private var serializer: ((V) -> T?)? = null + private var deserializer: ((T) -> V)? = null + private var default: (() -> V)? = null + + override fun validate() { + checkNotNull(serializer) { "No serializer specified for $name" } + checkNotNull(deserializer) { "No deserializer specified for $name" } + } + + fun withSerializer(serializer: (V) -> T?): Stateless { + this.serializer = serializer + return this + } + + fun withDeserializer(deserializer: (T) -> V): Stateless { + this.deserializer = deserializer + return this + } + + fun withDefault(default: () -> V): Stateless { + this.default = default + return this + } + + override fun serializeNBT(): T? { + return checkNotNull(serializer) { "No serializer specified for $name" }.invoke(prop.get()) + } + + override fun deserializeNBT(nbt: T?) { + if (nbt == null) { + if (default != null) { + prop.accept(default!!.invoke()) + } + } else { + prop.accept(checkNotNull(deserializer) { "No deserializer specified for $name" }.invoke(nbt)) + } + } + } +}