Allow FabricCodecDataProvider to access dynamic registries ()

* Add CompletableFuture to FabricCodecDataProvider

* Deprecate the old method and constructor, and fix the style

* Fix the style for real this time

* Add exceptions to the configure methods

* Apply suggestions from code review

---------

Co-authored-by: modmuss <modmuss50@gmail.com>
This commit is contained in:
ErrorCraft 2024-01-28 13:56:09 +01:00 committed by GitHub
parent 707e4d1bfd
commit 5c013344f0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 112 additions and 3 deletions
fabric-data-generation-api-v1/src
main/java/net/fabricmc/fabric/api/datagen/v1/provider
testmod/java/net/fabricmc/fabric/test/datagen

View file

@ -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();

View file

@ -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));
}
}
}