Better IO
This commit is contained in:
@@ -218,21 +218,9 @@ public class Annotations{
|
||||
* 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)
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
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.SOURCE)
|
||||
public @interface ReadClass{
|
||||
Class<?> value();
|
||||
public @interface TypeIOHandler{
|
||||
}
|
||||
|
||||
//endregion
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package mindustry.annotations.entity;
|
||||
|
||||
import arc.util.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.squareup.javapoet.MethodSpec.*;
|
||||
import mindustry.annotations.*;
|
||||
@@ -19,14 +18,14 @@ public class EntityIO{
|
||||
void io(TypeName type, String field) throws Exception{
|
||||
|
||||
if(type.isPrimitive()){
|
||||
s(type.toString(), field);
|
||||
s(type == TypeName.BOOLEAN ? "bool" : type.toString().charAt(0) + "", field);
|
||||
}else if(type.toString().equals("java.lang.String")){
|
||||
s("UTF", field);
|
||||
s("str", field);
|
||||
}else if(instanceOf(type.toString(), "mindustry.ctype.Content")){
|
||||
if(write){
|
||||
s("short", field + ".id");
|
||||
s("s", field + ".id");
|
||||
}else{
|
||||
st(field + " = mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, input.readShort())", BaseProcessor.simpleName(type.toString()).toLowerCase().replace("type", ""));
|
||||
st(field + " = mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type.toString()).toLowerCase().replace("type", ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,9 +44,9 @@ public class EntityIO{
|
||||
|
||||
private void s(String type, String field){
|
||||
if(write){
|
||||
builder.addStatement("output.write$L($L)", Strings.capitalize(type), field);
|
||||
builder.addStatement("write.$L($L)", type, field);
|
||||
}else{
|
||||
builder.addStatement("$L = input.read$L()", field, Strings.capitalize(type));
|
||||
builder.addStatement("$L = read.$L()", field, type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,88 +0,0 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* This class finds reader and writer methods annotated by the {@link WriteClass}
|
||||
* and {@link ReadClass} annotations.
|
||||
*/
|
||||
public class IOFinder{
|
||||
|
||||
/**
|
||||
* Finds all class serializers for all types and returns them. Logs errors when necessary.
|
||||
* Maps fully qualified 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);
|
||||
String typeName = getValue(writean);
|
||||
|
||||
//make sure there's only one read method
|
||||
if(readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count() > 1){
|
||||
BaseProcessor.err("Multiple writer methods for type '" + typeName + "'", writer);
|
||||
}
|
||||
|
||||
//make sure there's only one write method
|
||||
long count = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).count();
|
||||
if(count == 0){
|
||||
BaseProcessor.err("Writer method does not have an accompanying reader: ", writer);
|
||||
}else if(count > 1){
|
||||
BaseProcessor.err("Writer method has multiple reader for type: ", writer);
|
||||
}
|
||||
|
||||
Element reader = readers.stream().filter(elem -> getValue(elem.getAnnotation(ReadClass.class)).equals(typeName)).findFirst().get();
|
||||
|
||||
//add to result list
|
||||
result.put(typeName, new ClassSerializer(BaseProcessor.getMethodName(reader), BaseProcessor.getMethodName(writer), typeName));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private String getValue(WriteClass write){
|
||||
try{
|
||||
Class<?> type = write.value();
|
||||
return type.getName();
|
||||
}catch(MirroredTypeException e){
|
||||
return e.getTypeMirror().toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String getValue(ReadClass read){
|
||||
try{
|
||||
Class<?> type = read.value();
|
||||
return type.getName();
|
||||
}catch(MirroredTypeException e){
|
||||
return e.getTypeMirror().toString();
|
||||
}
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,11 +3,10 @@ package mindustry.annotations.remote;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.remote.IOFinder.*;
|
||||
import mindustry.annotations.remote.TypeIOResolver.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
@@ -32,7 +31,7 @@ public class RemoteProcess extends BaseProcessor{
|
||||
private static final String callLocation = "Call";
|
||||
|
||||
//class serializers
|
||||
private HashMap<String, ClassSerializer> serializers;
|
||||
private ClassSerializer serializer;
|
||||
//all elements with the Remote annotation
|
||||
private Set<? extends Element> elements;
|
||||
//map of all classes to generate by name
|
||||
@@ -51,7 +50,7 @@ public class RemoteProcess extends BaseProcessor{
|
||||
//round 1: find all annotations, generate *writers*
|
||||
if(round == 1){
|
||||
//get serializers
|
||||
serializers = new IOFinder().findSerializers(roundEnv);
|
||||
serializer = TypeIOResolver.resolve(this);
|
||||
//last method ID used
|
||||
int lastMethodID = 0;
|
||||
//find all elements with the Remote annotation
|
||||
@@ -98,12 +97,12 @@ public class RemoteProcess extends BaseProcessor{
|
||||
}
|
||||
|
||||
//create read/write generators
|
||||
RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers);
|
||||
RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializer);
|
||||
|
||||
//generate the methods to invoke (write)
|
||||
writegen.generateFor(classes, packageName);
|
||||
}else if(round == 2){ //round 2: generate all *readers*
|
||||
RemoteReadGenerator readgen = new RemoteReadGenerator(serializers);
|
||||
RemoteReadGenerator readgen = new RemoteReadGenerator(serializer);
|
||||
|
||||
//generate server readers
|
||||
readgen.generateFor(methods.stream().filter(method -> method.where.isClient).collect(Collectors.toList()), readServerName, packageName, true);
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.remote.IOFinder.*;
|
||||
import mindustry.annotations.remote.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.nio.*;
|
||||
import java.util.*;
|
||||
|
||||
/** Generates code for reading remote invoke packets on the client and server. */
|
||||
public class RemoteReadGenerator{
|
||||
private final HashMap<String, ClassSerializer> serializers;
|
||||
private final ClassSerializer serializers;
|
||||
|
||||
/** Creates a read generator that uses the supplied serializer setup. */
|
||||
public RemoteReadGenerator(HashMap<String, ClassSerializer> serializers){
|
||||
public RemoteReadGenerator(ClassSerializer serializers){
|
||||
this.serializers = serializers;
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ public class RemoteReadGenerator{
|
||||
//create main method builder
|
||||
MethodSpec.Builder readMethod = MethodSpec.methodBuilder("readPacket")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(ByteBuffer.class, "buffer") //buffer to read form
|
||||
.addParameter(Reads.class, "read") //buffer to read form
|
||||
.addParameter(int.class, "id") //ID of method type to read
|
||||
.returns(void.class);
|
||||
|
||||
@@ -76,26 +76,22 @@ public class RemoteReadGenerator{
|
||||
//name of parameter
|
||||
String varName = var.getSimpleName().toString();
|
||||
//captialized version of type name for reading primitives
|
||||
String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1);
|
||||
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
|
||||
|
||||
//write primitives automatically
|
||||
if(BaseProcessor.isPrimitive(typeName)){
|
||||
if(typeName.equals("boolean")){
|
||||
readBlock.addStatement("boolean " + varName + " = buffer.get() == 1");
|
||||
}else{
|
||||
readBlock.addStatement(typeName + " " + varName + " = buffer.get" + capName + "()");
|
||||
}
|
||||
readBlock.addStatement("$L $L = read.$L()", typeName, varName, pname);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
ClassSerializer ser = serializers.getOrDefault(typeName, SerializerResolver.locate(entry.element, var.asType()));
|
||||
String ser = serializers.readers.get(typeName, SerializerResolver.locate(entry.element, var.asType(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No @ReadClass method to read class type '" + typeName + "' in method " + entry.targetMethod, var);
|
||||
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + entry.targetMethod + "; " + serializers.readers, var);
|
||||
return;
|
||||
}
|
||||
|
||||
//add statement for reading it
|
||||
readBlock.addStatement(typeName + " " + varName + " = " + ser.readMethod + "(buffer)");
|
||||
readBlock.addStatement(typeName + " " + varName + " = " + ser + "(read)");
|
||||
}
|
||||
|
||||
//append variable name to string builder
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.remote.IOFinder.*;
|
||||
import mindustry.annotations.remote.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import java.io.*;
|
||||
import java.nio.*;
|
||||
import java.util.*;
|
||||
|
||||
/** Generates code for writing remote invoke packets on the client and server. */
|
||||
public class RemoteWriteGenerator{
|
||||
private final HashMap<String, ClassSerializer> serializers;
|
||||
private final ClassSerializer serializers;
|
||||
|
||||
/** Creates a write generator that uses the supplied serializer setup. */
|
||||
public RemoteWriteGenerator(HashMap<String, ClassSerializer> serializers){
|
||||
public RemoteWriteGenerator(ClassSerializer serializers){
|
||||
this.serializers = serializers;
|
||||
}
|
||||
|
||||
@@ -29,8 +29,12 @@ public class RemoteWriteGenerator{
|
||||
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
|
||||
|
||||
//add temporary write buffer
|
||||
classBuilder.addField(FieldSpec.builder(ByteBuffer.class, "TEMP_BUFFER", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL)
|
||||
.initializer("ByteBuffer.allocate($1L)", RemoteProcess.maxPacketSize).build());
|
||||
classBuilder.addField(FieldSpec.builder(ReusableByteOutStream.class, "OUT", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL)
|
||||
.initializer("new ReusableByteOutStream($L)", RemoteProcess.maxPacketSize).build());
|
||||
|
||||
//add writer for that buffer
|
||||
classBuilder.addField(FieldSpec.builder(Writes.class, "WRITE", Modifier.STATIC, Modifier.PRIVATE, Modifier.FINAL)
|
||||
.initializer("new Writes(new $T(OUT))", DataOutputStream.class).build());
|
||||
|
||||
//go through each method entry in this class
|
||||
for(MethodEntry methodEntry : entry.methods){
|
||||
@@ -128,14 +132,12 @@ public class RemoteWriteGenerator{
|
||||
|
||||
//add statement to create packet from pool
|
||||
method.addStatement("$1N packet = $2N.obtain($1N.class, $1N::new)", "mindustry.net.Packets.InvokePacket", "arc.util.pooling.Pools");
|
||||
//assign buffer
|
||||
method.addStatement("packet.writeBuffer = TEMP_BUFFER");
|
||||
//assign priority
|
||||
method.addStatement("packet.priority = (byte)" + methodEntry.priority.ordinal());
|
||||
//assign method ID
|
||||
method.addStatement("packet.type = (byte)" + methodEntry.id);
|
||||
//rewind buffer
|
||||
method.addStatement("TEMP_BUFFER.position(0)");
|
||||
//reset stream
|
||||
method.addStatement("OUT.reset()");
|
||||
|
||||
method.addTypeVariables(Array.with(elem.getTypeParameters()).map(BaseProcessor::getTVN));
|
||||
|
||||
@@ -169,15 +171,10 @@ public class RemoteWriteGenerator{
|
||||
}
|
||||
|
||||
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
|
||||
if(typeName.equals("boolean")){ //booleans are special
|
||||
method.addStatement("TEMP_BUFFER.put(" + varName + " ? (byte)1 : 0)");
|
||||
}else{
|
||||
method.addStatement("TEMP_BUFFER.put" +
|
||||
capName + "(" + varName + ")");
|
||||
}
|
||||
method.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
ClassSerializer ser = serializers.getOrDefault(typeName, SerializerResolver.locate(elem, var.asType()));
|
||||
String ser = serializers.writers.get(typeName, SerializerResolver.locate(elem, var.asType(), true));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No @WriteClass method to write class type: '" + typeName + "'", var);
|
||||
@@ -185,7 +182,7 @@ public class RemoteWriteGenerator{
|
||||
}
|
||||
|
||||
//add statement for writing it
|
||||
method.addStatement(ser.writeMethod + "(TEMP_BUFFER, " + varName + ")");
|
||||
method.addStatement(ser + "(WRITE, " + varName + ")");
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
@@ -193,8 +190,10 @@ public class RemoteWriteGenerator{
|
||||
}
|
||||
}
|
||||
|
||||
//assign packet bytes
|
||||
method.addStatement("packet.bytes = OUT.getBytes()");
|
||||
//assign packet length
|
||||
method.addStatement("packet.writeLength = TEMP_BUFFER.position()");
|
||||
method.addStatement("packet.length = OUT.size()");
|
||||
|
||||
String sendString;
|
||||
|
||||
|
||||
@@ -1,24 +1,17 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.remote.IOFinder.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
|
||||
public class SerializerResolver{
|
||||
private static final ClassSerializer entitySerializer = new ClassSerializer("mindustry.io.TypeIO.readEntity", "mindustry.io.TypeIO.writeEntity", "Entityc");
|
||||
|
||||
public static ClassSerializer locate(ExecutableElement elem, TypeMirror mirror){
|
||||
public static String locate(ExecutableElement elem, TypeMirror mirror, boolean write){
|
||||
//generic type
|
||||
if(mirror.toString().equals("T")){
|
||||
TypeParameterElement param = elem.getTypeParameters().get(0);
|
||||
if(Array.with(param.getBounds()).contains(SerializerResolver::isEntity)){
|
||||
return entitySerializer;
|
||||
}
|
||||
}
|
||||
if(isEntity(mirror)){
|
||||
return entitySerializer;
|
||||
if((mirror.toString().equals("T") && Array.with(elem.getTypeParameters().get(0).getBounds()).contains(SerializerResolver::isEntity)) ||
|
||||
isEntity(mirror)){
|
||||
return write ? "mindustry.io.TypeIO.writeEntity" : "mindustry.io.TypeIO.readEntity";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
/**
|
||||
* This class finds reader and writer methods.
|
||||
*/
|
||||
public class TypeIOResolver{
|
||||
|
||||
/**
|
||||
* Finds all class serializers for all types and returns them. Logs errors when necessary.
|
||||
* Maps fully qualified class names to their serializers.
|
||||
*/
|
||||
public static ClassSerializer resolve(BaseProcessor processor){
|
||||
ClassSerializer out = new ClassSerializer(new ObjectMap<>(), new ObjectMap<>());
|
||||
for(Stype type : processor.types(TypeIOHandler.class)){
|
||||
//look at all TypeIOHandler methods
|
||||
Array<Smethod> methods = type.methods();
|
||||
for(Smethod meth : methods){
|
||||
if(meth.is(Modifier.PUBLIC) && meth.is(Modifier.STATIC)){
|
||||
Array<Svar> params = meth.params();
|
||||
//2 params, second one is type, first is writer
|
||||
if(params.size == 2 && params.first().tname().toString().equals("arc.util.io.Writes")){
|
||||
out.writers.put(params.get(1).tname().toString(), type.fullName() + "." + meth.name());
|
||||
}else if(params.size == 1 && params.first().tname().toString().equals("arc.util.io.Reads") && !meth.isVoid()){
|
||||
//1 param, one is reader, returns type
|
||||
out.readers.put(meth.retn().toString(), type.fullName() + "." + meth.name());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Information about read/write methods for class types. */
|
||||
public static class ClassSerializer{
|
||||
public final ObjectMap<String, String> writers, readers;
|
||||
|
||||
public ClassSerializer(ObjectMap<String, String> writers, ObjectMap<String, String> readers){
|
||||
this.writers = writers;
|
||||
this.readers = readers;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user