Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disguise api #10478

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ updatingMinecraft=false
org.gradle.caching=true
org.gradle.parallel=true
org.gradle.vfs.watch=false
org.gradle.jvmargs=-Xmx4096m
2 changes: 2 additions & 0 deletions paper-server-generator.settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Uncomment to enable the 'paper-server-generator' project
// include(":paper-server-generator")
36 changes: 36 additions & 0 deletions paper-server-generator/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import io.papermc.paperweight.PaperweightSourceGeneratorHelper
import io.papermc.paperweight.extension.PaperweightSourceGeneratorExt

plugins {
java
}

plugins.apply(PaperweightSourceGeneratorHelper::class)

extensions.configure(PaperweightSourceGeneratorExt::class) {
atFile.set(projectDir.toPath().resolve("wideners.at").toFile())
}

dependencies {
implementation("com.squareup:javapoet:1.13.0")
implementation(project(":paper-api"))
implementation(project(":paper-api-generator"))
implementation("io.github.classgraph:classgraph:4.8.112")
implementation("org.jetbrains:annotations:24.0.1")
testImplementation("org.junit.jupiter:junit-jupiter:5.10.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.register<JavaExec>("generate") {
dependsOn(tasks.check)
mainClass.set("io.papermc.generator.Main")
classpath(sourceSets.main.map { it.runtimeClasspath })
args(projectDir.toPath().resolve("generated").toString())
}

tasks.test {
useJUnitPlatform()
}

group = "io.papermc.paper"
version = "1.0-SNAPSHOT"

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package io.papermc.generator;

import com.google.common.util.concurrent.MoreExecutors;
import com.mojang.logging.LogUtils;

import io.papermc.generator.types.EntityMetaWatcherGenerator;
import io.papermc.generator.types.EntityTypeToEntityClassGenerator;
import io.papermc.generator.types.SourceGenerator;
import io.papermc.generator.utils.TagCollector;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import net.minecraft.SharedConstants;
import net.minecraft.commands.Commands;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.Registry;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
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 net.minecraft.tags.TagKey;
import net.minecraft.tags.TagLoader;
import net.minecraft.world.flag.FeatureFlags;
import org.apache.commons.io.file.PathUtils;
import org.slf4j.Logger;

public final class Main {

private static final Logger LOGGER = LogUtils.getLogger();
public static final RegistryAccess.Frozen REGISTRY_ACCESS;
public static final Map<TagKey<?>, String> EXPERIMENTAL_TAGS;

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();
final List<Registry.PendingTags<?>> pendingTags = TagLoader.loadTagsForExistingRegistries(resourceManager, layers.getLayer(RegistryLayer.STATIC));
final List<HolderLookup.RegistryLookup<?>> worldGenLayer = TagLoader.buildUpdatedLookups(layers.getAccessForLoading(RegistryLayer.WORLDGEN), pendingTags);
final RegistryAccess.Frozen frozenWorldgenRegistries = RegistryDataLoader.load(resourceManager, worldGenLayer, RegistryDataLoader.WORLDGEN_REGISTRIES);
layers = layers.replaceFrom(RegistryLayer.WORLDGEN, frozenWorldgenRegistries);
REGISTRY_ACCESS = layers.compositeAccess().freeze();
final ReloadableServerResources reloadableServerResources = ReloadableServerResources.loadResources(
resourceManager,
layers,
pendingTags,
FeatureFlags.VANILLA_SET,
Commands.CommandSelection.DEDICATED,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
reloadableServerResources.updateStaticRegistryTags();
EXPERIMENTAL_TAGS = TagCollector.grabExperimental(resourceManager);
}

private Main() {
}

public static void main(final String[] args) {
LOGGER.info("Running SERVER generators...");

SourceGenerator[] SERVER = {
new EntityMetaWatcherGenerator("EntityMetaWatcher", "io.papermc.paper.entity.meta"),
new EntityTypeToEntityClassGenerator("EntityTypeToEntityClass", "io.papermc.paper.entity.meta"),
};

generate(Paths.get(args[0]), SERVER);
}

private static void generate(Path output, SourceGenerator[] generators) {
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);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
package io.papermc.generator.types;

import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.WildcardTypeName;
import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;
import io.papermc.generator.utils.Annotations;
import io.papermc.generator.utils.ReflectionHelper;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import java.util.stream.Collectors;
import javax.lang.model.element.Modifier;
import net.minecraft.network.syncher.EntityDataAccessor;
import net.minecraft.network.syncher.EntityDataSerializer;
import net.minecraft.network.syncher.EntityDataSerializers;
import net.minecraft.world.entity.Entity;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;

@DefaultQualifier(NonNull.class)
public class EntityMetaWatcherGenerator extends SimpleGenerator {

private static final ParameterizedTypeName GENERIC_ENTITY_DATA_SERIALIZER = ParameterizedTypeName.get(ClassName.get(Map.class), ClassName.get(Long.class), ParameterizedTypeName.get(ClassName.get(EntityDataSerializer.class), WildcardTypeName.subtypeOf(Object.class)));
private static final ParameterizedTypeName ENTITY_CLASS = ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class));
private static final ParameterizedTypeName OUTER_MAP_TYPE = ParameterizedTypeName.get(ClassName.get(Map.class), ENTITY_CLASS, GENERIC_ENTITY_DATA_SERIALIZER);

public EntityMetaWatcherGenerator(String className, String packageName) {
super(className, packageName);
}

@Override
protected TypeSpec getTypeSpec() {
Map<EntityDataSerializer<?>, String> dataAccessorStringMap = serializerMap();

List<Class<?>> classes;
try (ScanResult scanResult = new ClassGraph().enableAllInfo().acceptPackages("net.minecraft").scan()) {
classes = scanResult.getSubclasses(net.minecraft.world.entity.Entity.class.getName()).loadClasses();
}

classes = classes.stream()
.filter(clazz -> !java.lang.reflect.Modifier.isAbstract(clazz.getModifiers()))
.toList();

record Pair(Class<?> clazz, List<? extends EntityDataAccessor<?>> metaResults) {}

final List<Pair> list = classes.stream()
.map(clazz -> new Pair(
clazz,
ReflectionHelper.getAllForAllParents(clazz, EntityMetaWatcherGenerator::doFilter)
.stream()
.map(this::createData)
.filter(Objects::nonNull)
.toList()
)
)
.toList();

Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames = new TreeMap<>(Comparator.comparing(Class::getSimpleName));
vanillaNames.putAll(list.stream()
.collect(Collectors.toMap(pair -> pair.clazz, pair -> pair.metaResults)));

TypeSpec.Builder typeBuilder = TypeSpec.classBuilder(this.className)
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addAnnotations(Annotations.CLASS_HEADER);

generateIdAccessorMethods(vanillaNames, dataAccessorStringMap, typeBuilder);
generateClassToTypeMap(typeBuilder, vanillaNames.keySet());
generateIsValidAccessorForEntity(typeBuilder);

return typeBuilder.build();
}

private void generateIsValidAccessorForEntity(TypeSpec.Builder builder) {
var methodBuilder = MethodSpec.methodBuilder("isValidForClass")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL, Modifier.STATIC)
.returns(boolean.class)
.addParameter(ParameterizedTypeName.get(ClassName.get(Class.class), WildcardTypeName.subtypeOf(Entity.class)), "clazz")
.addParameter(ParameterizedTypeName.get(ClassName.get(EntityDataSerializer.class), WildcardTypeName.subtypeOf(Object.class)), "entityDataSerializer")
.addParameter(int.class, "id")
.addStatement("Map<Long, EntityDataSerializer<?>> serializerMap = VALID_ENTITY_META_MAP.get(clazz)")
.beginControlFlow("if(serializerMap == null)")
.addStatement("return false")
.endControlFlow()
.addStatement("var serializer = serializerMap.get(id)")
.addStatement("return serializer != null && serializer == entityDataSerializer");

builder.addMethod(methodBuilder.build());
}

private void generateClassToTypeMap(TypeSpec.Builder typeBuilder, Set<Class<?>> classes){
typeBuilder.addField(
FieldSpec.builder(OUTER_MAP_TYPE, "VALID_ENTITY_META_MAP", Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("initialize()")
.build()
);

MethodSpec.Builder builder = MethodSpec.methodBuilder("initialize")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(OUTER_MAP_TYPE)
.addStatement("$T result = new $T<>()", OUTER_MAP_TYPE, ClassName.get(HashMap.class));

classes.forEach(aClass -> {
String name = StringUtils.uncapitalize(aClass.getSimpleName());
if(!name.isBlank()) {
builder.addStatement("result.put($T.class, $L())", aClass, name);
}
});

typeBuilder.addMethod(builder.addStatement("return $T.copyOf(result)", Map.class).build());
}

private static void generateIdAccessorMethods(Map<Class<?>, List<? extends EntityDataAccessor<?>>> vanillaNames, Map<EntityDataSerializer<?>, String> dataAccessorStringMap, TypeSpec.Builder typeBuilder) {
for (final Map.Entry<Class<?>, List<? extends EntityDataAccessor<?>>> perClassResults : vanillaNames.entrySet()) {
if (perClassResults.getKey().getSimpleName().isBlank()) {
continue;
}
var simpleName = perClassResults.getKey().getSimpleName();

ClassName hashMap = ClassName.get(HashMap.class);

MethodSpec.Builder builder = MethodSpec.methodBuilder(StringUtils.uncapitalize(simpleName))
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.returns(GENERIC_ENTITY_DATA_SERIALIZER)
.addStatement("$T result = new $T<>()", GENERIC_ENTITY_DATA_SERIALIZER, hashMap);

perClassResults.getValue().stream().sorted(Comparator.comparing(EntityDataAccessor::id)).forEach(result -> {
builder.addStatement("result.put($LL, $T.$L)", result.id(), EntityDataSerializers.class, dataAccessorStringMap.get(result.serializer()));
});

var method = builder.addStatement("return $T.copyOf(result)", Map.class)
.build();

typeBuilder.addMethod(method);
}
}

private @Nullable EntityDataAccessor<?> createData(Field field) {
try {
field.setAccessible(true);
return (EntityDataAccessor<?>) field.get(null);
} catch (IllegalAccessException e) {
return null;
}
}

private static boolean doFilter(Field field) {
return java.lang.reflect.Modifier.isStatic(field.getModifiers()) && field.getType().isAssignableFrom(EntityDataAccessor.class);
}

@Override
protected JavaFile.Builder file(JavaFile.Builder builder) {
return builder.skipJavaLangImports(true);
}

private Map<EntityDataSerializer<?>, String> serializerMap(){
return Arrays.stream(EntityDataSerializers.class.getDeclaredFields())
.filter(field -> field.getType() == EntityDataSerializer.class)
.map(field -> {
try {
return Map.entry((EntityDataSerializer<?>)field.get(0), field.getName());
} catch (IllegalAccessException e) {
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
}
Loading
Loading