Serializable curried functions

This marks final step in migration of research to JSON
Fixes #98
This commit is contained in:
DBotThePony 2022-09-23 21:09:28 +07:00
parent c619bf4787
commit 5c8ddc505d
Signed by: DBot
GPG Key ID: DCC23B5715498507
11 changed files with 428 additions and 16 deletions

View File

@ -2,6 +2,7 @@ package ru.dbotthepony.mc.otm.datagen
import ru.dbotthepony.mc.otm.OverdriveThatMatters import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidResearchType import ru.dbotthepony.mc.otm.android.AndroidResearchType
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.client.render.ResearchIcons import ru.dbotthepony.mc.otm.client.render.ResearchIcons
import ru.dbotthepony.mc.otm.core.TextComponent import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -176,7 +177,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
(i + 1) * 6 (i + 1) * 6
) )
) )
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0,
transformersUp = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_UP.bind(level)),
transformersDown = listOf(NanobotsArmorFeature.STRENGTH_TRANSFORMER_DOWN.bind(level)),
)
.build() .build()
}) })
@ -199,7 +203,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
(i + 1) * 6 (i + 1) * 6
) )
) )
.addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0) // TODO .addFeatureResult(AndroidFeatures.NANOBOTS_ARMOR, 0,
transformersUp = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_UP.bind(level)),
transformersDown = listOf(NanobotsArmorFeature.SPEED_TRANSFORMER_DOWN.bind(level)),
)
.build() .build()
}) })
} }

View File

@ -17,6 +17,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import ru.dbotthepony.mc.otm.android.AndroidResearchManager; import ru.dbotthepony.mc.otm.android.AndroidResearchManager;
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature;
import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue; import ru.dbotthepony.mc.otm.block.entity.blackhole.ExplosionQueue;
import ru.dbotthepony.mc.otm.capability.MatteryCapability; import ru.dbotthepony.mc.otm.capability.MatteryCapability;
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability; import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
@ -99,6 +100,8 @@ public final class OverdriveThatMatters {
ClientConfig.INSTANCE.register(); ClientConfig.INSTANCE.register();
ServerConfig.INSTANCE.register(); ServerConfig.INSTANCE.register();
NanobotsArmorFeature.Companion.register();
} }
private void setup(final FMLCommonSetupEvent event) { private void setup(final FMLCommonSetupEvent event) {

View File

@ -63,6 +63,10 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
capability.removeFeature(feature.feature) capability.removeFeature(feature.feature)
} else { } else {
get.level = level.level get.level = level.level
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersDown) {
transformer.accept(this to get)
}
} }
} }
} }
@ -88,6 +92,10 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
get.level = feature.level get.level = feature.level
} }
} }
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersUp) {
transformer.accept(this to get)
}
} }
} catch(err: Throwable) { } catch(err: Throwable) {
oldResearchLevel.clear() oldResearchLevel.clear()

View File

@ -17,9 +17,11 @@ import net.minecraftforge.network.PacketDistributor
import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.MINECRAFT_SERVER import ru.dbotthepony.mc.otm.MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER import ru.dbotthepony.mc.otm.NULLABLE_MINECRAFT_SERVER
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.SERVER_IS_LIVE import ru.dbotthepony.mc.otm.SERVER_IS_LIVE
import ru.dbotthepony.mc.otm.capability.matteryPlayer import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.client.minecraft import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry
import ru.dbotthepony.mc.otm.network.MatteryPacket import ru.dbotthepony.mc.otm.network.MatteryPacket
import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel import ru.dbotthepony.mc.otm.network.RegistryNetworkChannel
import ru.dbotthepony.mc.otm.network.enqueueWork import ru.dbotthepony.mc.otm.network.enqueueWork
@ -29,6 +31,14 @@ import java.util.LinkedList
import java.util.function.Supplier import java.util.function.Supplier
object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> { object AndroidResearchManager : SimpleJsonResourceReloadListener(GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(), "otm_android_research"), Iterable<AndroidResearchType> {
/**
* Feel free to register functions inside this thing from anywhere in your code
* (registration and querying is completely thread safe).
*
* Just make sure client and server has the same set of functions defined.
*/
val featureResultTransformers = SerializedFunctionRegistry<Pair<AndroidResearch, AndroidFeature>>()
const val DIRECTORY = "otm_android_research" const val DIRECTORY = "otm_android_research"
private val LOGGER = LogManager.getLogger() private val LOGGER = LogManager.getLogger()

View File

@ -24,6 +24,8 @@ import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.stream import ru.dbotthepony.mc.otm.core.stream
import ru.dbotthepony.mc.otm.core.toImmutableList import ru.dbotthepony.mc.otm.core.toImmutableList
import ru.dbotthepony.mc.otm.data.ItemStackCodec import ru.dbotthepony.mc.otm.data.ItemStackCodec
import ru.dbotthepony.mc.otm.data.SerializedFunctionRegistry
import ru.dbotthepony.mc.otm.data.stream
import ru.dbotthepony.mc.otm.registry.MRegistry import ru.dbotthepony.mc.otm.registry.MRegistry
import java.util.ArrayList import java.util.ArrayList
import java.util.LinkedList import java.util.LinkedList
@ -134,13 +136,26 @@ class AndroidResearchType(
data class FeatureReference( data class FeatureReference(
val id: ResourceLocation, val id: ResourceLocation,
val level: Int = 0, val level: Int = 0,
val isRigid: Boolean val isRigid: Boolean,
val transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
val transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
) { ) {
fun toJson(): JsonObject { fun toJson(): JsonObject {
return JsonObject().also { return JsonObject().also {
it["id"] = JsonPrimitive(id.toString()) it["id"] = JsonPrimitive(id.toString())
it["level"] = JsonPrimitive(level) it["level"] = JsonPrimitive(level)
it["is_rigid"] = JsonPrimitive(isRigid) it["is_rigid"] = JsonPrimitive(isRigid)
it["functions_up"] = JsonArray().also {
for (transformer in transformersUp) {
it.add(transformer.toJson())
}
}
it["functions_down"] = JsonArray().also {
for (transformer in transformersDown) {
it.add(transformer.toJson())
}
}
} }
} }
@ -148,6 +163,8 @@ class AndroidResearchType(
buff.writeUtf(id.toString()) buff.writeUtf(id.toString())
buff.writeVarInt(level) buff.writeVarInt(level)
buff.writeBoolean(isRigid) buff.writeBoolean(isRigid)
buff.writeCollection(transformersUp) { a, b -> b.toNetwork(a) }
buff.writeCollection(transformersDown) { a, b -> b.toNetwork(a) }
} }
companion object { companion object {
@ -155,7 +172,9 @@ class AndroidResearchType(
return FeatureReference( return FeatureReference(
ResourceLocation(buff.readUtf()), ResourceLocation(buff.readUtf()),
buff.readVarInt(), buff.readVarInt(),
buff.readBoolean() buff.readBoolean(),
buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList(),
buff.readCollection({ LinkedList() }, AndroidResearchManager.featureResultTransformers::fromNetwork).filterNotNull().toImmutableList()
) )
} }
@ -165,7 +184,11 @@ class AndroidResearchType(
} else if (value is JsonObject) { } else if (value is JsonObject) {
return FeatureReference( return FeatureReference(
ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString), ResourceLocation((value["id"] as? JsonPrimitive ?: throw JsonSyntaxException("Invalid `id` value")).asString),
(value["level"] as JsonPrimitive?)?.asInt ?: 0, (value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true) (value["level"] as JsonPrimitive?)?.asInt ?: 0,
(value["is_rigid"] as JsonPrimitive?)?.asBoolean ?: true,
(value["functions_up"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()),
(value["functions_down"] as JsonArray? ?: JsonArray()).stream().map { AndroidResearchManager.featureResultTransformers.fromJson(it) }.filter { it != null }.collect(ImmutableList.toImmutableList<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>>()),
)
} else { } else {
throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}") throw JsonSyntaxException("Unknown element type ${value::class.qualifiedName}")
} }
@ -659,19 +682,41 @@ class AndroidResearchType(
fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid) fun addBlocker(type: AndroidResearchType, rigid: Boolean = true) = addBlocker(type.id, rigid)
@JvmOverloads @JvmOverloads
fun addFeatureResult(id: ResourceLocation, level: Int = 0, rigid: Boolean = false): Builder { fun addFeatureResult(
features.add(FeatureReference(id, level, rigid)) id: ResourceLocation,
level: Int = 0,
rigid: Boolean = false,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
): Builder {
features.add(FeatureReference(id, level, rigid, transformersUp, transformersDown))
return this return this
} }
@JvmOverloads @JvmOverloads
fun addFeatureResult(feature: AndroidFeatureType<*>, level: Int = 0, rigid: Boolean = true): Builder { fun addFeatureResult(
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid)) feature: AndroidFeatureType<*>,
level: Int = 0,
rigid: Boolean = true,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
): Builder {
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid, transformersUp, transformersDown))
return this return this
} }
fun addFeatureResult(id: ResourceLocation, rigid: Boolean = false): Builder { fun addFeatureResult(
features.add(FeatureReference(id, 0, rigid)) id: ResourceLocation,
rigid: Boolean = false,
transformersUp: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
transformersDown: Collection<SerializedFunctionRegistry.BoundFunction<Pair<AndroidResearch, AndroidFeature>>> = listOf(),
): Builder {
features.add(FeatureReference(id, 0, rigid, transformersUp, transformersDown))
return this
}
fun addFeatureResult(ref: FeatureReference): Builder {
features.add(ref)
return this return this
} }

View File

@ -1,9 +1,12 @@
package ru.dbotthepony.mc.otm.android.feature package ru.dbotthepony.mc.otm.android.feature
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer import net.minecraft.server.level.ServerPlayer
import net.minecraftforge.event.entity.living.LivingHurtEvent import net.minecraftforge.event.entity.living.LivingHurtEvent
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.android.AndroidFeature import ru.dbotthepony.mc.otm.android.AndroidFeature
import ru.dbotthepony.mc.otm.android.AndroidResearchManager
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact
import ru.dbotthepony.mc.otm.core.ImpreciseFraction import ru.dbotthepony.mc.otm.core.ImpreciseFraction
@ -14,10 +17,10 @@ import kotlin.math.roundToInt
class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) { class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
var strength: Int = 0 var strength: Int = 0
set(value) { field = value.coerceIn(0 .. 2) } set(value) { field = value.coerceIn(0 .. 3) }
var speed: Int = 0 var speed: Int = 0
set(value) { field = value.coerceIn(0 .. 2) } set(value) { field = value.coerceIn(0 .. 3) }
private var ticksPassed = 0 private var ticksPassed = 0
private var layers = 0 private var layers = 0
@ -86,5 +89,40 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
0.3f, 0.3f,
0.4f, 0.4f,
) )
val STRENGTH_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_up"))
{ level: Int ->
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level - 1) {
(second as NanobotsArmorFeature).strength = level
}
}
val STRENGTH_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_strength_down"))
{ level: Int ->
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).strength == level) {
(second as NanobotsArmorFeature).strength = level - 1
}
}
val SPEED_TRANSFORMER_UP = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_up"))
{ level: Int ->
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level - 1) {
(second as NanobotsArmorFeature).speed = level
}
}
val SPEED_TRANSFORMER_DOWN = AndroidResearchManager.featureResultTransformers.register(ResourceLocation(OverdriveThatMatters.MOD_ID, "nanobots_armor_speed_down"))
{ level: Int ->
if (second is NanobotsArmorFeature && (second as NanobotsArmorFeature).speed == level) {
(second as NanobotsArmorFeature).speed = level - 1
}
}
/**
* Dummy method just for static initializer to execute
*/
fun register() {
}
} }
} }

View File

@ -188,11 +188,11 @@ class JEIPlugin : IModPlugin {
val combined = combine.toImmutableList() val combined = combine.toImmutableList()
val newView = object : IRecipeSlotsView { val newView = object : IRecipeSlotsView {
override fun getSlotViews(): MutableList<IRecipeSlotView> { override fun getSlotViews(): List<IRecipeSlotView> {
return combined return combined
} }
override fun getSlotViews(role: RecipeIngredientRole): MutableList<IRecipeSlotView> { override fun getSlotViews(role: RecipeIngredientRole): List<IRecipeSlotView> {
return when (role) { return when (role) {
RecipeIngredientRole.INPUT -> filteredInputs RecipeIngredientRole.INPUT -> filteredInputs
RecipeIngredientRole.OUTPUT -> outputs RecipeIngredientRole.OUTPUT -> outputs

View File

@ -349,7 +349,7 @@ operator fun <T : Comparable<T>> StateHolder<*, *>.get(value: Property<T>): T {
return getValue(value) return getValue(value)
} }
fun <T> List<T>.toImmutableList(): ImmutableList<T> { fun <T> List<T>.toImmutableList(): List<T> {
return ImmutableList.copyOf(this) return ImmutableList.copyOf(this)
} }

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.core package ru.dbotthepony.mc.otm.core
import com.google.common.collect.ImmutableList
import io.netty.handler.codec.EncoderException import io.netty.handler.codec.EncoderException
import net.minecraft.nbt.CompoundTag import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtAccounter import net.minecraft.nbt.NbtAccounter
@ -8,9 +9,24 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack import net.minecraft.world.item.ItemStack
import net.minecraftforge.registries.ForgeRegistries import net.minecraftforge.registries.ForgeRegistries
import net.minecraftforge.registries.ForgeRegistry import net.minecraftforge.registries.ForgeRegistry
import ru.dbotthepony.mc.otm.network.BigDecimalValueCodec
import ru.dbotthepony.mc.otm.network.BooleanValueCodec
import ru.dbotthepony.mc.otm.network.ByteValueCodec
import ru.dbotthepony.mc.otm.network.DoubleValueCodec
import ru.dbotthepony.mc.otm.network.FloatValueCodec
import ru.dbotthepony.mc.otm.network.ImpreciseFractionValueCodec
import ru.dbotthepony.mc.otm.network.IntValueCodec
import ru.dbotthepony.mc.otm.network.ItemStackValueCodec
import ru.dbotthepony.mc.otm.network.LongValueCodec
import ru.dbotthepony.mc.otm.network.NetworkValueCodec
import ru.dbotthepony.mc.otm.network.NullValueCodec
import ru.dbotthepony.mc.otm.network.ShortValueCodec
import ru.dbotthepony.mc.otm.network.UUIDValueCodec
import java.io.* import java.io.*
import java.math.BigDecimal import java.math.BigDecimal
import java.math.BigInteger import java.math.BigInteger
import java.util.LinkedList
import java.util.function.Predicate
// But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement // But seriously, Mojang, why would you need to derive from ByteBuf directly, when you can implement
// your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway? // your own InputStream and OutputStream, since ByteBuf is meant to be operated on most time like a stream anyway?
@ -181,3 +197,71 @@ fun InputStream.readBigDecimal(): BigDecimal {
read(bytes) read(bytes)
return BigDecimal(BigInteger(bytes), scale) return BigDecimal(BigInteger(bytes), scale)
} }
private data class StreamCodec<T>(
val condition: Predicate<Any?>,
val id: Int,
val codec: NetworkValueCodec<T>
) {
fun read(stream: DataInputStream): T {
return codec.read(stream)
}
fun write(stream: DataOutputStream, value: Any?) {
codec.write(stream, value as T)
}
}
private inline fun <reified T> StreamCodec(id: Int, codec: NetworkValueCodec<T>): StreamCodec<T> {
return StreamCodec({it is T}, id, codec)
}
private val codecs: List<StreamCodec<*>> = ImmutableList.builder<StreamCodec<*>>().let {
var codecID = 0
it.add(StreamCodec({ it == null }, codecID++, NullValueCodec))
it.add(StreamCodec(codecID++, BooleanValueCodec))
it.add(StreamCodec(codecID++, ByteValueCodec))
it.add(StreamCodec(codecID++, ShortValueCodec))
it.add(StreamCodec(codecID++, IntValueCodec))
it.add(StreamCodec(codecID++, LongValueCodec))
it.add(StreamCodec(codecID++, FloatValueCodec))
it.add(StreamCodec(codecID++, DoubleValueCodec))
it.add(StreamCodec(codecID++, ItemStackValueCodec))
it.add(StreamCodec(codecID++, ImpreciseFractionValueCodec))
it.add(StreamCodec(codecID++, BigDecimalValueCodec))
it.add(StreamCodec(codecID++, UUIDValueCodec))
it.build()
}
class NotSerializableValueException(message: String) : Exception(message)
/**
* Write arbitrary data to this stream, in exploit-free way
*/
fun DataOutputStream.writeType(value: Any?) {
for (codec in codecs) {
if (codec.condition.test(value)) {
write(codec.id)
codec.write(this, value)
return
}
}
throw NotSerializableValueException("Value $value <${value?.let { it::class.qualifiedName }}> can not be serialized")
}
/**
* Read arbitrary data from this stream, in exploit-free way
*/
fun DataInputStream.readType(): Any? {
val id = read()
if (id >= codecs.size) {
throw IndexOutOfBoundsException("No codec for network type $id")
}
return codecs[id].read(this)
}

View File

@ -0,0 +1,215 @@
package ru.dbotthepony.mc.otm.data
import com.google.gson.Gson
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer
import com.google.gson.JsonSyntaxException
import com.google.gson.TypeAdapter
import com.google.gson.internal.bind.TypeAdapters
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import io.netty.buffer.ByteBufInputStream
import io.netty.buffer.ByteBufOutputStream
import it.unimi.dsi.fastutil.io.FastByteArrayInputStream
import it.unimi.dsi.fastutil.io.FastByteArrayOutputStream
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import ru.dbotthepony.mc.otm.core.readType
import ru.dbotthepony.mc.otm.core.readVarIntLE
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.writeType
import ru.dbotthepony.mc.otm.core.writeVarIntLE
import java.io.DataInputStream
import java.io.DataOutputStream
import java.lang.reflect.Type
import java.util.Base64
import java.util.Collections
import java.util.LinkedList
import java.util.function.Consumer
class SerializedFunctionRegistry<R>(val gson: Gson = Gson()) : JsonSerializer<SerializedFunctionRegistry.BoundFunction<R>>, JsonDeserializer<SerializedFunctionRegistry.BoundFunction<R>>, TypeAdapter<SerializedFunctionRegistry.BoundFunction<R>>() {
fun interface AnonymousFunction<R> {
fun invoke(receiver: R, arguments: List<Any?>)
}
data class BoundFunction<R>(
val function: Function<R>,
val arguments: List<Any?>
) : Consumer<R> {
fun toJson(): JsonElement {
return JsonObject().also {
it["function"] = function.toJson()
val stream = FastByteArrayOutputStream()
val dataStream = DataOutputStream(stream)
stream.writeVarIntLE(arguments.size)
for (argument in arguments) {
dataStream.writeType(argument)
}
it["arguments"] = JsonPrimitive(Base64.getEncoder().encode(stream.array.copyOfRange(0, stream.length)).toString(Charsets.UTF_8))
}
}
fun toNetwork(buff: FriendlyByteBuf) {
function.toNetwork(buff)
val stream = DataOutputStream(ByteBufOutputStream(buff))
stream.writeVarIntLE(arguments.size)
for (argument in arguments) {
stream.writeType(argument)
}
}
override fun accept(t: R) {
function.body.invoke(t, arguments)
}
}
data class Function<R>(
val id: ResourceLocation,
val body: AnonymousFunction<R>,
val registry: SerializedFunctionRegistry<R>
) {
fun bind(vararg arguments: Any?): BoundFunction<R> {
validate(arguments.iterator().withIndex())
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
}
fun bind(arguments: List<Any?>): BoundFunction<R> {
validate(arguments.iterator().withIndex())
return BoundFunction(this, Collections.unmodifiableList(LinkedList<Any?>().also { it.addAll(arguments) }))
}
fun toJson(): JsonElement {
return JsonPrimitive(id.toString())
}
fun toNetwork(buff: FriendlyByteBuf) {
buff.writeUtf(id.toString())
}
companion object {
private fun validate(iterator: Iterator<IndexedValue<Any?>>) {
try {
val stream = DataOutputStream(FastByteArrayOutputStream())
for ((i, argument) in iterator) {
try {
stream.writeType(argument)
} catch(err: Exception) {
throw IllegalArgumentException("Argument at $i can not be serialized", err)
}
}
} catch(err: Exception) {
throw IllegalArgumentException("Argument list validation failed", err)
}
}
}
}
private val map = HashMap<ResourceLocation, Function<R>>()
fun register(id: ResourceLocation, function: AnonymousFunction<R>): Function<R> {
synchronized(map) {
return map.computeIfAbsent(id) { Function(id, function, this) }
}
}
inline fun <reified A : Any> register(id: ResourceLocation, noinline function: R.(A) -> Unit): Function<R> {
return register(id, AnonymousFunction { r, args ->
check(args.size == 1) { "Invalid amount of arguments. 1 is required" }
function.invoke(
r,
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given")
)
})
}
inline fun <reified A : Any, reified B : Any> register(id: ResourceLocation, noinline function: R.(A, B) -> Unit): Function<R> {
return register(id, AnonymousFunction { r, args ->
check(args.size == 2) { "Invalid amount of arguments. 2 is required" }
function.invoke(
r,
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"),
args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"),
)
})
}
inline fun <reified A : Any, reified B : Any, reified C : Any> register(id: ResourceLocation, noinline function: R.(A, B, C) -> Unit): Function<R> {
return register(id, AnonymousFunction { r, args ->
check(args.size == 3) { "Invalid amount of arguments. 3 is required" }
function.invoke(
r,
args[0] as? A ?: throw ClassCastException("Argument at 0 is supposed to be ${A::class.qualifiedName}, ${args[0]?.let { it::class.qualifiedName }} given"),
args[1] as? B ?: throw ClassCastException("Argument at 1 is supposed to be ${B::class.qualifiedName}, ${args[1]?.let { it::class.qualifiedName }} given"),
args[2] as? C ?: throw ClassCastException("Argument at 2 is supposed to be ${B::class.qualifiedName}, ${args[2]?.let { it::class.qualifiedName }} given"),
)
})
}
fun get(id: ResourceLocation): Function<R>? {
synchronized(map) {
return map[id]
}
}
fun fromNetwork(buff: FriendlyByteBuf): BoundFunction<R>? {
val id = ResourceLocation(buff.readUtf())
val stream = DataInputStream(ByteBufInputStream(buff))
val arguments = LinkedList<Any?>()
for (i in 0 until stream.readVarIntLE()) {
arguments.add(stream.readType())
}
return map[id]?.bind(arguments)
}
fun fromJson(value: JsonElement): BoundFunction<R>? {
if (value !is JsonObject) {
return null
}
val id = value["function"]?.asString ?: return null
val argumentString = value["arguments"]?.asString ?: return null
val stream = DataInputStream(FastByteArrayInputStream(Base64.getDecoder().decode(argumentString)))
val arguments = LinkedList<Any?>()
for (i in 0 until stream.readVarIntLE()) {
arguments.add(stream.readType())
}
return map[ResourceLocation(id)]?.bind(arguments)
}
override fun serialize(src: BoundFunction<R>, typeOfSrc: Type, context: JsonSerializationContext): JsonElement {
return src.toJson()
}
override fun deserialize(
json: JsonElement,
typeOfT: Type,
context: JsonDeserializationContext
): BoundFunction<R> {
return fromJson(json) ?: throw JsonSyntaxException("Function is invalid")
}
override fun write(out: JsonWriter, value: BoundFunction<R>) {
TypeAdapters.JSON_ELEMENT.write(out, value.toJson())
}
override fun read(`in`: JsonReader): BoundFunction<R> {
return fromJson(TypeAdapters.JSON_ELEMENT.read(`in`)) ?: throw JsonSyntaxException("Function is invalid")
}
}

View File

@ -59,6 +59,8 @@ class NetworkValueCodec<V>(
} }
} }
val NullValueCodec = NetworkValueCodec({ null }, { _, _ -> })
val BooleanValueCodec = NetworkValueCodec(DataInputStream::readBoolean, DataOutputStream::writeBoolean) val BooleanValueCodec = NetworkValueCodec(DataInputStream::readBoolean, DataOutputStream::writeBoolean)
val ByteValueCodec = NetworkValueCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) }) val ByteValueCodec = NetworkValueCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) })
val ShortValueCodec = NetworkValueCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) }) val ShortValueCodec = NetworkValueCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) })