Serializable curried functions
This marks final step in migration of research to JSON Fixes #98
This commit is contained in:
parent
c619bf4787
commit
5c8ddc505d
@ -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()
|
||||
})
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
@ -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()) })
|
||||
|
Loading…
Reference in New Issue
Block a user