Better IO

This commit is contained in:
Anuken
2020-02-13 22:11:59 -05:00
parent e3621f44da
commit 09c57c32d6
54 changed files with 568 additions and 619 deletions

View File

@@ -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

View File

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

View File

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

View File

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

View File

@@ -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

View File

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

View File

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

View File

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