Write ingredients as plain itemstacks if networking
This commit is contained in:
parent
a0b0580bfa
commit
260be58951
@ -11,6 +11,8 @@ import com.mojang.serialization.JsonOps
|
|||||||
import net.minecraft.data.recipes.FinishedRecipe
|
import net.minecraft.data.recipes.FinishedRecipe
|
||||||
import net.minecraft.network.FriendlyByteBuf
|
import net.minecraft.network.FriendlyByteBuf
|
||||||
import net.minecraft.resources.ResourceLocation
|
import net.minecraft.resources.ResourceLocation
|
||||||
|
import net.minecraft.world.item.ItemStack
|
||||||
|
import net.minecraft.world.item.crafting.Ingredient
|
||||||
import net.minecraft.world.item.crafting.Recipe
|
import net.minecraft.world.item.crafting.Recipe
|
||||||
import net.minecraft.world.item.crafting.RecipeSerializer
|
import net.minecraft.world.item.crafting.RecipeSerializer
|
||||||
import org.apache.logging.log4j.LogManager
|
import org.apache.logging.log4j.LogManager
|
||||||
@ -20,51 +22,26 @@ import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
|
|||||||
import kotlin.collections.ArrayDeque
|
import kotlin.collections.ArrayDeque
|
||||||
import kotlin.concurrent.getOrSet
|
import kotlin.concurrent.getOrSet
|
||||||
|
|
||||||
class Codec2RecipeSerializer<S : Recipe<*>> private constructor(
|
class Codec2RecipeSerializer<S : Recipe<*>>(
|
||||||
val empty: S?,
|
val empty: S?,
|
||||||
|
|
||||||
/**
|
|
||||||
* [ThreadLocal] because optimization mods can (and probably should) parallelize recipe deserialization,
|
|
||||||
* since RecipeSerializers are expected to be stateless. [Codec2RecipeSerializer], however, is stateful (threading PoV).
|
|
||||||
* To make it stateless, [ThreadLocal] is used.
|
|
||||||
*/
|
|
||||||
private val idStack: ThreadLocal<ArrayDeque<ResourceLocation>>,
|
|
||||||
codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>,
|
codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>,
|
||||||
) : Codec<S>, RecipeSerializer<S> {
|
) : Codec<S>, RecipeSerializer<S> {
|
||||||
constructor(empty: S?, codec: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(empty, ThreadLocal(), codec)
|
|
||||||
constructor(supplier: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(null, supplier)
|
constructor(supplier: (Codec2RecipeSerializer<S>.Context) -> Codec<S>) : this(null, supplier)
|
||||||
|
|
||||||
private val codec = codec.invoke(Context())
|
private class CurrentContext {
|
||||||
private val id: ArrayDeque<ResourceLocation>
|
val idStack = ArrayDeque<ResourceLocation>()
|
||||||
get() = idStack.getOrSet { ArrayDeque() }
|
var isNetwork = 0
|
||||||
|
|
||||||
inner class Context() {
|
|
||||||
val id: ResourceLocation
|
|
||||||
get() = checkNotNull(this@Codec2RecipeSerializer.id.lastOrNull()) { "Not currently deserializing recipe" }
|
|
||||||
|
|
||||||
fun <O : Recipe<*>> wrap(other: Codec2RecipeSerializer<O>): Codec<O> {
|
|
||||||
return object : Codec<O> {
|
|
||||||
override fun <T : Any> encode(input: O, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
|
||||||
try {
|
|
||||||
other.id.addLast(this@Context.id)
|
|
||||||
return other.encode(input, ops, prefix)
|
|
||||||
} finally {
|
|
||||||
other.id.removeLast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<O, T>> {
|
|
||||||
try {
|
|
||||||
other.id.addLast(this@Context.id)
|
|
||||||
return other.decode(ops, input)
|
|
||||||
} finally {
|
|
||||||
other.id.removeLast()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class Context {
|
||||||
|
val id: ResourceLocation
|
||||||
|
get() = checkNotNull(context.idStack.lastOrNull()) { "Not currently deserializing recipe" }
|
||||||
|
|
||||||
|
val ingredients: Codec<Ingredient> get() = ActualIngredientCodec
|
||||||
|
}
|
||||||
|
|
||||||
|
private val codec = codec.invoke(Context())
|
||||||
|
|
||||||
override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
override fun <T : Any> encode(input: S, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
||||||
return codec.encode(input, ops, prefix)
|
return codec.encode(input, ops, prefix)
|
||||||
}
|
}
|
||||||
@ -74,41 +51,47 @@ class Codec2RecipeSerializer<S : Recipe<*>> private constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun <O : Recipe<*>> xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer<O> {
|
fun <O : Recipe<*>> xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer<O> {
|
||||||
return Codec2RecipeSerializer(empty?.let(to), idStack) { _ ->
|
return Codec2RecipeSerializer(empty?.let(to)) { _ ->
|
||||||
codec.xmap(to, from)
|
codec.xmap(to, from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromJson(id: ResourceLocation, data: JsonObject): S {
|
override fun fromJson(id: ResourceLocation, data: JsonObject): S {
|
||||||
try {
|
try {
|
||||||
this.id.addLast(id)
|
context.idStack.addLast(id)
|
||||||
|
|
||||||
return decode(JsonOps.INSTANCE, data).get().map(
|
return decode(JsonOps.INSTANCE, data).get().map(
|
||||||
{
|
{ it.first },
|
||||||
it.first
|
{ empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}") }
|
||||||
},
|
|
||||||
{
|
|
||||||
empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}")
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
} finally {
|
} finally {
|
||||||
this.id.removeLast()
|
context.idStack.removeLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): S? {
|
override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): S? {
|
||||||
try {
|
try {
|
||||||
this.id.addLast(id)
|
context.idStack.addLast(id)
|
||||||
|
context.isNetwork++
|
||||||
|
|
||||||
return data.readBinaryJsonWithCodecIndirect(this)
|
return data.readBinaryJsonWithCodecIndirect(this)
|
||||||
.resultOrPartial { LOGGER.error("Failed to read recipe $id from network: $it") }.orElse(null)
|
.resultOrPartial { LOGGER.error("Failed to read recipe $id from network: $it") }.orElse(null)
|
||||||
} finally {
|
} finally {
|
||||||
this.id.removeLast()
|
context.isNetwork--
|
||||||
|
context.idStack.removeLast()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toNetwork(data: FriendlyByteBuf, recipe: S) {
|
override fun toNetwork(data: FriendlyByteBuf, recipe: S) {
|
||||||
data.writeBinaryJsonWithCodec(this, recipe)
|
try {
|
||||||
|
context.idStack.addLast(recipe.id)
|
||||||
|
context.isNetwork++
|
||||||
|
|
||||||
|
data.writeBinaryJsonWithCodec(this, recipe)
|
||||||
|
} finally {
|
||||||
|
context.isNetwork--
|
||||||
|
context.idStack.removeLast()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toFinished(recipe: S): FinishedRecipe {
|
fun toFinished(recipe: S): FinishedRecipe {
|
||||||
@ -146,7 +129,35 @@ class Codec2RecipeSerializer<S : Recipe<*>> private constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private object ActualIngredientCodec : Codec<Ingredient> {
|
||||||
|
override fun <T : Any> encode(input: Ingredient, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
||||||
|
return if (context.isNetwork > 0) {
|
||||||
|
networkIngredientCodec.encode(input, ops, prefix)
|
||||||
|
} else {
|
||||||
|
IngredientCodec.encode(input, ops, prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun <T : Any> decode(ops: DynamicOps<T>, input: T): DataResult<Pair<Ingredient, T>> {
|
||||||
|
return if (context.isNetwork > 0) {
|
||||||
|
networkIngredientCodec.decode(ops, input)
|
||||||
|
} else {
|
||||||
|
IngredientCodec.decode(ops, input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val LOGGER = LogManager.getLogger()
|
private val LOGGER = LogManager.getLogger()
|
||||||
|
private val networkIngredientCodec = Codec.list(ItemStack.CODEC).xmap({ Ingredient.of(it.stream()) }, { it.items.toMutableList() })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [ThreadLocal] because optimization mods can (and probably should) parallelize recipe deserialization,
|
||||||
|
* since RecipeSerializers are expected to be stateless. [Codec2RecipeSerializer], however, is stateful (threading PoV).
|
||||||
|
* To make it stateless, [ThreadLocal] is used.
|
||||||
|
*/
|
||||||
|
private val contextHolder = ThreadLocal<CurrentContext>()
|
||||||
|
private val context: CurrentContext
|
||||||
|
get() = contextHolder.getOrSet { CurrentContext() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,24 +9,26 @@ import com.mojang.serialization.codecs.RecordCodecBuilder
|
|||||||
import net.minecraft.world.item.crafting.Ingredient
|
import net.minecraft.world.item.crafting.Ingredient
|
||||||
import ru.dbotthepony.mc.otm.core.collect.allEqual
|
import ru.dbotthepony.mc.otm.core.collect.allEqual
|
||||||
import ru.dbotthepony.mc.otm.core.collect.map
|
import ru.dbotthepony.mc.otm.core.collect.map
|
||||||
|
import ru.dbotthepony.mc.otm.core.collect.toList
|
||||||
import ru.dbotthepony.mc.otm.core.collect.toStream
|
import ru.dbotthepony.mc.otm.core.collect.toStream
|
||||||
import ru.dbotthepony.mc.otm.core.stream
|
import ru.dbotthepony.mc.otm.core.stream
|
||||||
import ru.dbotthepony.mc.otm.recipe.IIngredientMatrix
|
import ru.dbotthepony.mc.otm.recipe.IIngredientMatrix
|
||||||
import ru.dbotthepony.mc.otm.recipe.IngredientMatrix
|
import ru.dbotthepony.mc.otm.recipe.IngredientMatrix
|
||||||
import java.util.function.Supplier
|
import java.util.function.Supplier
|
||||||
|
|
||||||
object IngredientMatrixCodec : Codec<IIngredientMatrix> {
|
class IngredientMatrixCodec(ingredientCodec: Codec<Ingredient>) : Codec<IIngredientMatrix> {
|
||||||
private val ingredientList = Codec.list(IngredientCodec)
|
private val ingredientList = Codec.list(ingredientCodec)
|
||||||
|
private val doubleIngredientList = Codec.list(ingredientList)
|
||||||
|
|
||||||
override fun <T : Any> encode(input: IIngredientMatrix, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
override fun <T : Any> encode(input: IIngredientMatrix, ops: DynamicOps<T>, prefix: T): DataResult<T> {
|
||||||
return DataResult.success(
|
return doubleIngredientList.encode(
|
||||||
ops.createList(
|
(0 until input.height).iterator().map { row ->
|
||||||
(0 until input.height).stream().map { row ->
|
(0 until input.width).iterator().map { column ->
|
||||||
ops.createList((0 until input.width).stream().map { column ->
|
input[column, row]
|
||||||
JsonOps.INSTANCE.convertTo(ops, input[column, row].toJson())
|
}.toList()
|
||||||
})
|
}.toList(),
|
||||||
}
|
ops,
|
||||||
)
|
prefix
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +47,7 @@ object IngredientMatrixCodec : Codec<IIngredientMatrix> {
|
|||||||
.flatXmap(
|
.flatXmap(
|
||||||
{ if (it.length == 1) DataResult.success(it[0]) else DataResult.error { "Ingredient key must be exactly 1 symbol in length, '$it' is invalid" } },
|
{ if (it.length == 1) DataResult.success(it[0]) else DataResult.error { "Ingredient key must be exactly 1 symbol in length, '$it' is invalid" } },
|
||||||
{ DataResult.success(it.toString()) }
|
{ DataResult.success(it.toString()) }
|
||||||
), IngredientCodec).fieldOf("key").forGetter(Handwritten::key)
|
), ingredientCodec).fieldOf("key").forGetter(Handwritten::key)
|
||||||
).apply(it, ::Handwritten)
|
).apply(it, ::Handwritten)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,18 +143,10 @@ open class MatterEntanglerRecipe(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe>(
|
val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe> { context ->
|
||||||
MatterEntanglerRecipe(
|
|
||||||
ResourceLocation(OverdriveThatMatters.MOD_ID, "null"),
|
|
||||||
IIngredientMatrix.Companion,
|
|
||||||
Decimal.ZERO,
|
|
||||||
0.0,
|
|
||||||
ItemStack.EMPTY,
|
|
||||||
)
|
|
||||||
) { context ->
|
|
||||||
RecordCodecBuilder.create {
|
RecordCodecBuilder.create {
|
||||||
it.group(
|
it.group(
|
||||||
IngredientMatrixCodec.fieldOf("ingredients").forGetter(MatterEntanglerRecipe::ingredients),
|
IngredientMatrixCodec(context.ingredients).fieldOf("ingredients").forGetter(MatterEntanglerRecipe::ingredients),
|
||||||
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter),
|
DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter),
|
||||||
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
|
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
|
||||||
ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
|
ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
|
||||||
|
@ -113,12 +113,10 @@ class PainterRecipe(
|
|||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SERIALIZER = Codec2RecipeSerializer<PainterRecipe>(
|
val SERIALIZER = Codec2RecipeSerializer<PainterRecipe> { context ->
|
||||||
PainterRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, ItemStack.EMPTY, setOf())
|
|
||||||
) { context ->
|
|
||||||
RecordCodecBuilder.create {
|
RecordCodecBuilder.create {
|
||||||
it.group(
|
it.group(
|
||||||
IngredientCodec.fieldOf("input").forGetter(PainterRecipe::input),
|
context.ingredients.fieldOf("input").forGetter(PainterRecipe::input),
|
||||||
ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output),
|
ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output),
|
||||||
PredicatedCodecList<Map<DyeColor, Int>>(
|
PredicatedCodecList<Map<DyeColor, Int>>(
|
||||||
DyeColor.CODEC.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 },
|
DyeColor.CODEC.xmap({ mapOf(it to 1) }, { it.keys.first() }) to Predicate { it.keys.size == 1 && it.values.first() == 1 },
|
||||||
|
@ -78,11 +78,11 @@ class PlatePressRecipe(
|
|||||||
fun toFinished() = SERIALIZER.toFinished(this)
|
fun toFinished() = SERIALIZER.toFinished(this)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val SERIALIZER = Codec2RecipeSerializer<PlatePressRecipe>(PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1)) { context ->
|
val SERIALIZER = Codec2RecipeSerializer<PlatePressRecipe> { context ->
|
||||||
RecordCodecBuilder.create {
|
RecordCodecBuilder.create {
|
||||||
it.group(
|
it.group(
|
||||||
IngredientCodec.fieldOf("input").forGetter(PlatePressRecipe::input),
|
context.ingredients.fieldOf("input").forGetter(PlatePressRecipe::input),
|
||||||
IngredientCodec.fieldOf("output").forGetter(PlatePressRecipe::output),
|
context.ingredients.fieldOf("output").forGetter(PlatePressRecipe::output),
|
||||||
Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count),
|
Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count),
|
||||||
Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
|
Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
|
||||||
FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
|
FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
|
||||||
|
Loading…
Reference in New Issue
Block a user