Code Generation for TypedKeys (#9233)

Currently includes generated key holder classes for types
used in the Registry Modification API
This commit is contained in:
Jake Potrebic 2023-11-22 20:56:28 -08:00 committed by GitHub
parent e1cd9e59e5
commit 96d5e6ca48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
456 changed files with 2073 additions and 17 deletions

View file

@ -0,0 +1,85 @@
package io.papermc.generator;
import com.mojang.logging.LogUtils;
import io.papermc.generator.types.GeneratedKeyType;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.paper.registry.RegistryKey;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import net.kyori.adventure.key.Keyed;
import net.minecraft.SharedConstants;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.resources.ResourceKey;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.WorldLoader;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import org.apache.commons.io.file.PathUtils;
import org.bukkit.GameEvent;
import org.bukkit.block.Biome;
import org.bukkit.generator.structure.StructureType;
import org.bukkit.inventory.meta.trim.TrimMaterial;
import org.bukkit.inventory.meta.trim.TrimPattern;
import org.slf4j.Logger;
public final class Main {
private static final Logger LOGGER = LogUtils.getLogger();
public static final RegistryAccess.Frozen REGISTRY_ACCESS;
static {
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
final PackRepository resourceRepository = ServerPacksSource.createVanillaTrustedRepository();
resourceRepository.reload();
final MultiPackResourceManager resourceManager = new MultiPackResourceManager(PackType.SERVER_DATA, resourceRepository.getAvailablePacks().stream().map(Pack::open).toList());
LayeredRegistryAccess<RegistryLayer> layers = RegistryLayer.createRegistryAccess();
layers = WorldLoader.loadAndReplaceLayer(resourceManager, layers, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
}
private static final List<SourceGenerator> GENERATORS = List.of(
simpleKey("GameEventKeys", GameEvent.class, Registries.GAME_EVENT, RegistryKey.GAME_EVENT, true),
simpleKey("BiomeKeys", Biome.class, Registries.BIOME, RegistryKey.BIOME, true),
simpleKey("TrimMaterialKeys", TrimMaterial.class, Registries.TRIM_MATERIAL, RegistryKey.TRIM_MATERIAL, true),
simpleKey("TrimPatternKeys", TrimPattern.class, Registries.TRIM_PATTERN, RegistryKey.TRIM_PATTERN, true),
simpleKey("StructureTypeKeys", StructureType.class, Registries.STRUCTURE_TYPE, RegistryKey.STRUCTURE_TYPE, false)
);
private static <T, A> SourceGenerator simpleKey(final String className, final Class<A> apiType, final ResourceKey<? extends Registry<T>> registryKey, final RegistryKey<A> apiRegistryKey, final boolean publicCreateKeyMethod) {
return new GeneratedKeyType<>(className, apiType, "io.papermc.paper.registry.keys", registryKey, apiRegistryKey, publicCreateKeyMethod);
}
private Main() {
}
public static void main(final String[] args) {
final Path output = Paths.get(args[0]);
try {
if (Files.exists(output)) {
PathUtils.deleteDirectory(output);
}
Files.createDirectories(output);
for (final SourceGenerator generator : GENERATORS) {
generator.writeToFile(output);
}
LOGGER.info("Files written to {}", output.toAbsolutePath());
} catch (final Exception ex) {
throw new RuntimeException(ex);
}
}
}

View file

@ -0,0 +1,24 @@
package io.papermc.generator.types;
import com.squareup.javapoet.AnnotationSpec;
import java.util.List;
import org.bukkit.MinecraftExperimental;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
public final class Annotations {
public static final List<AnnotationSpec> EXPERIMENTAL_ANNOTATIONS = List.of(
AnnotationSpec.builder(ApiStatus.Experimental.class).build(),
AnnotationSpec.builder(MinecraftExperimental.class)
.addMember("value", "$S", "update 1.20")
.build()
);
@ApiStatus.Experimental
public static final AnnotationSpec EXPERIMENTAL_API_ANNOTATION = AnnotationSpec.builder(ApiStatus.Experimental.class).build();
public static final AnnotationSpec NOT_NULL = AnnotationSpec.builder(NotNull.class).build();
private Annotations() {
}
}

View file

@ -0,0 +1,205 @@
package io.papermc.generator.types;
import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import io.papermc.generator.Main;
import io.papermc.generator.utils.CollectingContext;
import io.papermc.paper.generated.GeneratedFrom;
import io.papermc.paper.registry.RegistryKey;
import io.papermc.paper.registry.TypedKey;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import net.kyori.adventure.key.Key;
import net.minecraft.SharedConstants;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistrySetBuilder;
import net.minecraft.resources.ResourceKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;
import static com.squareup.javapoet.TypeSpec.classBuilder;
import static io.papermc.generator.types.Annotations.EXPERIMENTAL_ANNOTATIONS;
import static io.papermc.generator.types.Annotations.EXPERIMENTAL_API_ANNOTATION;
import static io.papermc.generator.types.Annotations.NOT_NULL;
import static java.util.Objects.requireNonNull;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PRIVATE;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
@DefaultQualifier(NonNull.class)
public class GeneratedKeyType<T, A> implements SourceGenerator {
// don't exist anymore
// private static final Map<ResourceKey<? extends Registry<?>>, RegistrySetBuilder.RegistryBootstrap<?>> EXPERIMENTAL_REGISTRY_ENTRIES = UpdateOneTwentyRegistries.BUILDER.entries.stream()
// .collect(Collectors.toMap(RegistrySetBuilder.RegistryStub::key, RegistrySetBuilder.RegistryStub::bootstrap));
private static final Map<ResourceKey<? extends Registry<?>>, RegistrySetBuilder.RegistryBootstrap<?>> EXPERIMENTAL_REGISTRY_ENTRIES = Collections.emptyMap();
private static final Map<RegistryKey<?>, String> REGISTRY_KEY_FIELD_NAMES;
static {
final Map<RegistryKey<?>, String> map = new HashMap<>();
try {
for (final Field field : RegistryKey.class.getFields()) {
if (!Modifier.isStatic(field.getModifiers()) || !Modifier.isFinal(field.getModifiers()) || field.getType() != RegistryKey.class) {
continue;
}
map.put((RegistryKey<?>) field.get(null), field.getName());
}
REGISTRY_KEY_FIELD_NAMES = Map.copyOf(map);
} catch (final ReflectiveOperationException ex) {
throw new RuntimeException(ex);
}
}
private static final AnnotationSpec SUPPRESS_WARNINGS = AnnotationSpec.builder(SuppressWarnings.class)
.addMember("value", "$S", "unused")
.addMember("value", "$S", "SpellCheckingInspection")
.build();
private static final AnnotationSpec GENERATED_FROM = AnnotationSpec.builder(GeneratedFrom.class)
.addMember("value", "$S", SharedConstants.getCurrentVersion().getName())
.build();
private static final String TYPE_JAVADOC = """
Vanilla keys for {@link $T#$L}.
@apiNote The fields provided here are a direct representation of
what is available from the vanilla game source. They may be
changed (including removals) on any Minecraft version
bump, so cross-version compatibility is not provided on the
same level as it is on most of the other API.
""";
private static final String FIELD_JAVADOC = """
{@code $L}
@apiNote This field is version-dependant and may be removed in future Minecraft versions
""";
private static final String CREATE_JAVADOC = """
Creates a key for {@link $T} in a registry.
@param key the value's key in the registry
@return a new typed key
""";
private final String keysClassName;
private final Class<A> apiType;
private final String pkg;
private final ResourceKey<? extends Registry<T>> registryKey;
private final RegistryKey<A> apiRegistryKey;
private final boolean publicCreateKeyMethod;
public GeneratedKeyType(final String keysClassName, final Class<A> apiType, final String pkg, final ResourceKey<? extends Registry<T>> registryKey, final RegistryKey<A> apiRegistryKey, final boolean publicCreateKeyMethod) {
this.keysClassName = keysClassName;
this.apiType = apiType;
this.pkg = pkg;
this.registryKey = registryKey;
this.apiRegistryKey = apiRegistryKey;
this.publicCreateKeyMethod = publicCreateKeyMethod;
}
private MethodSpec.Builder createMethod(final TypeName returnType) {
final TypeName keyType = TypeName.get(Key.class).annotated(NOT_NULL);
final ParameterSpec keyParam = ParameterSpec.builder(keyType, "key", FINAL).build();
final MethodSpec.Builder create = MethodSpec.methodBuilder("create")
.addModifiers(this.publicCreateKeyMethod ? PUBLIC : PRIVATE, STATIC)
.addParameter(keyParam)
.addCode("return $T.create($T.$L, $N);", TypedKey.class, RegistryKey.class, requireNonNull(REGISTRY_KEY_FIELD_NAMES.get(this.apiRegistryKey), "Missing field for " + this.apiRegistryKey), keyParam)
.returns(returnType.annotated(NOT_NULL));
if (this.publicCreateKeyMethod) {
create.addAnnotation(EXPERIMENTAL_API_ANNOTATION); // TODO remove once not experimental
create.addJavadoc(CREATE_JAVADOC, this.apiType);
}
return create;
}
private TypeSpec.Builder keyHolderType() {
return classBuilder(this.keysClassName)
.addModifiers(PUBLIC, FINAL)
.addJavadoc(TYPE_JAVADOC, RegistryKey.class, REGISTRY_KEY_FIELD_NAMES.get(this.apiRegistryKey))
.addAnnotation(SUPPRESS_WARNINGS).addAnnotation(GENERATED_FROM)
.addMethod(MethodSpec.constructorBuilder()
.addModifiers(PRIVATE)
.build()
);
}
protected TypeSpec createTypeSpec() {
final TypeName typedKey = ParameterizedTypeName.get(TypedKey.class, this.apiType);
final TypeSpec.Builder typeBuilder = this.keyHolderType();
typeBuilder.addAnnotation(EXPERIMENTAL_API_ANNOTATION); // TODO experimental API
final MethodSpec.Builder createMethod = this.createMethod(typedKey);
final Registry<T> registry = Main.REGISTRY_ACCESS.registryOrThrow(this.registryKey);
final List<ResourceKey<T>> experimental = this.collectExperimentalKeys(registry);
boolean allExperimental = true;
for (final T value : registry) {
final ResourceKey<T> key = registry.getResourceKey(value).orElseThrow();
final String keyPath = key.location().getPath();
final String fieldName = keyPath.toUpperCase(Locale.ENGLISH).replaceAll("[.-/]", "_"); // replace invalid field name chars
final FieldSpec.Builder fieldBuilder = FieldSpec.builder(typedKey, fieldName, PUBLIC, STATIC, FINAL)
.initializer("$N(key($S))", createMethod.build(), keyPath)
.addJavadoc(FIELD_JAVADOC, key.location().toString());
if (experimental.contains(key)) {
fieldBuilder.addAnnotations(EXPERIMENTAL_ANNOTATIONS);
} else {
allExperimental = false;
}
typeBuilder.addField(fieldBuilder.build());
}
if (allExperimental) {
typeBuilder.addAnnotations(EXPERIMENTAL_ANNOTATIONS);
createMethod.addAnnotations(EXPERIMENTAL_ANNOTATIONS);
}
return typeBuilder.addMethod(createMethod.build()).build();
}
@SuppressWarnings("unchecked")
private List<ResourceKey<T>> collectExperimentalKeys(final Registry<T> registry) {
final RegistrySetBuilder.@Nullable RegistryBootstrap<T> registryBootstrap = (RegistrySetBuilder.RegistryBootstrap<T>) EXPERIMENTAL_REGISTRY_ENTRIES.get(this.registryKey);
if (registryBootstrap == null) {
return Collections.emptyList();
}
final List<ResourceKey<T>> experimental = new ArrayList<>();
final CollectingContext<T> context = new CollectingContext<>(experimental, registry);
registryBootstrap.run(context);
return experimental;
}
protected JavaFile createFile() {
return JavaFile.builder(this.pkg, this.createTypeSpec())
.skipJavaLangImports(true)
.addStaticImport(Key.class, "key")
.indent(" ")
.build();
}
@Override
public final String outputString() {
return this.createFile().toString();
}
@Override
public void writeToFile(final Path parent) throws IOException {
final Path pkgDir = parent.resolve(this.pkg.replace('.', '/'));
Files.createDirectories(pkgDir);
Files.writeString(pkgDir.resolve(this.keysClassName + ".java"), this.outputString(), StandardCharsets.UTF_8);
}
}

View file

@ -0,0 +1,11 @@
package io.papermc.generator.types;
import java.io.IOException;
import java.nio.file.Path;
public interface SourceGenerator {
String outputString();
void writeToFile(Path parent) throws IOException;
}

View file

@ -0,0 +1,28 @@
package io.papermc.generator.utils;
import com.mojang.serialization.Lifecycle;
import io.papermc.generator.Main;
import java.util.List;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderGetter;
import net.minecraft.core.Registry;
import net.minecraft.data.worldgen.BootstapContext;
import net.minecraft.resources.ResourceKey;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
@DefaultQualifier(NonNull.class)
public record CollectingContext<T>(List<ResourceKey<T>> registered,
Registry<T> registry) implements BootstapContext<T> {
@Override
public Holder.Reference<T> register(final ResourceKey<T> resourceKey, final @NonNull T t, final Lifecycle lifecycle) {
this.registered.add(resourceKey);
return Holder.Reference.createStandAlone(this.registry.holderOwner(), resourceKey);
}
@Override
public <S> HolderGetter<S> lookup(final ResourceKey<? extends Registry<? extends S>> resourceKey) {
return Main.REGISTRY_ACCESS.registryOrThrow(resourceKey).asLookup();
}
}