Moved many block files; work on annotation processor

This commit is contained in:
Anuken
2018-06-06 14:51:25 -04:00
parent 917e2e40fb
commit ccb97e34d5
147 changed files with 685 additions and 520 deletions

View File

@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Goal: To create a system to send events to the server from the client and vice versa.<br>
* Goal: To create a system to send events to the server from the client and vice versa, without creating a new packet type each time.<br>
* These events may optionally also trigger on the caller client/server as well.<br>
*<br>
* Three annotations are used for this purpose.<br>
@@ -22,7 +22,12 @@ public class Annotations {
/**Marks a method as invokable remotely from a server on a client.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface RemoteClient {}
public @interface RemoteClient {
/**Whether a client-specific method is generated that accepts a connecton ID and sends to only one player. Default is false.*/
boolean one() default false;
/**Whether a 'global' method is generated that sends the event to all players. Default is true.*/
boolean all() default true;
}
/**Marks a method as invokable remotely from a client on a server.
* All RemoteServer methods must have their first formal parameter be of type Player.
@@ -42,4 +47,30 @@ public class Annotations {
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface Unreliable{}
/**Specifies that this method will be placed in the class specified by its value.
* Only use constants for this value!*/ //TODO enforce this
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface In{
String value();
}
/**Specifies that this method will be used to write classes of the type returned by {@link #value()}.<br>
* This method must return void and have two parameters, the first being of type {@link java.nio.ByteBuffer} and the second
* being the type returned by {@link #value()}.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface WriteClass {
Class<?> value();
}
/**Specifies that this method will be used to read classes of the type returned by {@link #value()}. <br>
* This method must return the type returned by {@link #value()},
* and have one parameter, being of type {@link java.nio.ByteBuffer}.*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
public @interface ReadClass {
Class<?> value();
}
}

View File

@@ -0,0 +1,15 @@
package io.anuke.annotations;
import java.util.ArrayList;
/**Represents a class witha list method entries to include in it.*/
public class ClassEntry {
/**All methods in this generated class.*/
public final ArrayList<MethodEntry> methods = new ArrayList<>();
/**Simple class name.*/
public final String name;
public ClassEntry(String name) {
this.name = name;
}
}

View File

@@ -0,0 +1,72 @@
package io.anuke.annotations;
import io.anuke.annotations.Annotations.ReadClass;
import io.anuke.annotations.Annotations.WriteClass;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.element.Element;
import javax.tools.Diagnostic.Kind;
import java.util.HashMap;
import java.util.Set;
import java.util.stream.Stream;
/**This class finds reader and writer methods annotated by the {@link io.anuke.annotations.Annotations.WriteClass}
* and {@link io.anuke.annotations.Annotations.ReadClass} annotations.*/
public class IOFinder {
/**Finds all class serializers for all types and returns them. Logs errors when necessary.
* Maps full class names to their serializers.*/
public HashMap<String, ClassSerializer> findSerializers(RoundEnvironment env){
HashMap<String, ClassSerializer> result = new HashMap<>();
//get methods with the types
Set<? extends Element> writers = env.getElementsAnnotatedWith(WriteClass.class);
Set<? extends Element> readers = env.getElementsAnnotatedWith(ReadClass.class);
//look for writers first
for(Element writer : writers){
WriteClass writean = writer.getAnnotation(WriteClass.class);
Class<?> type = writean.value();
//make sure there's only one read method
if(readers.stream().filter(elem -> elem.getAnnotation(ReadClass.class).value() == type).count() > 1){
Utils.messager.printMessage(Kind.ERROR, "Multiple writer methods for type: ", writer);
}
//make sure there's only one write method
Stream<? extends Element> stream = readers.stream().filter(elem -> elem.getAnnotation(ReadClass.class).value() == type);
if(stream.count() == 0){
Utils.messager.printMessage(Kind.ERROR, "Writer method does not have an accompanying reader: ", writer);
}else if(stream.count() > 1){
Utils.messager.printMessage(Kind.ERROR, "Writer method has multiple reader for type: ", writer);
}
Element reader = stream.findFirst().get();
//add to result list
result.put(type.getName(), new ClassSerializer(getFullMethod(reader), getFullMethod(writer), type.getName()));
}
return result;
}
private String getFullMethod(Element element){
return element.getEnclosingElement().asType().toString() + "." + element.getSimpleName();
}
/**Information about read/write methods for a specific class type.*/
public static class ClassSerializer{
/**Fully qualified method name of the reader.*/
public final String readMethod;
/**Fully qualified method name of the writer.*/
public final String writeMethod;
/**Fully qualified class type name.*/
public final String classType;
public ClassSerializer(String readMethod, String writeMethod, String classType) {
this.readMethod = readMethod;
this.writeMethod = writeMethod;
this.classType = classType;
}
}
}

View File

@@ -0,0 +1,23 @@
package io.anuke.annotations;
/**Class that repesents a remote method to be constructed and put into a class.*/
public class MethodEntry {
/**Simple target class name.*/
public final String className;
/**Fully qualified target method to call.*/
public final String targetMethod;
/**Whether this method can be called on a client/server.*/
public final boolean client, server;
/**Whether an additional 'one' and 'all' method variant is generated. At least one of these must be true.
* Only applicable to client (server-invoked) methods.*/
public final boolean allVariant, oneVariant;
public MethodEntry(String className, String targetMethod, boolean client, boolean server, boolean allVariant, boolean oneVariant) {
this.className = className;
this.targetMethod = targetMethod;
this.client = client;
this.server = server;
this.allVariant = allVariant;
this.oneVariant = oneVariant;
}
}

View File

@@ -5,6 +5,7 @@ import io.anuke.annotations.Annotations.Local;
import io.anuke.annotations.Annotations.RemoteClient;
import io.anuke.annotations.Annotations.RemoteServer;
import io.anuke.annotations.Annotations.Unreliable;
import io.anuke.annotations.IOFinder.ClassSerializer;
import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
@@ -34,60 +35,30 @@ import java.util.Set;
"io.anuke.annotations.Annotations.RemoteClient",
"io.anuke.annotations.Annotations.RemoteServer",
"io.anuke.annotations.Annotations.Local",
"io.anuke.annotations.Annotations.Unreliable"
"io.anuke.annotations.Annotations.Unreliable",
"io.anuke.annotations.Annotations.In",
"io.anuke.annotations.Annotations.WriteClass",
"io.anuke.annotations.Annotations.ReadClass",
})
public class AnnotationProcessor extends AbstractProcessor {
private static final int maxPacketSize = 128;
public class RemoteMethodAnnotationProcessor extends AbstractProcessor {
/**Maximum size of each event packet.*/
private static final int maxPacketSize = 512;
/**Name of the base package to put all the generated classes.*/
private static final String packageClassName = "io.anuke.mindustry.gen";
private static final String clientFullClassName = "io.anuke.mindustry.gen.CallClient";
private static final String serverFullClassName = "io.anuke.mindustry.gen.CallServer";
private static final HashMap<String, String[][]> writeMap = new HashMap<String, String[][]>(){{
put("Player", new String[][]{
{
"rbuffer.putInt(rvalue.id)"
},
{
"rtype rvalue = io.anuke.mindustry.Vars.playerGroup.getByID(rbuffer.getInt())"
}
});
put("Tile", new String[][]{
{
"rbuffer.putInt(rvalue.packedPosition())"
},
{
"rtype rvalue = io.anuke.mindustry.Vars.world.tile(rbuffer.getInt())"
}
});
put("String", new String[][]{
{
"rbuffer.putShort((short)rvalue.getBytes().length)",
"rbuffer.put(rvalue.getBytes())"
},
{
"short __rvalue_length = rbuffer.getShort()",
"byte[] __rvalue_bytes = new byte[__rvalue_length]",
"rbuffer.get(__rvalue_bytes)",
"rtype rvalue = new rtype(__rvalue_bytes)"
}
});
}};
private Types typeUtils;
private Elements elementUtils;
private Filer filer;
private Messager messager;
/**Maps fully qualified class names to serializers.*/
private HashMap<String, ClassSerializer> serializers;
/**Whether the initial round is done.*/
private boolean done;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
typeUtils = processingEnv.getTypeUtils();
elementUtils = processingEnv.getElementUtils();
filer = processingEnv.getFiler();
messager = processingEnv.getMessager();
//put all relevant utils into utils class
Utils.typeUtils = processingEnv.getTypeUtils();
Utils.elementUtils = processingEnv.getElementUtils();
Utils.filer = processingEnv.getFiler();
Utils.messager = processingEnv.getMessager();
}
@Override
@@ -95,13 +66,15 @@ public class AnnotationProcessor extends AbstractProcessor {
if(done) return false;
done = true;
serializers = new IOFinder().findSerializers(roundEnv);
writeElements(roundEnv, clientFullClassName, RemoteClient.class);
writeElements(roundEnv, serverFullClassName, RemoteServer.class);
return true;
}
private void writeElements(RoundEnvironment env, String fullClassName, Class<? extends Annotation> annotation){
private void writeElements(RoundEnvironment env){
try {
boolean client = annotation == RemoteServer.class;
String className = fullClassName.substring(1 + fullClassName.lastIndexOf('.'));
@@ -279,15 +252,14 @@ public class AnnotationProcessor extends AbstractProcessor {
TypeSpec spec = classBuilder.build();
JavaFile.builder(packageName, spec).build().writeTo(filer);
}catch (Exception e){
e.printStackTrace();
throw new RuntimeException(e);
}
}
private boolean isPrimitive(String type){
return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int")
|| type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char");
}
}

View File

@@ -1,4 +0,0 @@
package io.anuke.annotations;
public class Serializers {
}

View File

@@ -0,0 +1,18 @@
package io.anuke.annotations;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
public class Utils {
public static Types typeUtils;
public static Elements elementUtils;
public static Filer filer;
public static Messager messager;
public static boolean isPrimitive(String type){
return type.equals("boolean") || type.equals("byte") || type.equals("short") || type.equals("int")
|| type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char");
}
}