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.network.FriendlyByteBuf
|
||||
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.RecipeSerializer
|
||||
import org.apache.logging.log4j.LogManager
|
||||
@ -20,51 +22,26 @@ import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec
|
||||
import kotlin.collections.ArrayDeque
|
||||
import kotlin.concurrent.getOrSet
|
||||
|
||||
class Codec2RecipeSerializer<S : Recipe<*>> private constructor(
|
||||
class Codec2RecipeSerializer<S : Recipe<*>>(
|
||||
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<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)
|
||||
|
||||
private val codec = codec.invoke(Context())
|
||||
private val id: ArrayDeque<ResourceLocation>
|
||||
get() = idStack.getOrSet { ArrayDeque() }
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private class CurrentContext {
|
||||
val idStack = ArrayDeque<ResourceLocation>()
|
||||
var isNetwork = 0
|
||||
}
|
||||
|
||||
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> {
|
||||
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> {
|
||||
return Codec2RecipeSerializer(empty?.let(to), idStack) { _ ->
|
||||
return Codec2RecipeSerializer(empty?.let(to)) { _ ->
|
||||
codec.xmap(to, from)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fromJson(id: ResourceLocation, data: JsonObject): S {
|
||||
try {
|
||||
this.id.addLast(id)
|
||||
context.idStack.addLast(id)
|
||||
|
||||
return decode(JsonOps.INSTANCE, data).get().map(
|
||||
{
|
||||
it.first
|
||||
},
|
||||
{
|
||||
empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}")
|
||||
}
|
||||
{ it.first },
|
||||
{ empty ?: throw JsonSyntaxException("Failed to deserialize recipe from JSON: ${it.message()}") }
|
||||
)
|
||||
} finally {
|
||||
this.id.removeLast()
|
||||
context.idStack.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
override fun fromNetwork(id: ResourceLocation, data: FriendlyByteBuf): S? {
|
||||
try {
|
||||
this.id.addLast(id)
|
||||
context.idStack.addLast(id)
|
||||
context.isNetwork++
|
||||
|
||||
return data.readBinaryJsonWithCodecIndirect(this)
|
||||
.resultOrPartial { LOGGER.error("Failed to read recipe $id from network: $it") }.orElse(null)
|
||||
} finally {
|
||||
this.id.removeLast()
|
||||
context.isNetwork--
|
||||
context.idStack.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -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 {
|
||||
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 ru.dbotthepony.mc.otm.core.collect.allEqual
|
||||
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.stream
|
||||
import ru.dbotthepony.mc.otm.recipe.IIngredientMatrix
|
||||
import ru.dbotthepony.mc.otm.recipe.IngredientMatrix
|
||||
import java.util.function.Supplier
|
||||
|
||||
object IngredientMatrixCodec : Codec<IIngredientMatrix> {
|
||||
private val ingredientList = Codec.list(IngredientCodec)
|
||||
class IngredientMatrixCodec(ingredientCodec: Codec<Ingredient>) : Codec<IIngredientMatrix> {
|
||||
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> {
|
||||
return DataResult.success(
|
||||
ops.createList(
|
||||
(0 until input.height).stream().map { row ->
|
||||
ops.createList((0 until input.width).stream().map { column ->
|
||||
JsonOps.INSTANCE.convertTo(ops, input[column, row].toJson())
|
||||
})
|
||||
}
|
||||
)
|
||||
return doubleIngredientList.encode(
|
||||
(0 until input.height).iterator().map { row ->
|
||||
(0 until input.width).iterator().map { column ->
|
||||
input[column, row]
|
||||
}.toList()
|
||||
}.toList(),
|
||||
ops,
|
||||
prefix
|
||||
)
|
||||
}
|
||||
|
||||
@ -45,7 +47,7 @@ object IngredientMatrixCodec : Codec<IIngredientMatrix> {
|
||||
.flatXmap(
|
||||
{ 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()) }
|
||||
), IngredientCodec).fieldOf("key").forGetter(Handwritten::key)
|
||||
), ingredientCodec).fieldOf("key").forGetter(Handwritten::key)
|
||||
).apply(it, ::Handwritten)
|
||||
}
|
||||
|
||||
|
@ -143,18 +143,10 @@ open class MatterEntanglerRecipe(
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe>(
|
||||
MatterEntanglerRecipe(
|
||||
ResourceLocation(OverdriveThatMatters.MOD_ID, "null"),
|
||||
IIngredientMatrix.Companion,
|
||||
Decimal.ZERO,
|
||||
0.0,
|
||||
ItemStack.EMPTY,
|
||||
)
|
||||
) { context ->
|
||||
val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe> { context ->
|
||||
RecordCodecBuilder.create {
|
||||
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),
|
||||
Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
|
||||
ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
|
||||
|
@ -113,12 +113,10 @@ class PainterRecipe(
|
||||
}
|
||||
|
||||
companion object {
|
||||
val SERIALIZER = Codec2RecipeSerializer<PainterRecipe>(
|
||||
PainterRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, ItemStack.EMPTY, setOf())
|
||||
) { context ->
|
||||
val SERIALIZER = Codec2RecipeSerializer<PainterRecipe> { context ->
|
||||
RecordCodecBuilder.create {
|
||||
it.group(
|
||||
IngredientCodec.fieldOf("input").forGetter(PainterRecipe::input),
|
||||
context.ingredients.fieldOf("input").forGetter(PainterRecipe::input),
|
||||
ItemStack.CODEC.fieldOf("output").forGetter(PainterRecipe::output),
|
||||
PredicatedCodecList<Map<DyeColor, Int>>(
|
||||
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)
|
||||
|
||||
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 {
|
||||
it.group(
|
||||
IngredientCodec.fieldOf("input").forGetter(PlatePressRecipe::input),
|
||||
IngredientCodec.fieldOf("output").forGetter(PlatePressRecipe::output),
|
||||
context.ingredients.fieldOf("input").forGetter(PlatePressRecipe::input),
|
||||
context.ingredients.fieldOf("output").forGetter(PlatePressRecipe::output),
|
||||
Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count),
|
||||
Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
|
||||
FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
|
||||
|
Loading…
Reference in New Issue
Block a user