diff --git a/src/main/java/com/mojang/serialization/codecs/BaseMapCodec.java b/src/main/java/com/mojang/serialization/codecs/BaseMapCodec.java index 89835b64..350ff9ce 100644 --- a/src/main/java/com/mojang/serialization/codecs/BaseMapCodec.java +++ b/src/main/java/com/mojang/serialization/codecs/BaseMapCodec.java @@ -11,9 +11,10 @@ import com.mojang.serialization.Lifecycle; import com.mojang.serialization.MapLike; import com.mojang.serialization.RecordBuilder; -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; +import it.unimi.dsi.fastutil.objects.Object2ObjectLinkedOpenHashMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; +import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Stream; @@ -24,10 +25,41 @@ public interface BaseMapCodec { Codec elementCodec(); default DataResult> decode(final DynamicOps ops, final MapLike input) { - final Object2ObjectMap read = new Object2ObjectArrayMap<>(); + final List> pairs = input.entries().toList(); + + final ImmutableMap.Builder builder = ImmutableMap.builder(); + boolean anyDecodeFailure = false; + for (final Pair pair : pairs) { + final Optional k = keyCodec().parse(ops, pair.getFirst()).result(); + final Optional v = elementCodec().parse(ops, pair.getSecond()).result(); + if (k.isPresent() && v.isPresent()) { + builder.put(k.get(), v.get()); + } else { + anyDecodeFailure = true; + break; + } + } + + // failures and duplicates are rare, so it is okay to fall back to linked HashMap + // no failures: best performance + // failure: acceptable performance + if (!anyDecodeFailure) { + try { + return DataResult.success(builder.buildOrThrow()); + } catch (IllegalArgumentException duplicateKey) { + return fallbackDecode(ops, pairs); + } + } + + return fallbackDecode(ops, pairs); + } + + // HashMap fallback to check for failures and duplicates + private DataResult> fallbackDecode(final DynamicOps ops, final List> pairs) { + final Object2ObjectMap read = new Object2ObjectLinkedOpenHashMap<>(); final Stream.Builder> failed = Stream.builder(); - final DataResult result = input.entries().reduce( + final DataResult result = pairs.stream().reduce( DataResult.success(Unit.INSTANCE, Lifecycle.stable()), (r, pair) -> { final DataResult key = keyCodec().parse(ops, pair.getFirst());