diff --git a/src/main/java/net/minecraftforge/registries/ForgeRegistry.java b/src/main/java/net/minecraftforge/registries/ForgeRegistry.java index 5aefd25d7b..5cfd9986d0 100644 --- a/src/main/java/net/minecraftforge/registries/ForgeRegistry.java +++ b/src/main/java/net/minecraftforge/registries/ForgeRegistry.java @@ -1,6 +1,6 @@ /* * Minecraft Forge - * Copyright (c) 2016-2021. + * Copyright (c) 2016-2022. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -25,6 +25,10 @@ import java.util.Map.Entry; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; import net.minecraftforge.common.util.LogMessageAdapter; import net.minecraftforge.fml.ModLoadingContext; import net.minecraftforge.fml.util.ObfuscationReflectionHelper; @@ -96,6 +100,8 @@ public class ForgeRegistry> implements IForgeRe private final ResourceKey> key; private final RegistryBuilder builder; + private final Codec codec = new RegistryCodec(); + ForgeRegistry(RegistryManager stage, ResourceLocation name, RegistryBuilder builder) { this.name = name; @@ -180,6 +186,12 @@ public class ForgeRegistry> implements IForgeRe return tagFolder; } + @Override + public Codec getCodec() + { + return this.codec; + } + @Override public void registerAll(@SuppressWarnings("unchecked") V... values) { @@ -871,6 +883,45 @@ public class ForgeRegistry> implements IForgeRe return ret; } + private class RegistryCodec implements Codec + { + @Override + public DataResult> decode(DynamicOps ops, T input) + { + if (ops.compressMaps()) + { + return ops.getNumberValue(input).flatMap(n -> + { + int id = n.intValue(); + if (ids.get(id) == null) + { + return DataResult.error("Unknown registry id in " + ForgeRegistry.this.key + ": " + n); + } + V val = ForgeRegistry.this.getValue(id); + return DataResult.success(val); + }).map(v -> Pair.of(v, ops.empty())); + } + else + { + return ResourceLocation.CODEC.decode(ops, input).flatMap(keyValuePair -> !ForgeRegistry.this.containsKey(keyValuePair.getFirst()) + ? DataResult.error("Unknown registry key in " + ForgeRegistry.this.key + ": " + keyValuePair.getFirst()) + : DataResult.success(keyValuePair.mapFirst(ForgeRegistry.this::getValue))); + } + } + + @Override + public DataResult encode(V input, DynamicOps ops, T prefix) + { + ResourceLocation key = getKey(input); + if (key == null) + { + return DataResult.error("Unknown registry element in " + ForgeRegistry.this.key + ": " + input); + } + T toMerge = ops.compressMaps() ? ops.createInt(getID(input)) : ops.createString(key.toString()); + return ops.mergeToPrimitive(prefix, toMerge); + } + } + public static class Snapshot { private static final Comparator sorter = (a,b) -> a.compareNamespaced(b); diff --git a/src/main/java/net/minecraftforge/registries/IForgeRegistry.java b/src/main/java/net/minecraftforge/registries/IForgeRegistry.java index 7bc53ded7b..9e890a88fe 100644 --- a/src/main/java/net/minecraftforge/registries/IForgeRegistry.java +++ b/src/main/java/net/minecraftforge/registries/IForgeRegistry.java @@ -1,6 +1,6 @@ /* * Minecraft Forge - * Copyright (c) 2016-2021. + * Copyright (c) 2016-2022. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -26,6 +26,7 @@ import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import com.mojang.serialization.Codec; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; @@ -56,6 +57,8 @@ public interface IForgeRegistry> extends Iterab @Nonnull Collection getValues(); @Nonnull Set, V>> getEntries(); + @Nonnull Codec getCodec(); + /** * Retrieve the slave map of type T from the registry. * Slave maps are maps which are dependent on registry content in some way. diff --git a/src/test/java/net/minecraftforge/debug/misc/RegistryCodecTest.java b/src/test/java/net/minecraftforge/debug/misc/RegistryCodecTest.java new file mode 100644 index 0000000000..ec947d1ecf --- /dev/null +++ b/src/test/java/net/minecraftforge/debug/misc/RegistryCodecTest.java @@ -0,0 +1,113 @@ +/* + * Minecraft Forge + * Copyright (c) 2016-2022. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.debug.misc; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.Items; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Blocks; +import net.minecraftforge.fml.common.Mod; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.ForgeRegistry; +import net.minecraftforge.registries.IForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * This test mod show a few example usages of {@link IForgeRegistry#getCodec()} to serialize and deserialize {@link IForgeRegistryEntry} to json or nbt. + * There are 4 tested cases : + * 1. json -> Pair + * 2. Pair -> nbt + * 3. Pair -> compressed json + * 4. compressed json -> Pair + * For each test the result will be logged. + */ +@Mod("registry_codec_test") +public class RegistryCodecTest +{ + + private static final Logger LOGGER = LogManager.getLogger("Codec Registry Test"); + + /** + * This Codec can serialize and deserialize a {@code Pair}. + * The resulting JSON (or NBT equivalent) will have this structure: + *
{@code
+     * {
+     *     "block": "block_registry_name",
+     *     "item": "item_registry_name"
+     * }
+     * }
+ */ + private static final Codec> CODEC = RecordCodecBuilder.create(codecInstance -> codecInstance.group( + ForgeRegistries.BLOCKS.getCodec().fieldOf("block").forGetter(Pair::getFirst), + ForgeRegistries.ITEMS.getCodec().fieldOf("item").forGetter(Pair::getSecond) + ).apply(codecInstance, Pair::of)); + + public RegistryCodecTest() + { + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); + } + + public void commonSetup(final FMLCommonSetupEvent event) + { + //Create our Json to decode + JsonObject json = new JsonObject(); + json.addProperty("block", "minecraft:diamond_block"); + json.addProperty("item", "minecraft:diamond_pickaxe"); + + //Decode our Json and log an info in case of success or a warning in case of error + DataResult, JsonElement>> result = CODEC.decode(JsonOps.INSTANCE, json); + result.resultOrPartial(LOGGER::warn).ifPresent(pair -> LOGGER.info("Successfully decoded a diamond block and a diamond pickaxe from json to Block/Item")); + + //Create a Pair to test the serialization of our codec + Pair pair = Pair.of(Blocks.DIAMOND_BLOCK, Items.DIAMOND_PICKAXE); + + //Serialize the Pair to NBT, and log an info in case of success or a warning in case of error + DataResult result2 = CODEC.encodeStart(NbtOps.INSTANCE, pair); + result2.resultOrPartial(LOGGER::warn).ifPresent(tag -> LOGGER.info("Successfully encoded a Pair to a nbt tag: {}", tag)); + + //Serialize the Pair to JSON using the COMPRESSED JsonOps, this will use the int registry id instead of the ResourceLocation one, + //This is not recommended because int IDs can change, so you should not rely on them + DataResult result3 = CODEC.encodeStart(JsonOps.COMPRESSED, pair); + result3.resultOrPartial(LOGGER::warn).ifPresent(compressedJson -> LOGGER.info("Successfully encoded a Pair to a compressed json: {}", compressedJson)); + + //Create a json to decode using numerical IDs, to be decoded by JsonOps.COMPRESSED + JsonArray jsonCompressed = new JsonArray(); + jsonCompressed.add(((ForgeRegistry) ForgeRegistries.BLOCKS).getID(Blocks.DIAMOND_BLOCK)); + jsonCompressed.add(((ForgeRegistry) ForgeRegistries.ITEMS).getID(Items.DIAMOND_PICKAXE)); + + //Decode a compressed json to the corresponding Pair, this time using Codec#parse + DataResult> result4 = CODEC.parse(JsonOps.COMPRESSED, jsonCompressed); + result4.resultOrPartial(LOGGER::warn).ifPresent(pair2 -> LOGGER.info("Successfully decoded a diamond block and a diamond pickaxe from compressed json to Block/Item")); + } +} \ No newline at end of file diff --git a/src/test/resources/META-INF/mods.toml b/src/test/resources/META-INF/mods.toml index ab9f7887f8..30d6a8a5c5 100644 --- a/src/test/resources/META-INF/mods.toml +++ b/src/test/resources/META-INF/mods.toml @@ -190,5 +190,7 @@ license="LGPL v2.1" modId="ingredient_invalidation" [[mods]] modId="client_command_test" +[[mods]] + modId="registry_codec_test" # ADD ABOVE THIS LINE