diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricCodecDataProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricCodecDataProvider.java index 86190079b..36d8b5a97 100644 --- a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricCodecDataProvider.java +++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricCodecDataProvider.java @@ -19,38 +19,74 @@ package net.fabricmc.fabric.api.datagen.v1.provider; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import com.google.gson.JsonElement; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; +import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import net.minecraft.data.DataOutput; import net.minecraft.data.DataProvider; import net.minecraft.data.DataWriter; +import net.minecraft.registry.RegistryOps; +import net.minecraft.registry.RegistryWrapper; import net.minecraft.util.Identifier; import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; /** - * Extend this class and implement {@link FabricCodecDataProvider#configure}. + * Extend this class and implement {@link FabricCodecDataProvider#configure(BiConsumer, RegistryWrapper.WrapperLookup)}. * * <p>Register an instance of the class with {@link FabricDataGenerator.Pack#addProvider} in a {@link net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint}. */ public abstract class FabricCodecDataProvider<T> implements DataProvider { private final DataOutput.PathResolver pathResolver; + private final CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture; private final Codec<T> codec; + /** + * @deprecated Please use {@link FabricCodecDataProvider#FabricCodecDataProvider(FabricDataOutput, CompletableFuture, DataOutput.OutputType, String, Codec)}. + */ + @Deprecated() protected FabricCodecDataProvider(FabricDataOutput dataOutput, DataOutput.OutputType outputType, String directoryName, Codec<T> codec) { this.pathResolver = dataOutput.getResolver(outputType, directoryName); + this.registriesFuture = null; + this.codec = codec; + } + + protected FabricCodecDataProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture, DataOutput.OutputType outputType, String directoryName, Codec<T> codec) { + this.pathResolver = dataOutput.getResolver(outputType, directoryName); + this.registriesFuture = Objects.requireNonNull(registriesFuture); this.codec = codec; } @Override public CompletableFuture<?> run(DataWriter writer) { + // TODO: Remove the null check once the deprecated method and constructor are removed. + if (this.registriesFuture != null) { + return this.registriesFuture.thenCompose(lookup -> { + Map<Identifier, JsonElement> entries = new HashMap<>(); + RegistryOps<JsonElement> ops = RegistryOps.of(JsonOps.INSTANCE, lookup); + + BiConsumer<Identifier, T> provider = (id, value) -> { + JsonElement json = this.convert(id, value, ops); + JsonElement existingJson = entries.put(id, json); + + if (existingJson != null) { + throw new IllegalArgumentException("Duplicate entry " + id); + } + }; + + this.configure(provider, lookup); + return this.write(writer, entries); + }); + } + Map<Identifier, JsonElement> entries = new HashMap<>(); BiConsumer<Identifier, T> provider = (id, value) -> { JsonElement json = this.convert(id, value); @@ -69,11 +105,29 @@ public abstract class FabricCodecDataProvider<T> implements DataProvider { * Implement this method to register entries to generate. * * @param provider A consumer that accepts an {@link Identifier} and a value to register. + * @deprecated Please use {@link FabricCodecDataProvider#configure(BiConsumer, RegistryWrapper.WrapperLookup)}. */ - protected abstract void configure(BiConsumer<Identifier, T> provider); + @Deprecated() + protected void configure(BiConsumer<Identifier, T> provider) { + throw new UnsupportedOperationException("Override this method."); + } + + /** + * Implement this method to register entries to generate using a {@link RegistryWrapper.WrapperLookup}. + * @param provider A consumer that accepts an {@link Identifier} and a value to register. + * @param lookup A lookup for registries. + */ + protected void configure(BiConsumer<Identifier, T> provider, RegistryWrapper.WrapperLookup lookup) { + // TODO: Make abstract once the deprecated method is removed. + throw new UnsupportedOperationException("Override this method."); + } private JsonElement convert(Identifier id, T value) { - DataResult<JsonElement> dataResult = this.codec.encodeStart(JsonOps.INSTANCE, value); + return this.convert(id, value, JsonOps.INSTANCE); + } + + private JsonElement convert(Identifier id, T value, DynamicOps<JsonElement> ops) { + DataResult<JsonElement> dataResult = this.codec.encodeStart(ops, value); return dataResult.get() .mapRight(partial -> "Invalid entry %s: %s".formatted(id, partial.message())) .orThrow(); diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java index 559cb3068..d42da1518 100644 --- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java +++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java @@ -34,6 +34,8 @@ import java.util.concurrent.CompletableFuture; import java.util.function.BiConsumer; import java.util.function.Consumer; +import com.mojang.serialization.Codec; +import com.mojang.serialization.codecs.RecordCodecBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -41,7 +43,10 @@ import net.minecraft.advancement.Advancement; import net.minecraft.advancement.AdvancementEntry; import net.minecraft.advancement.AdvancementFrame; import net.minecraft.advancement.criterion.OnKilledCriterion; +import net.minecraft.block.Block; +import net.minecraft.block.BlockKeys; import net.minecraft.block.Blocks; +import net.minecraft.data.DataOutput; import net.minecraft.data.client.BlockStateModelGenerator; import net.minecraft.data.client.ItemModelGenerator; import net.minecraft.data.server.recipe.RecipeExporter; @@ -53,6 +58,9 @@ import net.minecraft.item.Items; import net.minecraft.loot.LootPool; import net.minecraft.loot.LootTable; import net.minecraft.loot.LootTables; +import net.minecraft.loot.condition.BlockStatePropertyLootCondition; +import net.minecraft.loot.condition.LootCondition; +import net.minecraft.loot.condition.LootConditionTypes; import net.minecraft.loot.context.LootContextTypes; import net.minecraft.loot.entry.ItemEntry; import net.minecraft.loot.provider.number.ConstantLootNumberProvider; @@ -60,8 +68,11 @@ import net.minecraft.recipe.Ingredient; import net.minecraft.recipe.book.RecipeCategory; import net.minecraft.registry.Registerable; import net.minecraft.registry.RegistryBuilder; +import net.minecraft.registry.RegistryEntryLookup; import net.minecraft.registry.RegistryKeys; import net.minecraft.registry.RegistryWrapper; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.registry.entry.RegistryFixedCodec; import net.minecraft.registry.tag.BlockTags; import net.minecraft.registry.tag.ItemTags; import net.minecraft.registry.tag.TagKey; @@ -77,6 +88,7 @@ import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; import net.fabricmc.fabric.api.datagen.v1.JsonKeySortOrderCallback; import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider; +import net.fabricmc.fabric.api.datagen.v1.provider.FabricCodecDataProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider; import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider; @@ -110,6 +122,8 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint { pack.addProvider(ExistingEnglishLangProvider::new); pack.addProvider(JapaneseLangProvider::new); pack.addProvider(TestDynamicRegistryProvider::new); + pack.addProvider(TestPredicateProvider::new); + pack.addProvider(TestCustomCodecProvider::new); TestBlockTagProvider blockTagProvider = pack.addProvider(TestBlockTagProvider::new); pack.addProvider((output, registries) -> new TestItemTagProvider(output, registries, blockTagProvider)); @@ -410,4 +424,45 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint { return "Test Dynamic Registry"; } } + + private static class TestPredicateProvider extends FabricCodecDataProvider<LootCondition> { + private TestPredicateProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) { + super(dataOutput, registriesFuture, DataOutput.OutputType.DATA_PACK, "predicates", LootConditionTypes.CODEC); + } + + @Override + protected void configure(BiConsumer<Identifier, LootCondition> provider, RegistryWrapper.WrapperLookup lookup) { + RegistryEntryLookup<Block> blocks = lookup.createRegistryLookup().getOrThrow(RegistryKeys.BLOCK); + provider.accept(new Identifier(MOD_ID, "predicate_test"), BlockStatePropertyLootCondition.builder( + blocks.getOrThrow(BlockKeys.MELON).value()).build()); // Pretend this actually does something and we cannot access the blocks directly + } + + @Override + public String getName() { + return "Predicates"; + } + } + + private static class TestCustomCodecProvider extends FabricCodecDataProvider<TestCustomCodecProvider.Entry> { + private TestCustomCodecProvider(FabricDataOutput dataOutput, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) { + super(dataOutput, registriesFuture, DataOutput.OutputType.DATA_PACK, "biome_entry", Entry.CODEC); + } + + @Override + protected void configure(BiConsumer<Identifier, Entry> provider, RegistryWrapper.WrapperLookup lookup) { + RegistryEntryLookup<Biome> biomes = lookup.createRegistryLookup().getOrThrow(RegistryKeys.BIOME); + provider.accept(new Identifier(MOD_ID, "custom_codec_test"), new Entry(biomes.getOrThrow(BiomeKeys.PLAINS))); + } + + @Override + public String getName() { + return "Codec Test Using Dynamic Registry"; + } + + private record Entry(RegistryEntry<Biome> biome) { + private static final Codec<Entry> CODEC = RecordCodecBuilder.create(instance -> instance.group( + RegistryFixedCodec.of(RegistryKeys.BIOME).fieldOf("biome").forGetter(Entry::biome) + ).apply(instance, Entry::new)); + } + } }