diff --git a/src/main/java/io/wispforest/owo/serialization/CodecUtils.java b/src/main/java/io/wispforest/owo/serialization/CodecUtils.java index 4a2ddd9d..0205f43e 100644 --- a/src/main/java/io/wispforest/owo/serialization/CodecUtils.java +++ b/src/main/java/io/wispforest/owo/serialization/CodecUtils.java @@ -1,29 +1,48 @@ package io.wispforest.owo.serialization; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.*; import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; import io.wispforest.endec.*; import io.wispforest.endec.format.bytebuf.ByteBufDeserializer; import io.wispforest.endec.format.bytebuf.ByteBufSerializer; import io.wispforest.endec.format.edm.*; +import io.wispforest.endec.format.forwarding.ForwardingDeserializer; +import io.wispforest.endec.format.forwarding.ForwardingSerializer; +import io.wispforest.endec.format.gson.GsonDeserializer; +import io.wispforest.endec.format.gson.GsonEndec; +import io.wispforest.endec.format.gson.GsonSerializer; import io.wispforest.owo.mixin.ForwardingDynamicOpsAccessor; import io.wispforest.owo.mixin.RegistryOpsAccessor; import io.wispforest.owo.serialization.endec.EitherEndec; import io.wispforest.owo.serialization.endec.MinecraftEndecs; +import io.wispforest.owo.serialization.format.ContextHolder; +import io.wispforest.owo.serialization.format.DynamicOpsWithContext; import io.wispforest.owo.serialization.format.edm.EdmOps; +import io.wispforest.owo.serialization.format.nbt.NbtDeserializer; +import io.wispforest.owo.serialization.format.nbt.NbtEndec; +import io.wispforest.owo.serialization.format.nbt.NbtSerializer; import io.wispforest.owo.util.Scary; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.minecraft.nbt.NbtCompound; +import net.minecraft.nbt.NbtElement; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.NbtString; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.RegistryByteBuf; import net.minecraft.network.codec.PacketCodec; import net.minecraft.registry.RegistryOps; import net.minecraft.util.dynamic.ForwardingDynamicOps; import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; @@ -46,71 +65,91 @@ public class CodecUtils { * written into the serializer */ public static Endec toEndec(Codec codec) { - return Endec.of( - (ctx, serializer, value) -> { - var ops = createEdmOps(ctx); - - EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new)); - }, - (ctx, deserializer) -> { - var ops = createEdmOps(ctx); + return Endec.of(encoderOfCodec(codec), decoderOfCodec(codec)); + } - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); - } - ); + private static Endec.Encoder encoderOfCodec(Codec codec) { + return (ctx, serializer, value) -> encodeWithCodecIntoSerializer(codec, value, serializer, ctx); } - public static Endec toEndec(Codec codec, PacketCodec packetCodec) { - return Endec.of( - (ctx, serializer, value) -> { - if (serializer instanceof ByteBufSerializer) { - var buffer = PacketByteBufs.create(); - packetCodec.encode(buffer, value); + private static void encodeWithCodecIntoSerializer(Codec codec, T value, Serializer serializer, SerializationContext ctx) { + var unpackedSerializer = unpackSerializer(serializer); + var pair = getOpsAndAdapter(unpackedSerializer, ctx); - MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); - return; - } + if (pair == null || !(unpackedSerializer instanceof SelfDescribedSerializer selfDescribedSerializer)) { + EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(createEdmOps(ctx), value).getOrThrow()); + } else { + var ops = pair.getFirst(); + var adapter = pair.getSecond(); - var ops = createEdmOps(ctx); + encodeValue(adapter, selfDescribedSerializer, codec.encodeStart(ops, value).getOrThrow()); + } + } - EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new)); - }, - (ctx, deserializer) -> { - if (deserializer instanceof ByteBufDeserializer) { - var buffer = MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer); - return packetCodec.decode(buffer); - } + private static Endec.Decoder decoderOfCodec(Codec codec) { + return (ctx, deserializer) -> decodeWithCodecFromDeserializer(codec, deserializer, ctx); + } - var ops = createEdmOps(ctx); + private static T decodeWithCodecFromDeserializer(Codec codec, Deserializer deserializer, SerializationContext ctx) { + var unpackedDeserializer = unpackDeserializer(deserializer); + var pair = CodecUtils.getOpsAndAdapter(unpackedDeserializer, ctx); + + return pair == null || !(unpackedDeserializer instanceof SelfDescribedDeserializer selfDescribedDeserializer) + ? codec.parse(createEdmOps(ctx), EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow() + : codec.parse(pair.getFirst(), copyDecodedValue(pair.getSecond(), selfDescribedDeserializer)).getOrThrow(); + } + + public static Endec toEndec(Codec codec, PacketCodec packetCodec) { + var encoder = encoderOfCodec(codec); + var decoder = decoderOfCodec(codec); - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); + return Endec.of( + (ctx, serializer, value) -> { + if (serializer instanceof ByteBufSerializer) { + var buffer = new PacketByteBuf(Unpooled.buffer()); + packetCodec.encode(buffer, value); + MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); + } else { + encoder.encode(ctx, serializer, value); + } + }, + (ctx, deserializer) -> { + if (deserializer instanceof ByteBufDeserializer) { + return packetCodec.decode(MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer)); + } else { + return decoder.decode(ctx, deserializer); } + } ); } public static Endec toEndecWithRegistries(Codec codec, PacketCodec packetCodec) { - return Endec.of( - (ctx, serializer, value) -> { - if (serializer instanceof ByteBufSerializer) { - var buffer = new RegistryByteBuf(PacketByteBufs.create(), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager()); - packetCodec.encode(buffer, value); + var encoder = encoderOfCodec(codec); + var decoder = decoderOfCodec(codec); - MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); - return; - } + return Endec.of( + (ctx, serializer, value) -> { + if (serializer instanceof ByteBufSerializer) { + var buffer = new RegistryByteBuf(new PacketByteBuf(Unpooled.buffer()), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager()); - var ops = RegistryOps.of(EdmOps.withContext(ctx), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); - EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new)); - }, - (ctx, deserializer) -> { - if (deserializer instanceof ByteBufDeserializer) { - var buffer = MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer); - return packetCodec.decode(new RegistryByteBuf(buffer, ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager())); - } + packetCodec.encode(buffer, value); - var ops = RegistryOps.of(EdmOps.withContext(ctx), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); + MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); + } else { + encoder.encode(ctx, serializer, value); } + }, + (ctx, deserializer) -> { + if (deserializer instanceof ByteBufDeserializer) { + return packetCodec.decode( + new RegistryByteBuf( + MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer), + ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager() + )); + } else { + return decoder.decode(ctx, deserializer); + } + } ); } @@ -152,20 +191,36 @@ public static Codec toCodec(Endec endec, SerializationContext assumedC @Override public DataResult> decode(DynamicOps ops, D input) { return captureThrows(() -> { - return new Pair<>(endec.decode(createContext(ops, assumedContext), LenientEdmDeserializer.of(ops.convertTo(EdmOps.withoutContext(), input))), input); + var deserializer = deserializerForValue(ops, input); + var context = createContext(ops, assumedContext); + + var decodedValue = (deserializer != null) + ? endec.decode(deserializer.setupContext(context), deserializer) + : endec.decode(context, LenientEdmDeserializer.of(ops.convertTo(EdmOps.withoutContext(), input))); + + return new Pair<>(decodedValue, input); }); } @Override - @SuppressWarnings("unchecked") public DataResult encode(T input, DynamicOps ops, D prefix) { return captureThrows(() -> { - return EdmOps.withoutContext().convertTo(ops, endec.encodeFully(createContext(ops, assumedContext), EdmSerializer::of, input)); + var serializer = serializerForOps(ops); + var context = createContext(ops, assumedContext); + + return (serializer != null) + ? endec.encodeFully(context, () -> serializer, input) + : EdmOps.withoutContext().convertTo(ops, endec.encodeFully(context, EdmSerializer::of, input)); }); } }; } + @Deprecated + public static Codec ofEndec(Endec endec) { + return toCodec(endec); + } + public static Codec toCodec(Endec endec) { return toCodec(endec, SerializationContext.empty()); } @@ -180,16 +235,23 @@ public Stream keys(DynamicOps ops) { @Override public DataResult decode(DynamicOps ops, MapLike input) { return captureThrows(() -> { - var map = new HashMap>(); - input.entries().forEach(pair -> { - map.put( - ops.getStringValue(pair.getFirst()) - .getOrThrow(s -> new IllegalStateException("Unable to parse key: " + s)), + var deserializer = deserializerForMapLike(ops, input); + var context = createContext(ops, assumedContext); + + if (deserializer != null) { + return structEndec.decode(deserializer.setupContext(context), deserializer); + } else { + var map = new HashMap>(); + + input.entries().forEach(pair -> { + map.put( + ops.getStringValue(pair.getFirst()).getOrThrow(s -> new IllegalStateException("Unable to parse key: " + s)), ops.convertTo(EdmOps.withoutContext(), pair.getSecond()) - ); - }); + ); + }); - return structEndec.decode(createContext(ops, assumedContext), LenientEdmDeserializer.of(EdmElement.wrapMap(map))); + return structEndec.decode(context, LenientEdmDeserializer.of(EdmElement.wrapMap(map))); + } }); } @@ -197,15 +259,21 @@ public DataResult decode(DynamicOps ops, MapLike input) { public RecordBuilder encode(T input, DynamicOps ops, RecordBuilder prefix) { try { var context = createContext(ops, assumedContext); + var pair = serializerForRecordBuilder(ops, prefix); - var element = structEndec.encodeFully(context, EdmSerializer::of, input).>>cast(); + if (pair != null) { + var serializer = pair.getFirst(); + return pair.getSecond().apply(structEndec.encodeFully(serializer.setupContext(context), () -> serializer, input)); + } else { + var element = structEndec.encodeFully(context, EdmSerializer::of, input).>>cast(); - var result = prefix; - for (var entry : element.entrySet()) { - result = result.add(entry.getKey(), EdmOps.withoutContext().convertTo(ops, entry.getValue())); - } + var result = prefix; + for (var entry : element.entrySet()) { + result = result.add(entry.getKey(), EdmOps.withoutContext().convertTo(ops, entry.getValue())); + } - return result; + return result; + } } catch (Exception e) { return prefix.withErrorsFrom(DataResult.error(e::getMessage, input)); } @@ -218,7 +286,7 @@ public static MapCodec toMapCodec(StructEndec structEndec) { } /* - * This method overall should be fine but do not expect such tot work always as it could be a problem as + * This method overall should be fine but do not expect such to work always as it could be a problem as * it bypasses certain features about Deserializer API that may be an issue but is low chance for general * cases within Minecraft. * @@ -230,28 +298,49 @@ public static StructEndec toStructEndec(MapCodec mapCodec) { return new StructEndec() { @Override public void encodeStruct(SerializationContext ctx, Serializer serializer, Serializer.Struct struct, T value) { - var ops = createEdmOps(ctx); + this.doStructEncode(ctx, serializer, struct, value); + } + + private void doStructEncode(SerializationContext ctx, Serializer serializer, Serializer.Struct struct, T value) { + var unpackedSerializer = unpackSerializer(serializer); + var pair = getOpsAndAdapter(unpackedSerializer, ctx); + + if (pair == null || !(unpackedSerializer instanceof SelfDescribedSerializer selfDescribedSerializer)) { + var edmOps = createEdmOps(ctx); - var edmMap = mapCodec.encode(value, ops, ops.mapBuilder()).build(ops.emptyMap()) - .getOrThrow(IllegalStateException::new) + var edmMap = mapCodec.encode(value, edmOps, edmOps.mapBuilder()).build(edmOps.emptyMap()) + .getOrThrow() .asMap(); - if(serializer instanceof SelfDescribedSerializer) { - edmMap.value().forEach((s, element) -> struct.field(s, ctx, EdmEndec.INSTANCE, element)); + if (serializer instanceof SelfDescribedSerializer) { + edmMap.value().forEach((s, element) -> struct.field(s, ctx, EdmEndec.INSTANCE, element)); + } else { + struct.field("element", ctx, EdmEndec.MAP, edmMap); + } } else { - struct.field("element", ctx, EdmEndec.MAP, edmMap); + CodecUtils.encodeStruct(pair.getSecond(), pair.getFirst(), selfDescribedSerializer, struct, mapCodec, value); } } @Override public T decodeStruct(SerializationContext ctx, Deserializer deserializer, Deserializer.Struct struct) { - var edmMap = ((deserializer instanceof SelfDescribedDeserializer) + return this.doStructDecode(ctx, deserializer, struct); + } + + private T doStructDecode(SerializationContext ctx, Deserializer deserializer, Deserializer.Struct struct) { + var unpackedDeserializer = unpackDeserializer(deserializer); + var pair = getOpsAndAdapter(unpackedDeserializer, ctx); + + if (pair == null || !(unpackedDeserializer instanceof SelfDescribedDeserializer selfDescribedDeserializer)) { + var edmMap = ((deserializer instanceof SelfDescribedDeserializer) ? EdmEndec.MAP.decode(ctx, deserializer) : struct.field("element", ctx, EdmEndec.MAP)); - var ops = createEdmOps(ctx); - - return mapCodec.decode(ops, ops.getMap(edmMap).getOrThrow(IllegalStateException::new)).getOrThrow(IllegalStateException::new); + var ops = createEdmOps(ctx); + return mapCodec.decode(ops, ops.getMap(edmMap).getOrThrow()).getOrThrow(); + } else { + return CodecUtils.decodeStruct(pair.getSecond(), pair.getFirst(), selfDescribedDeserializer, struct, mapCodec); + } } }; } @@ -268,8 +357,8 @@ public static PacketCodec toPacketCodec(Endec @Override public T decode(B buf) { var ctx = buf instanceof RegistryByteBuf registryByteBuf - ? SerializationContext.attributes(RegistriesAttribute.of(registryByteBuf.getRegistryManager())) - : SerializationContext.empty(); + ? SerializationContext.attributes(RegistriesAttribute.of(registryByteBuf.getRegistryManager())) + : SerializationContext.empty(); return endec.decode(ctx, ByteBufDeserializer.of(buf)); } @@ -277,23 +366,31 @@ public T decode(B buf) { @Override public void encode(B buf, T value) { var ctx = buf instanceof RegistryByteBuf registryByteBuf - ? SerializationContext.attributes(RegistriesAttribute.of(registryByteBuf.getRegistryManager())) - : SerializationContext.empty(); + ? SerializationContext.attributes(RegistriesAttribute.of(registryByteBuf.getRegistryManager())) + : SerializationContext.empty(); endec.encode(ctx, ByteBufSerializer.of(buf), value); } }; } - //-- + // --- - public static SerializationContext createContext(DynamicOps ops, SerializationContext assumedContext) { + private static SerializationContext createContext(DynamicOps ops, SerializationContext assumedContext) { var rootOps = ops; - while (rootOps instanceof ForwardingDynamicOps) rootOps = ((ForwardingDynamicOpsAccessor) rootOps).owo$delegate(); + var context = rootOps instanceof ContextHolder holder + ? holder.capturedContext().and(assumedContext) + : null; - var context = rootOps instanceof EdmOps edmOps - ? edmOps.capturedContext().and(assumedContext) - : assumedContext; + while (rootOps instanceof ForwardingDynamicOps) { + rootOps = ((ForwardingDynamicOpsAccessor) rootOps).owo$delegate(); + + if (context == null && rootOps instanceof ContextHolder holder) { + context = holder.capturedContext().and(assumedContext); + } + } + + if (context == null) context = assumedContext; if (ops instanceof RegistryOps registryOps) { context = context.withAttributes(RegistriesAttribute.tryFromCachedInfoGetter(((RegistryOpsAccessor) registryOps).owo$infoGetter())); @@ -302,7 +399,7 @@ public static SerializationContext createContext(DynamicOps ops, Serializatio return context; } - public static DynamicOps> createEdmOps(SerializationContext ctx) { + private static DynamicOps> createEdmOps(SerializationContext ctx) { DynamicOps> ops = EdmOps.withContext(ctx); if (ctx.hasAttribute(RegistriesAttribute.REGISTRIES)) { @@ -319,4 +416,309 @@ private static DataResult captureThrows(Supplier action) { return DataResult.error(e::getMessage); } } -} + + // --- codec adapter shenanigans ensue --- + + private static final Map>, CodecAdapter> serializerToAdapter = new HashMap<>(); + private static final Map>, CodecAdapter> deserializerToAdapter = new HashMap<>(); + private static final Map>, CodecAdapter> opsToAdapter = new HashMap<>(); + + @ApiStatus.Experimental + public static void registerCodecAdapter(CodecAdapter adapter) { + if (serializerToAdapter.containsKey(adapter.serializerClass())) { + throw new IllegalStateException("Serializer class " + adapter.serializerClass().getSimpleName() + " is already managed by a different codec adapter"); + } + if (deserializerToAdapter.containsKey(adapter.deserializerClass())) { + throw new IllegalStateException("Deserializer class " + adapter.deserializerClass().getSimpleName() + " is already managed by a different codec adapter"); + } + if (opsToAdapter.containsKey(adapter.opsClass())) { + throw new IllegalStateException("DynamicOps class " + adapter.opsClass().getSimpleName() + " is already managed by a different codec adapter"); + } + + serializerToAdapter.put(adapter.serializerClass(), adapter); + deserializerToAdapter.put(adapter.deserializerClass(), adapter); + opsToAdapter.put(adapter.opsClass(), adapter); + } + + private static DynamicOps unpackOps(DynamicOps ops) { + var rootOps = ops; + while (rootOps instanceof ForwardingDynamicOps) rootOps = ((ForwardingDynamicOpsAccessor) rootOps).owo$delegate(); + return rootOps; + } + + private static Serializer unpackSerializer(Serializer serializer) { + var rootSerializer = serializer; + while (rootSerializer instanceof ForwardingSerializer forwardingSerializer) rootSerializer = forwardingSerializer.delegate(); + return rootSerializer; + } + + private static Deserializer unpackDeserializer(Deserializer deserializer) { + var rootDeserializer = deserializer; + while (rootDeserializer instanceof ForwardingDeserializer forwardingDeserializer) rootDeserializer = forwardingDeserializer.delegate(); + return rootDeserializer; + } + + @Nullable + @SuppressWarnings("unchecked") + private static > Pair, CodecAdapter> getOpsAndAdapter(Serializer serializer, SerializationContext ctx) { + var adapter = (CodecAdapter) serializerToAdapter.get(serializer.getClass()); + if (adapter == null) return null; + + DynamicOps ops = DynamicOpsWithContext.of(ctx, adapter.getOps()); + if (ctx.hasAttribute(RegistriesAttribute.REGISTRIES)) { + ops = RegistryOps.of(ops, ctx.getAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); + } + + return new Pair<>(ops, adapter); + } + + @Nullable + @SuppressWarnings("unchecked") + private static > Pair, CodecAdapter> getOpsAndAdapter(Deserializer deserializer, SerializationContext ctx) { + var adapter = (CodecAdapter) deserializerToAdapter.get(deserializer.getClass()); + if (adapter == null) return null; + + DynamicOps ops = DynamicOpsWithContext.of(ctx, adapter.getOps()); + if (ctx.hasAttribute(RegistriesAttribute.REGISTRIES)) { + ops = RegistryOps.of(ops, ctx.getAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); + } + + return new Pair<>(ops, adapter); + } + + @Nullable + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + private static Serializer serializerForOps(DynamicOps dynamicOps) { + var adapter = (CodecAdapter, ?>) opsToAdapter.get(unpackOps(dynamicOps).getClass()); + return adapter != null ? adapter.createSerializer() : null; + } + + @Nullable + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + private static Deserializer deserializerForValue(DynamicOps dynamicOps, T value) { + var adapter = (CodecAdapter>) opsToAdapter.get(unpackOps(dynamicOps).getClass()); + return (adapter != null) ? adapter.createDeserializer(value) : null; + } + + @Nullable + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + private static Pair, Function>> serializerForRecordBuilder(DynamicOps dynamicOps, RecordBuilder builder) { + var adapter = (CodecAdapter, ?>) opsToAdapter.get(unpackOps(dynamicOps).getClass()); + + return (adapter != null) + ? new Pair<>(adapter.createSerializer(), t -> adapter.addToBuilder(t, builder)) + : null; + } + + @Nullable + @SuppressWarnings({"unchecked", "SuspiciousMethodCalls"}) + private static Deserializer deserializerForMapLike(DynamicOps dynamicOps, MapLike mapLike) { + var adapter = (CodecAdapter>) opsToAdapter.get(unpackOps(dynamicOps).getClass()); + + return (adapter != null) + ? adapter.createDeserializer(adapter.unpackMapLike(mapLike)) + : null; + } + + //-- + + private static > void encodeValue(CodecAdapter adapter, S serializer, T value) { + adapter.createDeserializer(value).readAny(SerializationContext.empty(), serializer); + } + + private static > T copyDecodedValue(CodecAdapter adapter, D deserializer) { + var serializer = adapter.createSerializer(); + deserializer.readAny(SerializationContext.empty(), serializer); + return serializer.result(); + } + + private static > void encodeStruct(CodecAdapter adapter, DynamicOps ops, S serializer, Serializer.Struct struct, MapCodec mapCodec, V value) { + var formatValue = mapCodec.encode(value, ops, ops.mapBuilder()).build(ops.emptyMap()).getOrThrow(); + adapter.encodeStruct(SerializationContext.empty(), serializer, struct, formatValue); + } + + private static > V decodeStruct(CodecAdapter adapter, DynamicOps ops, D deserializer, Deserializer.Struct struct, MapCodec mapCodec) { + var formatValue = adapter.copyDecodedStruct(SerializationContext.empty(), deserializer, struct); + return mapCodec.decode(ops, ops.getMap(formatValue).getOrThrow()).getOrThrow(); + } + + public interface CodecAdapter, D extends SelfDescribedDeserializer> { + Class> serializerClass(); + Class> deserializerClass(); + Class> opsClass(); + + // --- + + S createSerializer(); + D createDeserializer(T value); + DynamicOps getOps(); + + // --- + + T unpackMapLike(MapLike mapLike); + RecordBuilder addToBuilder(T value, RecordBuilder builder); + + // --- + + void encodeStruct(SerializationContext ctx, S serializer, Serializer.Struct struct, T value); + T copyDecodedStruct(SerializationContext ctx, D serializer, Deserializer.Struct struct); + } + + static { + registerCodecAdapter(new CodecAdapter() { + @Override + public Class> serializerClass() { + return NbtSerializer.class; + } + + @Override + public Class> deserializerClass() { + return NbtDeserializer.class; + } + + @Override + public Class> opsClass() { + return NbtOps.class; + } + + @Override + public NbtSerializer createSerializer() { + return NbtSerializer.of(); + } + + @Override + public NbtDeserializer createDeserializer(NbtElement value) { + return NbtDeserializer.of(value); + } + + @Override + public DynamicOps getOps() { + return NbtOps.INSTANCE; + } + + @Override + public NbtElement unpackMapLike(MapLike mapLike) { + var compound = new NbtCompound(); + + mapLike.entries().forEach(pairs -> { + var key = pairs.getFirst(); + var value = pairs.getSecond(); + + if (!(key instanceof NbtString primitive)) { + throw new IllegalStateException("Unable to parse key: " + key); + } + + compound.put(primitive.asString(), value); + }); + + return compound; + } + + @Override + public RecordBuilder addToBuilder(NbtElement value, RecordBuilder builder) { + if (!(value instanceof NbtCompound compoundTag)) { + throw new IllegalStateException("Cannot add non-NbtCompound value into record builder: " + value); + } + + var result = builder; + for (var key : compoundTag.getKeys()) { + result = result.add(key, compoundTag.get(key)); + } + + return result; + } + + @Override + public void encodeStruct(SerializationContext ctx, NbtSerializer serializer, Serializer.Struct struct, NbtElement value) { + if (!(value instanceof NbtCompound compoundTag)) { + throw new IllegalStateException("Cannot encode non-NbtCompound value as struct: " + value); + } + + compoundTag.getKeys().forEach(key -> struct.field(key, ctx, NbtEndec.ELEMENT, compoundTag.get(key))); + } + + @Override + public NbtElement copyDecodedStruct(SerializationContext ctx, NbtDeserializer deserializer, Deserializer.Struct struct) { + return NbtEndec.COMPOUND.decode(ctx, deserializer); + } + }); + + registerCodecAdapter(new CodecAdapter() { + @Override + public Class> serializerClass() { + return GsonSerializer.class; + } + + @Override + public Class> deserializerClass() { + return GsonDeserializer.class; + } + + @Override + public Class> opsClass() { + return JsonOps.class; + } + + @Override + public GsonSerializer createSerializer() { + return GsonSerializer.of(); + } + + @Override + public GsonDeserializer createDeserializer(JsonElement value) { + return GsonDeserializer.of(value); + } + + @Override + public DynamicOps getOps() { + return JsonOps.INSTANCE; + } + + @Override + public JsonElement unpackMapLike(MapLike mapLike) { + var jsonObject = new JsonObject(); + + mapLike.entries().forEach(pairs -> { + var key = pairs.getFirst(); + var value = pairs.getSecond(); + + if (!(key instanceof JsonPrimitive primitive && primitive.isString())) { + throw new IllegalStateException("Unable to parse key: " + key); + } + + jsonObject.add(primitive.getAsString(), value); + }); + + return jsonObject; + } + + @Override + public RecordBuilder addToBuilder(JsonElement value, RecordBuilder builder) { + if (!(value instanceof JsonObject jsonObject)) { + throw new IllegalStateException("Cannot add non-JsonObject value into record builder: " + value); + } + + var result = builder; + for (var entry : jsonObject.asMap().entrySet()) { + result = result.add(entry.getKey(), entry.getValue()); + } + + return result; + } + + @Override + public void encodeStruct(SerializationContext ctx, GsonSerializer serializer, Serializer.Struct struct, JsonElement value) { + if (!(value instanceof JsonObject jsonObject)) { + throw new IllegalStateException("Cannot encode non-JsonObject value as struct: " + value); + } + + jsonObject.asMap().forEach((key, element) -> struct.field(key, ctx, GsonEndec.INSTANCE, element)); + } + + @Override + public JsonElement copyDecodedStruct(SerializationContext ctx, GsonDeserializer serializer, Deserializer.Struct struct) { + return GsonEndec.INSTANCE.decode(ctx, serializer); + } + }); + } +} \ No newline at end of file diff --git a/src/main/java/io/wispforest/owo/serialization/format/ContextHolder.java b/src/main/java/io/wispforest/owo/serialization/format/ContextHolder.java new file mode 100644 index 00000000..37f65ee5 --- /dev/null +++ b/src/main/java/io/wispforest/owo/serialization/format/ContextHolder.java @@ -0,0 +1,12 @@ +package io.wispforest.owo.serialization.format; + +import io.wispforest.endec.SerializationContext; + +/** + * A common interface for parts of a serialization infrastructure + * which provide an instance of {@link SerializationContext}. Primarily + * used for attaching context to {@link com.mojang.serialization.DynamicOps} + */ +public interface ContextHolder { + SerializationContext capturedContext(); +} \ No newline at end of file diff --git a/src/main/java/io/wispforest/owo/serialization/format/DynamicOpsWithContext.java b/src/main/java/io/wispforest/owo/serialization/format/DynamicOpsWithContext.java new file mode 100644 index 00000000..4024726b --- /dev/null +++ b/src/main/java/io/wispforest/owo/serialization/format/DynamicOpsWithContext.java @@ -0,0 +1,29 @@ +package io.wispforest.owo.serialization.format; + +import com.mojang.serialization.DynamicOps; +import io.wispforest.endec.SerializationContext; +import net.minecraft.util.dynamic.ForwardingDynamicOps; + +public class DynamicOpsWithContext extends ForwardingDynamicOps implements ContextHolder { + + private final SerializationContext capturedContext; + + protected DynamicOpsWithContext(SerializationContext capturedContext, DynamicOps delegate) { + super(delegate); + + this.capturedContext = capturedContext; + } + + public static DynamicOpsWithContext of(SerializationContext context, DynamicOps delegate) { + return new DynamicOpsWithContext<>(context, delegate); + } + + public static DynamicOpsWithContext ofEmptyContext(DynamicOps delegate) { + return new DynamicOpsWithContext<>(SerializationContext.empty(), delegate); + } + + @Override + public SerializationContext capturedContext() { + return this.capturedContext; + } +} diff --git a/src/main/java/io/wispforest/owo/serialization/format/edm/EdmOps.java b/src/main/java/io/wispforest/owo/serialization/format/edm/EdmOps.java index 88b3182b..722ace67 100644 --- a/src/main/java/io/wispforest/owo/serialization/format/edm/EdmOps.java +++ b/src/main/java/io/wispforest/owo/serialization/format/edm/EdmOps.java @@ -6,13 +6,14 @@ import com.mojang.serialization.DynamicOps; import io.wispforest.endec.SerializationContext; import io.wispforest.endec.format.edm.EdmElement; +import io.wispforest.owo.serialization.format.ContextHolder; import java.nio.ByteBuffer; import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -public class EdmOps implements DynamicOps> { +public class EdmOps implements DynamicOps>, ContextHolder { private static final EdmOps NO_CONTEXT = new EdmOps(SerializationContext.empty()); @@ -29,6 +30,7 @@ public static EdmOps withoutContext() { return NO_CONTEXT; } + @Override public SerializationContext capturedContext() { return this.capturedContext; }