From 7a5a08059cc10ab7465bed0e117897052de098db Mon Sep 17 00:00:00 2001 From: Dragon-Seeker Date: Mon, 16 Sep 2024 17:30:22 -0500 Subject: [PATCH] [endec] Fix issues with Codec <-> Endec interop issues involved with NBT Rewrites logic to fix issues involved with using EDM as Nbt format is so bad that it just has to many problematic edge cases leading to issues with various data objects like optional and lists --- .../owo/serialization/CodecUtils.java | 513 ++++++++++++++---- .../serialization/format/ContextHolder.java | 7 + .../format/ContextedDelegatingOps.java | 29 + .../owo/serialization/format/edm/EdmOps.java | 4 +- 4 files changed, 457 insertions(+), 96 deletions(-) create mode 100644 src/main/java/io/wispforest/owo/serialization/format/ContextHolder.java create mode 100644 src/main/java/io/wispforest/owo/serialization/format/ContextedDelegatingOps.java diff --git a/src/main/java/io/wispforest/owo/serialization/CodecUtils.java b/src/main/java/io/wispforest/owo/serialization/CodecUtils.java index 4a2ddd9d..92b86cfe 100644 --- a/src/main/java/io/wispforest/owo/serialization/CodecUtils.java +++ b/src/main/java/io/wispforest/owo/serialization/CodecUtils.java @@ -1,30 +1,41 @@ package io.wispforest.owo.serialization; -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.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.ContextedDelegatingOps; import io.wispforest.owo.serialization.format.edm.EdmOps; -import io.wispforest.owo.util.Scary; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +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 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.Collector; import java.util.stream.Stream; public class CodecUtils { @@ -46,94 +57,87 @@ public class CodecUtils { * written into the serializer */ public static Endec toEndec(Codec codec) { - return Endec.of( - (ctx, serializer, value) -> { - var ops = createEdmOps(ctx); + return Endec.of(encoderOfCodec(codec), decoderOfCodec(codec)); + } - EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(ops, value).getOrThrow(IllegalStateException::new)); - }, - (ctx, deserializer) -> { - var ops = createEdmOps(ctx); + private static Endec.Encoder encoderOfCodec(Codec codec) { + return (ctx, serializer, value) -> { + var pair = convertToOps(serializer, ctx); - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); - } - ); + if(pair != null) { + setDecodedValueUnsafe(pair.getSecond(), serializer, codec.encodeStart(pair.getFirst(), value).getOrThrow()); + } else { + EdmEndec.INSTANCE.encode(ctx, serializer, codec.encodeStart(createEdmOps(ctx), value).getOrThrow()); + } + }; + } + + private static Endec.Decoder decoderOfCodec(Codec codec) { + return (ctx, deserializer) -> { + var pair = CodecUtils.convertToOps(deserializer, ctx); + + return (pair != null) + ? codec.parse((DynamicOps) pair.getFirst(), getEncodedValueUnsafe(pair.getSecond(), deserializer)).getOrThrow() + : codec.parse(createEdmOps(ctx), EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(); + }; } public static Endec toEndec(Codec codec, PacketCodec packetCodec) { + var encoder = encoderOfCodec(codec); + var decoder = decoderOfCodec(codec); + return Endec.of( (ctx, serializer, value) -> { if (serializer instanceof ByteBufSerializer) { - var buffer = PacketByteBufs.create(); + var buffer = new PacketByteBuf(Unpooled.buffer()); + packetCodec.encode(buffer, value); MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); - return; + } else { + encoder.encode(ctx, serializer, value); } - - var ops = createEdmOps(ctx); - - 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); + return packetCodec.decode(MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer)); + } else { + return decoder.decode(ctx, deserializer); } - - var ops = createEdmOps(ctx); - - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); } ); } public static Endec toEndecWithRegistries(Codec codec, PacketCodec packetCodec) { + var encoder = encoderOfCodec(codec); + var decoder = decoderOfCodec(codec); + return Endec.of( (ctx, serializer, value) -> { if (serializer instanceof ByteBufSerializer) { - var buffer = new RegistryByteBuf(PacketByteBufs.create(), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager()); + var buffer = new RegistryByteBuf(new PacketByteBuf(Unpooled.buffer()), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager()); + packetCodec.encode(buffer, value); MinecraftEndecs.PACKET_BYTE_BUF.encode(ctx, serializer, buffer); - return; + } else { + encoder.encode(ctx, serializer, value); } - - 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())); + return packetCodec.decode( + new RegistryByteBuf( + MinecraftEndecs.PACKET_BYTE_BUF.decode(ctx, deserializer), + ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).registryManager() + )); + } else { + return decoder.decode(ctx, deserializer); } - - var ops = RegistryOps.of(EdmOps.withContext(ctx), ctx.requireAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); - return codec.parse(ops, EdmEndec.INSTANCE.decode(ctx, deserializer)).getOrThrow(IllegalStateException::new); } ); } - /** - * Create an endec which serializes an instance of {@link Either}, using {@code first} - * for the left and {@code second} for the right variant - *

- * In a self-describing format, the serialized representation is simply that of the endec of - * whichever variant is represented. In the general for non-self-described formats, the - * which variant is represented must also be stored - */ - public static Endec> eitherEndec(Endec first, Endec second) { - return new EitherEndec<>(first, second, false); - } - - /** - * Like {@link #eitherEndec(Endec, Endec)}, but ensures when decoding from a self-described format - * that only {@code first} or {@code second}, but not both, succeed - */ - public static Endec> xorEndec(Endec first, Endec second) { - return new EitherEndec<>(first, second, true); - } - //-- /** @@ -152,20 +156,38 @@ 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 = convertToDeserializer(ops, input); + + var context = createContext(ops, assumedContext); + + T 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 = convertToSerializer(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 +202,25 @@ 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)), - ops.convertTo(EdmOps.withoutContext(), pair.getSecond()) - ); - }); + var deserializer = convertToDeserializerStruct(ops, input); + + var context = createContext(ops, assumedContext); - return structEndec.decode(createContext(ops, assumedContext), LenientEdmDeserializer.of(EdmElement.wrapMap(map))); + 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(context, LenientEdmDeserializer.of(EdmElement.wrapMap(map))); + } }); } @@ -198,14 +229,22 @@ public RecordBuilder encode(T input, DynamicOps ops, RecordBuilder< try { var context = createContext(ops, assumedContext); - var element = structEndec.encodeFully(context, EdmSerializer::of, input).>>cast(); + var pair = convertToSerializerStruct(ops, prefix); - var result = prefix; - for (var entry : element.entrySet()) { - result = result.add(entry.getKey(), EdmOps.withoutContext().convertTo(ops, entry.getValue())); - } + 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())); + } - return result; + return result; + } } catch (Exception e) { return prefix.withErrorsFrom(DataResult.error(e::getMessage, input)); } @@ -224,34 +263,46 @@ public static MapCodec toMapCodec(StructEndec structEndec) { * * blodhgarm: 21.07.2024 */ - @Scary + //@Scary @ApiStatus.Experimental 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); - - var edmMap = mapCodec.encode(value, ops, ops.mapBuilder()).build(ops.emptyMap()) - .getOrThrow(IllegalStateException::new) - .asMap(); + var pair = convertToOps(serializer, ctx); - if(serializer instanceof SelfDescribedSerializer) { - edmMap.value().forEach((s, element) -> struct.field(s, ctx, EdmEndec.INSTANCE, element)); + if(pair != null) { + setEncodedValueStructUnsafe(pair.getSecond(), pair.getFirst(), serializer, struct, mapCodec, value); } else { - struct.field("element", ctx, EdmEndec.MAP, edmMap); + var edmOps = createEdmOps(ctx); + + 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)); + } else { + struct.field("element", ctx, EdmEndec.MAP, edmMap); + } } } @Override public T decodeStruct(SerializationContext ctx, Deserializer deserializer, Deserializer.Struct struct) { - var edmMap = ((deserializer instanceof SelfDescribedDeserializer) - ? EdmEndec.MAP.decode(ctx, deserializer) - : struct.field("element", ctx, EdmEndec.MAP)); + var pair = convertToOps(deserializer, ctx); + + if(pair != null) { + return getEncodedValueStructUnsafe(pair.getSecond(), pair.getFirst(), deserializer, struct, mapCodec); + } else { + var edmMap = ((deserializer instanceof SelfDescribedDeserializer) + ? EdmEndec.MAP.decode(ctx, deserializer) + : struct.field("element", ctx, EdmEndec.MAP)); - var ops = createEdmOps(ctx); + var ops = createEdmOps(ctx); - return mapCodec.decode(ops, ops.getMap(edmMap).getOrThrow(IllegalStateException::new)).getOrThrow(IllegalStateException::new); + return mapCodec.decode(ops, ops.getMap(edmMap).getOrThrow()).getOrThrow(); + } } }; } @@ -287,13 +338,20 @@ public void encode(B buf, T 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 EdmOps edmOps - ? edmOps.capturedContext().and(assumedContext) - : assumedContext; + var context = rootOps instanceof ContextHolder holder ? holder.capturedContext().and(assumedContext) : null; + + 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 +360,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 +377,269 @@ private static DataResult captureThrows(Supplier action) { return DataResult.error(e::getMessage); } } -} + + //-- + + private static final Map>, CodecInteropBinding> serializerToBinding = new HashMap<>(); + private static final Map>, CodecInteropBinding> deserializerToBinding = new HashMap<>(); + private static final Map>, CodecInteropBinding> opsToBinding = new HashMap<>(); + + public static void registerInteropBinding(CodecInteropBinding binding) { + if (serializerToBinding.containsKey(binding.serializerClass())) { + throw new IllegalStateException("Unable to add the given CodecInteropBinding as the given Serializer clazz was already added by another! [Class: " + binding.serializerClass() + "]"); + } + if (deserializerToBinding.containsKey(binding.deserializerClass())) { + throw new IllegalStateException("Unable to add the given CodecInteropBinding as the given Deserializer clazz was already added by another! [Class: " + binding.deserializerClass() + "]"); + } + if (opsToBinding.containsKey(binding.opsClass())) { + throw new IllegalStateException("Unable to add the given CodecInteropBinding as the given DynamicOps clazz was already added by another! [Class: " + binding.opsClass() + "]"); + } + + serializerToBinding.put(binding.serializerClass(), binding); + deserializerToBinding.put(binding.deserializerClass(), binding); + opsToBinding.put(binding.opsClass(), binding); + } + + 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 + private static Pair, CodecInteropBinding> convertToOps(Serializer serializer, SerializationContext ctx) { + var bindings = serializerToBinding.get(unpackSerializer(serializer).getClass()); + + if(bindings != null) { + DynamicOps ops = ContextedDelegatingOps.withContext(ctx, ((CodecInteropBinding) bindings).getOps()); + + if (ctx.hasAttribute(RegistriesAttribute.REGISTRIES)) { + ops = RegistryOps.of(ops, ctx.getAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); + } + + return new Pair<>(ops, (CodecInteropBinding) bindings); + } + + return null; + } + + @Nullable + private static Pair, CodecInteropBinding> convertToOps(Deserializer deserializer, SerializationContext ctx) { + var bindings = deserializerToBinding.get(unpackDeserializer(deserializer).getClass()); + + if (bindings != null) { + DynamicOps ops = ContextedDelegatingOps.withContext(ctx, ((CodecInteropBinding) bindings).getOps()); + + if (ctx.hasAttribute(RegistriesAttribute.REGISTRIES)) { + ops = RegistryOps.of(ops, ctx.getAttributeValue(RegistriesAttribute.REGISTRIES).infoGetter()); + } + + return new Pair<>(ops, (CodecInteropBinding) bindings); + } + + return null; + } + + @Nullable + private static Deserializer convertToDeserializer(DynamicOps dynamicOps, T t) { + var bindings = opsToBinding.get(unpackOps(dynamicOps).getClass()); + + return (bindings != null) ? ((CodecInteropBinding) bindings).createDeserializer(t) : null; + } + + @Nullable + private static Deserializer convertToDeserializerStruct(DynamicOps dynamicOps, MapLike mapLike) { + var bindings = opsToBinding.get(unpackOps(dynamicOps).getClass()); + + return (bindings != null) + ? ((CodecInteropBinding) bindings).createDeserializer(((CodecInteropBinding) bindings).convertMapLike(mapLike)) + : null; + } + + @Nullable + private static Pair, Function>> convertToSerializerStruct(DynamicOps dynamicOps, RecordBuilder builder) { + var bindings = opsToBinding.get(unpackOps(dynamicOps).getClass()); + + return (bindings != null) + ? new Pair<>(((CodecInteropBinding) bindings).createSerializer(), t -> ((CodecInteropBinding) bindings).addToBuilder(t, builder)) + : null; + } + + @Nullable + private static Serializer convertToSerializer(DynamicOps dynamicOps) { + var bindings = opsToBinding.get(unpackOps(dynamicOps).getClass()); + + return (bindings != null) ? ((CodecInteropBinding) bindings).createSerializer() : null; + } + + //-- + + private static void setDecodedValueUnsafe(CodecInteropBinding binding, Serializer serializer, Object t) { + try { + binding.setEncodedValue((Serializer) serializer, (T) t); + } catch (ClassCastException e) { + throw new IllegalStateException("Unable to set the given encoded value into the passed Serializer as its not the correct type!", e); + } + } + + private static T getEncodedValueUnsafe(CodecInteropBinding binding, Deserializer deserializer) { + try { + return binding.getEncodedValue((Deserializer) deserializer); + } catch (ClassCastException e) { + throw new IllegalStateException("Unable to get the given encoded value from the passed Deserializer as its not the correct type!", e); + } + } + + private static void setEncodedValueStructUnsafe(CodecInteropBinding binding, DynamicOps ops, Serializer serializer, Serializer.Struct struct, MapCodec mapCodec, V v) { + try { + var typedOps = (DynamicOps) ops; + var t = mapCodec.encode(v, typedOps, typedOps.mapBuilder()).build(typedOps.emptyMap()).getOrThrow(); + + binding.setEncodedValueStruct(SerializationContext.empty(), serializer, struct, t); + } catch (ClassCastException e) { + throw new IllegalStateException("Unable to set the given encoded value into the passed Struct Serializer as its not the correct type!", e); + } + } + + private static V getEncodedValueStructUnsafe(CodecInteropBinding binding, DynamicOps ops, Deserializer deserializer, Deserializer.Struct struct, MapCodec mapCodec) { + try { + var typedOps = (DynamicOps) ops; + var t = binding.getEncodedValueStruct(SerializationContext.empty(), deserializer, struct); + + return mapCodec.decode(typedOps, typedOps.getMap(t).getOrThrow()).getOrThrow(); + } catch (ClassCastException e) { + throw new IllegalStateException("Unable to set the given encoded value into the passed Struct Deserializer as its not the correct type!", e); + } + } + + public interface CodecInteropBinding { + Class> serializerClass(); + Class> deserializerClass(); + Class> opsClass(); + + //-- + + Serializer createSerializer(); + Deserializer createDeserializer(T t); + DynamicOps getOps(); + + //- + + T convertMapLike(MapLike mapLike); + RecordBuilder addToBuilder(T t, RecordBuilder builder); + + //-- + + void setEncodedValue(Serializer serializer, T t); + T getEncodedValue(Deserializer deserializer); + + //-- + + void setEncodedValueStruct(SerializationContext ctx, Serializer serializer, Serializer.Struct struct, T t); + T getEncodedValueStruct(SerializationContext ctx, Deserializer serializer, Deserializer.Struct struct); + } + + static { + registerInteropBinding(new CodecInteropBinding() { + @Override + public Class> serializerClass() { + return NbtSerializer.class; + } + + @Override + public Class> deserializerClass() { + return NbtDeserializer.class; + } + + @Override + public Class> opsClass() { + return NbtOps.class; + } + + @Override + public Serializer createSerializer() { + return NbtSerializer.of(); + } + + @Override + public Deserializer createDeserializer(NbtElement tag) { + return NbtDeserializer.of(tag); + } + + @Override + public DynamicOps getOps() { + return NbtOps.INSTANCE; + } + + @Override + public NbtElement convertMapLike(MapLike mapLike) { + return mapLike.entries().map(pair -> { + return pair.mapFirst(tag -> { + if(!(tag instanceof NbtString stringTag)) throw new IllegalStateException("Unable to parse key: " + tag); + + return stringTag.asString(); + }); + }).collect(Collector.of(NbtCompound::new, (compound, pair) -> compound.put(pair.getFirst(), pair.getSecond()), NbtCompound::copyFrom)); + } + + @Override + public RecordBuilder addToBuilder(NbtElement tag, RecordBuilder builder) { + if(!(tag instanceof NbtCompound compoundTag)) { + throw new IllegalStateException("Unable to add to builder as the given Tag was not a CompoundTag: " + tag); + } + + var result = builder; + + for (var key : compoundTag.getKeys()) result = result.add(key, compoundTag.get(key)); + + return result; + } + + @Override + public void setEncodedValue(Serializer serializer, NbtElement tag) { + ((SelfDescribedDeserializer) createDeserializer(tag)).readAny(SerializationContext.empty(), serializer); + } + + @Override + public NbtElement getEncodedValue(Deserializer deserializer) { + var serializer = createSerializer(); + + ((SelfDescribedDeserializer) deserializer).readAny(SerializationContext.empty(), serializer); + + return serializer.result(); + } + + @Override + public void setEncodedValueStruct(SerializationContext ctx, Serializer serializer, Serializer.Struct struct, NbtElement tag) { + if(!(tag instanceof NbtCompound compoundTag)) { + throw new IllegalStateException("Unable to add to builder as the given Tag was not a CompoundTag: " + tag); + } + + if (serializer instanceof SelfDescribedSerializer) { + compoundTag.getKeys().forEach(key -> struct.field(key, ctx, NbtEndec.ELEMENT, compoundTag.get(key))); + } else { + struct.field("element", ctx, NbtEndec.COMPOUND, compoundTag); + } + } + + @Override + public NbtElement getEncodedValueStruct(SerializationContext ctx, Deserializer deserializer, Deserializer.Struct struct) { + return ((deserializer instanceof SelfDescribedDeserializer) + ? NbtEndec.COMPOUND.decode(ctx, deserializer) + : struct.field("element", ctx, NbtEndec.ELEMENT)); + } + }); + } +} \ 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..2d8123f1 --- /dev/null +++ b/src/main/java/io/wispforest/owo/serialization/format/ContextHolder.java @@ -0,0 +1,7 @@ +package io.wispforest.owo.serialization.format; + +import io.wispforest.endec.SerializationContext; + +public interface ContextHolder { + SerializationContext capturedContext(); +} \ No newline at end of file diff --git a/src/main/java/io/wispforest/owo/serialization/format/ContextedDelegatingOps.java b/src/main/java/io/wispforest/owo/serialization/format/ContextedDelegatingOps.java new file mode 100644 index 00000000..ceb711cf --- /dev/null +++ b/src/main/java/io/wispforest/owo/serialization/format/ContextedDelegatingOps.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 ContextedDelegatingOps extends ForwardingDynamicOps implements ContextHolder { + + private final SerializationContext capturedContext; + + protected ContextedDelegatingOps(SerializationContext capturedContext, DynamicOps delegate) { + super(delegate); + + this.capturedContext = capturedContext; + } + + public static ContextedDelegatingOps withContext(SerializationContext context, DynamicOps delegate) { + return new ContextedDelegatingOps<>(context, delegate); + } + + public static ContextedDelegatingOps withoutContext(DynamicOps delegate) { + return new ContextedDelegatingOps(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; }