From c89ec406193e8171f1a2e75159ba54ecaf6d66e4 Mon Sep 17 00:00:00 2001 From: Spottedleaf Date: Sun, 20 Jun 2021 12:58:35 +0100 Subject: [PATCH] Fix Codec log spam Mojang did NOT add dataconverters for world gen configurations that they CHANGED. So, the codec fails to parse old data. This fixes two instances: - IntProvider is new and Mojang did not account for old data. Thankfully, only ColumnPlace needed to be special cased. - TreeConfiguration had changes. Thankfully, they were only renames for one value and thankfully defaults could be provided for two new values (WITHOUT changing behavior). --- patches/server/0714-Fix-Codec-log-spam.patch | 208 +++++++++++++++++++ 1 file changed, 208 insertions(+) create mode 100644 patches/server/0714-Fix-Codec-log-spam.patch diff --git a/patches/server/0714-Fix-Codec-log-spam.patch b/patches/server/0714-Fix-Codec-log-spam.patch new file mode 100644 index 0000000000..61eae51b38 --- /dev/null +++ b/patches/server/0714-Fix-Codec-log-spam.patch @@ -0,0 +1,208 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Spottedleaf +Date: Sat, 19 Jun 2021 10:54:52 -0700 +Subject: [PATCH] Fix Codec log spam + +Mojang did NOT add dataconverters for world gen configurations +that they CHANGED. So, the codec fails to parse old data. + +This fixes two instances: +- IntProvider is new and Mojang did not account for old data. + Thankfully, only ColumnPlace needed to be special cased. +- TreeConfiguration had changes. Thankfully, they were + only renames for one value and thankfully defaults could + be provided for two new values (WITHOUT changing behavior). + +diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java +index 7684f0146fa522287d223d4b3cb1c999458b872e..3d1e1cb8a1ca32e11ccea7e554827cf88dd28c1b 100644 +--- a/src/main/java/net/minecraft/server/MCUtil.java ++++ b/src/main/java/net/minecraft/server/MCUtil.java +@@ -718,4 +718,70 @@ public final class MCUtil { + public static int getTicketLevelFor(net.minecraft.world.level.chunk.ChunkStatus status) { + return net.minecraft.server.level.ChunkMap.MAX_VIEW_DISTANCE + net.minecraft.world.level.chunk.ChunkStatus.getDistance(status); + } ++ ++ public static com.mojang.serialization.MapCodec fieldWithFallbacks(com.mojang.serialization.Codec codec, String name, String ...fallback) { ++ return com.mojang.serialization.MapCodec.of( ++ new com.mojang.serialization.codecs.FieldEncoder<>(name, codec), ++ new FieldFallbackDecoder<>(name, java.util.Arrays.asList(fallback), codec), ++ () -> "FieldFallback[" + name + ": " + codec.toString() + "]" ++ ); ++ } ++ ++ // This is likely a common occurrence, sadly ++ public static final class FieldFallbackDecoder extends com.mojang.serialization.MapDecoder.Implementation { ++ protected final String name; ++ protected final List fallback; ++ private final com.mojang.serialization.Decoder elementCodec; ++ ++ public FieldFallbackDecoder(final String name, final List fallback, final com.mojang.serialization.Decoder elementCodec) { ++ this.name = name; ++ this.fallback = fallback; ++ this.elementCodec = elementCodec; ++ } ++ ++ @Override ++ public com.mojang.serialization.DataResult decode(final com.mojang.serialization.DynamicOps ops, final com.mojang.serialization.MapLike input) { ++ T value = input.get(name); ++ if (value == null) { ++ for (String fall : fallback) { ++ value = input.get(fall); ++ if (value != null) { ++ break; ++ } ++ } ++ if (value == null) { ++ return com.mojang.serialization.DataResult.error("No key " + name + " in " + input); ++ } ++ } ++ return elementCodec.parse(ops, value); ++ } ++ ++ @Override ++ public java.util.stream.Stream keys(final com.mojang.serialization.DynamicOps ops) { ++ return java.util.stream.Stream.of(ops.createString(name)); ++ } ++ ++ @Override ++ public boolean equals(final Object o) { ++ if (this == o) { ++ return true; ++ } ++ if (o == null || getClass() != o.getClass()) { ++ return false; ++ } ++ final FieldFallbackDecoder that = (FieldFallbackDecoder)o; ++ return java.util.Objects.equals(name, that.name) && java.util.Objects.equals(elementCodec, that.elementCodec) ++ && java.util.Objects.equals(fallback, that.fallback); ++ } ++ ++ @Override ++ public int hashCode() { ++ return java.util.Objects.hash(name, fallback, elementCodec); ++ } ++ ++ @Override ++ public String toString() { ++ return "FieldDecoder[" + name + ": " + elementCodec + ']'; ++ } ++ } + } +diff --git a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java +index 020a19cd683dd3779c5116d12b3cdcd3b3ca69b4..c81a0eec12436c10869bfdcc21af09173f438331 100644 +--- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java ++++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java +@@ -9,13 +9,44 @@ import net.minecraft.core.Registry; + + public abstract class IntProvider { + private static final Codec> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec)); +- public static final Codec CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { ++ public static final Codec CODEC_REAL = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { // Paper - used by CODEC below + return either.map(ConstantInt::of, (intProvider) -> { + return intProvider; + }); + }, (intProvider) -> { + return intProvider.getType() == IntProviderType.CONSTANT ? Either.left(((ConstantInt)intProvider).getValue()) : Either.right(intProvider); + }); ++ // Paper start ++ public static final Codec CODEC = new Codec<>() { ++ @Override ++ public DataResult> decode(com.mojang.serialization.DynamicOps ops, T input) { ++ /* ++ UniformInt: ++ count -> { (old format) ++ base, spread ++ } -> {UniformInt} { (new format & type) ++ base, base + spread ++ } */ ++ ++ ++ if (ops.get(input, "base").result().isPresent() && ops.get(input, "spread").result().isPresent()) { ++ // detected old format ++ int base = ops.getNumberValue(ops.get(input, "base").result().get()).result().get().intValue(); ++ int spread = ops.getNumberValue(ops.get(input, "spread").result().get()).result().get().intValue(); ++ return DataResult.success(new com.mojang.datafixers.util.Pair<>(UniformInt.of(base, base + spread), input)); ++ } ++ ++ // not old format, forward to real codec ++ return CODEC_REAL.decode(ops, input); ++ } ++ ++ @Override ++ public DataResult encode(IntProvider input, com.mojang.serialization.DynamicOps ops, T prefix) { ++ // forward to real codec ++ return CODEC_REAL.encode(input, ops, prefix); ++ } ++ }; ++ // Paper end + public static final Codec NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE); + public static final Codec POSITIVE_CODEC = codec(1, Integer.MAX_VALUE); + +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java +index 05bba5410fbd9f8e333584ccbd65a909f3040322..0cac5869be03bbbfb090c00447f75b4183a06781 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java +@@ -10,11 +10,41 @@ import net.minecraft.world.level.LevelAccessor; + import net.minecraft.world.level.block.state.BlockState; + + public class ColumnPlacer extends BlockPlacer { +- public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { ++ public static final Codec CODEC_REAL = RecordCodecBuilder.create((instance) -> { // Paper - used by CODEC below + return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> { + return columnPlacer.size; + })).apply(instance, ColumnPlacer::new); + }); ++ // Paper start ++ public static final Codec CODEC = new Codec<>() { ++ @Override ++ public com.mojang.serialization.DataResult> decode(com.mojang.serialization.DynamicOps ops, T input) { ++ /* ++ Old format: ++ min_size, extra_size -> BiasedToBottomInt(min_size, min_size + extra_size) to place @ root.size ++ */ ++ if (ops.get(input, "min_size").result().isPresent() && ops.get(input, "extra_size").result().isPresent()) { ++ // detected old format ++ int min_size = ops.getNumberValue(ops.get(input, "min_size").result().get()).result().get().intValue(); ++ int extra_size = ops.getNumberValue(ops.get(input, "extra_size").result().get()).result().get().intValue(); ++ return com.mojang.serialization.DataResult.success( ++ new com.mojang.datafixers.util.Pair<>(new ColumnPlacer(net.minecraft.util.valueproviders.BiasedToBottomInt.of( ++ min_size, min_size + extra_size ++ )), input) ++ ); ++ } ++ ++ // not old format, forward to real codec ++ return CODEC_REAL.decode(ops, input); ++ } ++ ++ @Override ++ public com.mojang.serialization.DataResult encode(ColumnPlacer input, com.mojang.serialization.DynamicOps ops, T prefix) { ++ // forward to real codec ++ return CODEC_REAL.encode(input, ops, prefix); ++ } ++ }; ++ // Paper end + private final IntProvider size; + + public ColumnPlacer(IntProvider size) { +diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java +index 5da68897148192905c2747676c1ee2ee649f923f..b990099cf274f8cb0d96c139345cf0bf328affd6 100644 +--- a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java ++++ b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java +@@ -18,13 +18,13 @@ public class TreeConfiguration implements FeatureConfiguration { + return treeConfiguration.trunkProvider; + }), TrunkPlacer.CODEC.fieldOf("trunk_placer").forGetter((treeConfiguration) -> { + return treeConfiguration.trunkPlacer; +- }), BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter((treeConfiguration) -> { ++ }), net.minecraft.server.MCUtil.fieldWithFallbacks(BlockStateProvider.CODEC, "foliage_provider", "leaves_provider").forGetter((treeConfiguration) -> { // Paper - provide fallback for rename + return treeConfiguration.foliageProvider; +- }), BlockStateProvider.CODEC.fieldOf("sapling_provider").forGetter((treeConfiguration) -> { ++ }), BlockStateProvider.CODEC.optionalFieldOf("sapling_provider", new SimpleStateProvider(Blocks.OAK_SAPLING.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide default - it looks like for now this is OK because it's just used to check canSurvive. Same check happens in 1.16.5 for the default we provide - so it should retain behavior... + return treeConfiguration.saplingProvider; + }), FoliagePlacer.CODEC.fieldOf("foliage_placer").forGetter((treeConfiguration) -> { + return treeConfiguration.foliagePlacer; +- }), BlockStateProvider.CODEC.fieldOf("dirt_provider").forGetter((treeConfiguration) -> { ++ }), BlockStateProvider.CODEC.optionalFieldOf("dirt_provider", new SimpleStateProvider(Blocks.DIRT.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide defaults, old data DOES NOT have this key (thankfully ALL OLD DATA used DIRT) + return treeConfiguration.dirtProvider; + }), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> { + return treeConfiguration.minimumSize;