diff --git a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt index bd43dd751..16e1830f7 100644 --- a/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt +++ b/src/main/kotlin/ru/dbotthepony/mc/otm/data/Codec2RecipeSerializer.kt @@ -18,16 +18,25 @@ import ru.dbotthepony.mc.otm.core.set import ru.dbotthepony.mc.otm.core.util.readBinaryJsonWithCodecIndirect import ru.dbotthepony.mc.otm.core.util.writeBinaryJsonWithCodec import kotlin.collections.ArrayDeque +import kotlin.concurrent.getOrSet class Codec2RecipeSerializer> private constructor( val empty: S?, - private val id: ArrayDeque, + + /** + * [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>, codec: (Codec2RecipeSerializer.Context) -> Codec, ) : Codec, RecipeSerializer { - constructor(empty: S?, codec: (Codec2RecipeSerializer.Context) -> Codec) : this(empty, ArrayDeque(), codec) + constructor(empty: S?, codec: (Codec2RecipeSerializer.Context) -> Codec) : this(empty, ThreadLocal(), codec) constructor(supplier: (Codec2RecipeSerializer.Context) -> Codec) : this(null, supplier) private val codec = codec.invoke(Context()) + private val id: ArrayDeque + get() = idStack.getOrSet { ArrayDeque() } inner class Context() { val id: ResourceLocation @@ -65,7 +74,7 @@ class Codec2RecipeSerializer> private constructor( } fun > xmap(to: (S) -> O, from: (O) -> S): Codec2RecipeSerializer { - return Codec2RecipeSerializer(empty?.let(to), id) { _ -> + return Codec2RecipeSerializer(empty?.let(to), idStack) { _ -> codec.xmap(to, from) } }