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.android.AndroidResearchType
import ru.dbotthepony.mc.otm.android.feature.NanobotsArmorFeature
import ru.dbotthepony.mc.otm.client.render.ResearchIcons
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
@ -176,7 +177,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
(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()
})
@ -199,7 +203,10 @@ fun addResearchData(serializer: Consumer<AndroidResearchType>, lang: MatteryLang
(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()
})
}

View File

@ -17,6 +17,7 @@ import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
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.capability.MatteryCapability;
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability;
@ -99,6 +100,8 @@ public final class OverdriveThatMatters {
ClientConfig.INSTANCE.register();
ServerConfig.INSTANCE.register();
NanobotsArmorFeature.Companion.register();
}
private void setup(final FMLCommonSetupEvent event) {

View File

@ -63,6 +63,10 @@ class AndroidResearch(val type: AndroidResearchType, val capability: MatteryPlay
capability.removeFeature(feature.feature)
} else {
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
}
}
for (transformer in type.features.first { it.id == feature.feature.registryName }.transformersUp) {
transformer.accept(this to get)
}
}
} catch(err: Throwable) {
oldResearchLevel.clear()

View File

@ -17,9 +17,11 @@ import net.minecraftforge.network.PacketDistributor
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.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.capability.matteryPlayer
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.RegistryNetworkChannel
import ru.dbotthepony.mc.otm.network.enqueueWork
@ -29,6 +31,14 @@ import java.util.LinkedList
import java.util.function.Supplier
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"
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.toImmutableList
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 java.util.ArrayList
import java.util.LinkedList
@ -134,13 +136,26 @@ class AndroidResearchType(
data class FeatureReference(
val id: ResourceLocation,
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 {
return JsonObject().also {
it["id"] = JsonPrimitive(id.toString())
it["level"] = JsonPrimitive(level)
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.writeVarInt(level)
buff.writeBoolean(isRigid)
buff.writeCollection(transformersUp) { a, b -> b.toNetwork(a) }
buff.writeCollection(transformersDown) { a, b -> b.toNetwork(a) }
}
companion object {
@ -155,7 +172,9 @@ class AndroidResearchType(
return FeatureReference(
ResourceLocation(buff.readUtf()),
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) {
return FeatureReference(
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 {
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)
@JvmOverloads
fun addFeatureResult(id: ResourceLocation, level: Int = 0, rigid: Boolean = false): Builder {
features.add(FeatureReference(id, level, rigid))
fun addFeatureResult(
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
}
@JvmOverloads
fun addFeatureResult(feature: AndroidFeatureType<*>, level: Int = 0, rigid: Boolean = true): Builder {
features.add(FeatureReference(feature.registryName ?: throw NullPointerException("Feature $feature does not have registry name"), level, rigid))
fun addFeatureResult(
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
}
fun addFeatureResult(id: ResourceLocation, rigid: Boolean = false): Builder {
features.add(FeatureReference(id, 0, rigid))
fun addFeatureResult(
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
}

View File

@ -1,9 +1,12 @@
package ru.dbotthepony.mc.otm.android.feature
import net.minecraft.nbt.CompoundTag
import net.minecraft.resources.ResourceLocation
import net.minecraft.server.level.ServerPlayer
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.AndroidResearchManager
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.capability.extractEnergyInnerExact
import ru.dbotthepony.mc.otm.core.ImpreciseFraction
@ -14,10 +17,10 @@ import kotlin.math.roundToInt
class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(AndroidFeatures.NANOBOTS_ARMOR, android) {
var strength: Int = 0
set(value) { field = value.coerceIn(0 .. 2) }
set(value) { field = value.coerceIn(0 .. 3) }
var speed: Int = 0
set(value) { field = value.coerceIn(0 .. 2) }
set(value) { field = value.coerceIn(0 .. 3) }
private var ticksPassed = 0
private var layers = 0
@ -86,5 +89,40 @@ class NanobotsArmorFeature(android: MatteryPlayerCapability) : AndroidFeature(An
0.3f,
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 newView = object : IRecipeSlotsView {
override fun getSlotViews(): MutableList<IRecipeSlotView> {
override fun getSlotViews(): List<IRecipeSlotView> {
return combined
}
override fun getSlotViews(role: RecipeIngredientRole): MutableList<IRecipeSlotView> {
override fun getSlotViews(role: RecipeIngredientRole): List<IRecipeSlotView> {
return when (role) {
RecipeIngredientRole.INPUT -> filteredInputs
RecipeIngredientRole.OUTPUT -> outputs

View File

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

View File

@ -1,5 +1,6 @@
package ru.dbotthepony.mc.otm.core
import com.google.common.collect.ImmutableList
import io.netty.handler.codec.EncoderException
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtAccounter
@ -8,9 +9,24 @@ import net.minecraft.world.item.Item
import net.minecraft.world.item.ItemStack
import net.minecraftforge.registries.ForgeRegistries
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.math.BigDecimal
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
// 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)
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 ByteValueCodec = NetworkValueCodec(DataInputStream::readByte, { s, v -> s.writeByte(v.toInt()) })
val ShortValueCodec = NetworkValueCodec(DataInputStream::readShort, { s, v -> s.writeShort(v.toInt()) })