diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt new file mode 100644 index 000000000..0466a82da --- /dev/null +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/CodecList.kt @@ -0,0 +1,112 @@ +package ru.dbotthepony.mc.otm.data + +import com.google.common.collect.ImmutableList +import com.mojang.datafixers.util.Pair +import com.mojang.serialization.Codec +import com.mojang.serialization.DataResult +import com.mojang.serialization.DynamicOps +import ru.dbotthepony.mc.otm.core.stream +import java.util.function.Predicate +import java.util.stream.Stream + +class CodecList(codecs: Stream>) : Codec { + constructor(codecs: Collection>) : this(codecs.stream()) + constructor(vararg codecs: Codec) : this(codecs.stream() as Stream>) + + private val codecs = codecs.collect(ImmutableList.toImmutableList()) + + init { + require(this.codecs.isNotEmpty()) { "No codecs provided" } + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + val results = ArrayList>(codecs.size) + + for (codec in codecs) { + val result = codec.encode(input, ops, prefix) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error { + "None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + } + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val results = ArrayList>>(codecs.size) + + for (codec in codecs) { + val result = codec.decode(ops, input) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error { + "None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + } + } +} + +class PredicatedCodecList(codecs: Stream, Predicate>>) : Codec { + constructor(codecs: Collection, Predicate>>) : this(codecs.stream()) + constructor(vararg codecs: kotlin.Pair, Predicate>) : this(codecs.stream() as Stream, Predicate>>) + + private val codecs = codecs.collect(ImmutableList.toImmutableList()) + + init { + require(this.codecs.isNotEmpty()) { "No codecs provided" } + } + + override fun encode(input: S, ops: DynamicOps, prefix: T): DataResult { + val results = ArrayList>(codecs.size) + var i = -1 + + for ((codec, predicate) in codecs) { + i++ + + if (predicate.test(input)) { + val result = codec.encode(input, ops, prefix) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } else { + val i2 = i + results.add(DataResult.error { "Codec #$i2 predicate tested false" }) + } + } + + return DataResult.error { + "None of codecs encoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + } + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val results = ArrayList>>(codecs.size) + + for ((codec) in codecs) { + val result = codec.decode(ops, input) + + if (result.result().isPresent) { + return result + } else { + results.add(result) + } + } + + return DataResult.error { + "None of codecs decoded the input:\n " + results.joinToString(";\n ") { it.error().get().message() } + } + } +} diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt index 371e0a017..140bd0848 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/DecimalCodec.kt @@ -12,12 +12,20 @@ object DecimalCodec : Codec { } override fun decode(ops: DynamicOps, input: T): DataResult> { - return ops.getStringValue(input).flatMap { + val result = ops.getStringValue(input).flatMap { try { DataResult.success(Pair(Decimal(it), ops.empty())) } catch (err: NumberFormatException) { DataResult.error { "Not a valid number for converting into Decimal: $it" } } } + + if (result.result().isPresent) { + return result + } + + return ops.getNumberValue(input).flatMap { + DataResult.success(Pair(Decimal(it.toString()), ops.empty())) + } } } diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt index 5ea678522..50449c5dd 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/matter/ComputeAction.kt @@ -13,7 +13,9 @@ import net.minecraft.world.item.Item import net.minecraftforge.registries.ForgeRegistries import ru.dbotthepony.mc.otm.core.math.Decimal import ru.dbotthepony.mc.otm.data.DecimalCodec +import ru.dbotthepony.mc.otm.data.PredicatedCodecList import java.util.Optional +import java.util.function.Predicate class ComputeAction( id: Either>, @@ -21,74 +23,158 @@ class ComputeAction( priority: Optional = Optional.empty(), ) : AbstractRegistryAction(id, priority = priority) { companion object : Type { - private val constantCodecConcise: Codec by lazy { - RecordCodecBuilder.create { - it.group( - DecimalCodec.fieldOf("matter").forGetter(Constant::matter), - Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), - IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), - ).apply(it, ::Constant) - } - } - - private val constantCodecVeryConcise: Codec by lazy { - RecordCodecBuilder.create { - it.group( - DecimalCodec.fieldOf("value").forGetter(Constant::matter), - IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), - ).apply(it, ::Constant) - } - } - - private val constantCodecVeryConcise2: Codec by lazy { - RecordCodecBuilder.create { - it.group( - Codec.DOUBLE.fieldOf("value").forGetter(Constant::complexity), - IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), - ).apply(it, ::Constant) - } - } - - private val constantCodecVerbose: Codec by lazy { - RecordCodecBuilder.create { + private val constantCodecFull: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { it.group( DecimalCodec.fieldOf("matter").forGetter(Constant::matter), Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), ).apply(it, ::Constant) + } to Predicate { true } + } + + private val constantCodecValues: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b, c -> Constant(a, b, c, c) } + } to Predicate { it.matterFunction == it.complexityFunction } + } + + private val constantCodecFunctions: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("value").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), + ).apply(it) { a, b, c -> Constant(a, a.toDouble(), b, c) } + } to Predicate { it.matter == Decimal(it.complexity.toString()) } + } + + private val constantCodecConcise: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("value").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, a.toDouble(), b, b) } + } to Predicate { it.matter == Decimal(it.complexity.toString()) && it.matterFunction == it.complexityFunction } + } + + private val constantCodecComplexity: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::complexityFunction), + ).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) } + } to Predicate { it.matterFunction == IMatterFunction.NOOP } + } + + private val constantCodecComplexity2: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + Codec.DOUBLE.fieldOf("complexity").forGetter(Constant::complexity), + IMatterFunction.registry.codec.fieldOf("complexityFunction").forGetter(Constant::complexityFunction), + ).apply(it) { a, b -> Constant(Decimal.ZERO, a.toDouble(), IMatterFunction.NOOP, b) } + } to Predicate { it.matterFunction == IMatterFunction.NOOP } + } + + private val constantCodecMatter: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("function").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) } + } to Predicate { it.complexityFunction == IMatterFunction.NOOP } + } + + private val constantCodecMatter2: kotlin.Pair, Predicate> by lazy { + RecordCodecBuilder.create { + it.group( + DecimalCodec.fieldOf("matter").forGetter(Constant::matter), + IMatterFunction.registry.codec.fieldOf("matterFunction").forGetter(Constant::matterFunction), + ).apply(it) { a, b -> Constant(a, 0.0, b, IMatterFunction.NOOP) } + } to Predicate { it.complexityFunction == IMatterFunction.NOOP } + } + + private data class StringConstantCodec(val fn: IMatterFunction, val symbol: Char) : Codec, Predicate { + val pair = this to this + + override fun encode(input: Constant, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.createString(symbol + input.matter.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + return ops.getStringValue(input).flatMap { + if (it[0] == symbol) { + try { + DataResult.success(Pair(Constant(Decimal(it.substring(1).trim()), it.substring(1).trim().toDouble(), fn, fn), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error { "Not a number: ${it.substring(1).trim()} (input string: $it)" } + } + } else { + DataResult.error { "Input string does not match expected operand: expected $symbol, got ${it[0]} (for input $it)" } + } + } + } + + override fun test(t: Constant): Boolean { + return t.matterFunction == fn && t.complexityFunction == fn && t.matter == Decimal(t.complexity.toString()) + } + } + + private object PlainStringConstantCodec : Codec, Predicate { + override fun encode(input: Constant, ops: DynamicOps, prefix: T): DataResult { + return DataResult.success(ops.createString(input.matter.toString())) + } + + override fun decode(ops: DynamicOps, input: T): DataResult> { + val result = ops.getNumberValue(input).flatMap { + try { + DataResult.success(Pair(Constant(Decimal(it.toString()), it.toDouble(), IMatterFunction.PLUS, IMatterFunction.PLUS), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error { "Not a number: $it" } + } + } + + if (result.result().isPresent) + return result + + return ops.getStringValue(input).flatMap { + try { + DataResult.success(Pair(Constant(Decimal(it), it.toDouble(), IMatterFunction.PLUS, IMatterFunction.PLUS), ops.empty())) + } catch (err: NumberFormatException) { + DataResult.error { "Not a number: $it" } + } + } + } + + override fun test(t: Constant): Boolean { + return t.matterFunction == IMatterFunction.PLUS && t.complexityFunction == IMatterFunction.PLUS && t.matter == Decimal(t.complexity.toString()) } } private val constantCodec: Codec by lazy { - Codec.either(constantCodecConcise, constantCodecVerbose) - .xmap({ it.map({ it }, { it }) }, { if (it.matterFunction == it.complexityFunction) Either.left(it) else Either.right(it) }) - } - - private object ActualConstantCodec : Codec { - override fun encode(input: Constant, ops: DynamicOps, prefix: T): DataResult { - if (input.complexity == input.matter.toDouble()) { - return constantCodecVeryConcise.encode(input, ops, prefix) - } else { - return constantCodec.encode(input, ops, prefix) - } - } - - override fun decode(ops: DynamicOps, input: T): DataResult> { - var result = constantCodecVeryConcise.decode(ops, input) - - if (result.result().isPresent) { - return result - } - - result = constantCodecVeryConcise2.decode(ops, input) - - if (result.result().isPresent) { - return result - } - - return constantCodec.decode(ops, input) - } + PredicatedCodecList( + PlainStringConstantCodec to PlainStringConstantCodec, + StringConstantCodec(IMatterFunction.DIV, '/').pair, + StringConstantCodec(IMatterFunction.MUL, '*').pair, + StringConstantCodec(IMatterFunction.AT_LEAST, '>').pair, + StringConstantCodec(IMatterFunction.AT_MOST, '<').pair, + StringConstantCodec(IMatterFunction.MINUS, '-').pair, + StringConstantCodec(IMatterFunction.MOD, '%').pair, + StringConstantCodec(IMatterFunction.REPLACE, '^').pair, + constantCodecMatter, + constantCodecMatter2, + constantCodecComplexity, + constantCodecComplexity2, + constantCodecConcise, + constantCodecFunctions, + constantCodecValues, + constantCodecFull, + ) } private val valueCodecConcise: Codec by lazy { @@ -140,7 +226,7 @@ class ComputeAction( it.group( TargetCodec.fieldOf("id").forGetter(ComputeAction::id), Codec.list(Codec - .either(ActualConstantCodec, ActualValueCodec) + .either(constantCodec, ActualValueCodec) .xmap({ it.map({ it }, { it }) }, { if (it is Constant) Either.left(it) else Either.right(it as Value) })) .fieldOf("values") .forGetter(ComputeAction::values), @@ -179,12 +265,6 @@ class ComputeAction( matterFunction: IMatterFunction, complexityFunction: IMatterFunction = matterFunction, ) : Source(matterFunction, complexityFunction) { - constructor( - value: Decimal, - matterFunction: IMatterFunction, - complexityFunction: IMatterFunction = matterFunction - ) : this(value, value.toDouble(), matterFunction, complexityFunction) - constructor( value: Double, matterFunction: IMatterFunction,