Merge branch '6.0' into crater
# Conflicts: # core/assets/icons/icons.properties # core/assets/sprites/block_colors.png # core/assets/sprites/sprites.atlas # core/assets/sprites/sprites.png # core/assets/sprites/sprites3.png # core/assets/sprites/sprites5.png # core/src/mindustry/content/Blocks.java # core/src/mindustry/ui/fragments/PlayerListFragment.java # core/src/mindustry/world/BlockStorage.java
2
.gitignore
vendored
|
|
@ -18,6 +18,7 @@ logs/
|
|||
/annotations/out/
|
||||
/net/build/
|
||||
/tools/build/
|
||||
/core/build/
|
||||
/tests/build/
|
||||
/server/build/
|
||||
changelog
|
||||
|
|
@ -43,6 +44,7 @@ changelog
|
|||
*.gif
|
||||
/core/assets/saves/
|
||||
/out/
|
||||
/core/assets-raw/fontgen/out/
|
||||
|
||||
version.properties
|
||||
|
||||
|
|
|
|||
|
|
@ -29,12 +29,10 @@ dependencies{
|
|||
|
||||
implementation arcModule("backends:backend-android")
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
|
||||
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi"
|
||||
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-armeabi-v7a"
|
||||
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-arm64-v8a"
|
||||
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86"
|
||||
natives "com.badlogicgames.gdx:gdx-platform:$gdxVersion:natives-x86_64"
|
||||
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi"
|
||||
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-armeabi-v7a"
|
||||
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-arm64-v8a"
|
||||
natives "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-x86"
|
||||
|
|
@ -119,7 +117,6 @@ android{
|
|||
// the natives configuration, and extracts them to the proper libs/ folders
|
||||
// so they get packed with the APK.
|
||||
task copyAndroidNatives(){
|
||||
file("libs/armeabi/").mkdirs()
|
||||
file("libs/armeabi-v7a/").mkdirs()
|
||||
file("libs/arm64-v8a/").mkdirs()
|
||||
file("libs/x86_64/").mkdirs()
|
||||
|
|
@ -129,7 +126,6 @@ task copyAndroidNatives(){
|
|||
def outputDir = null
|
||||
if(jar.name.endsWith("natives-arm64-v8a.jar")) outputDir = file("libs/arm64-v8a")
|
||||
if(jar.name.endsWith("natives-armeabi-v7a.jar")) outputDir = file("libs/armeabi-v7a")
|
||||
if(jar.name.endsWith("natives-armeabi.jar")) outputDir = file("libs/armeabi")
|
||||
if(jar.name.endsWith("natives-x86_64.jar")) outputDir = file("libs/x86_64")
|
||||
if(jar.name.endsWith("natives-x86.jar")) outputDir = file("libs/x86")
|
||||
if(outputDir != null){
|
||||
|
|
|
|||
|
|
@ -144,7 +144,6 @@ public class AndroidLauncher extends AndroidApplication{
|
|||
|
||||
}, new AndroidApplicationConfiguration(){{
|
||||
useImmersiveMode = true;
|
||||
depth = 0;
|
||||
hideStatusBar = true;
|
||||
errorHandler = CrashSender::log;
|
||||
}});
|
||||
|
|
|
|||
|
|
@ -2,5 +2,4 @@ apply plugin: "java"
|
|||
|
||||
sourceCompatibility = 1.8
|
||||
sourceSets.main.java.srcDirs = ["src/main/java/"]
|
||||
sourceSets.main.resources.srcDirs = ["src/main/resources/"]
|
||||
|
||||
sourceSets.main.resources.srcDirs = ["src/main/resources/"]
|
||||
|
|
@ -5,11 +5,56 @@ import java.lang.annotation.*;
|
|||
public class Annotations{
|
||||
//region entity interfaces
|
||||
|
||||
public enum DrawLayer{
|
||||
floor,
|
||||
floorOver,
|
||||
groundShadows,
|
||||
groundUnder,
|
||||
ground,
|
||||
flyingShadows,
|
||||
flying,
|
||||
bullets,
|
||||
effects,
|
||||
overlays,
|
||||
names,
|
||||
weather
|
||||
}
|
||||
|
||||
/** Indicates that a method overrides other methods. */
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Replace{
|
||||
}
|
||||
|
||||
/** Indicates that a component field is imported from other components. */
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Import{
|
||||
}
|
||||
|
||||
/** Indicates that a component field is read-only. */
|
||||
@Target({ElementType.FIELD, ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface ReadOnly{
|
||||
}
|
||||
|
||||
/** Indicates multiple inheritance on a component type. */
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Depends{
|
||||
Class[] value();
|
||||
public @interface Component{
|
||||
}
|
||||
|
||||
/** Indicates that a method is implemented by the annotation processor. */
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface InternalImpl{
|
||||
}
|
||||
|
||||
/** Indicates priority of a method in an entity. Methods with higher priority are done last. */
|
||||
@Target(ElementType.METHOD)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface MethodPriority{
|
||||
float value();
|
||||
}
|
||||
|
||||
/** Indicates that a component def is present on all entities. */
|
||||
|
|
@ -18,11 +63,28 @@ public class Annotations{
|
|||
public @interface BaseComponent{
|
||||
}
|
||||
|
||||
/** Creates a group that only examines entities that have all the components listed. */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface GroupDef{
|
||||
Class[] value();
|
||||
Class[] collide() default {};
|
||||
boolean spatial() default false;
|
||||
boolean mapping() default false;
|
||||
}
|
||||
|
||||
/** Indicates an entity definition. */
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface EntityDef{
|
||||
/** List of component interfaces */
|
||||
Class[] value();
|
||||
/** Whether the class is final */
|
||||
boolean isFinal() default true;
|
||||
/** If true, entities are recycled. */
|
||||
boolean pooled() default false;
|
||||
/** Whether to serialize (makes the serialize method return this value) */
|
||||
boolean serialize() default true;
|
||||
/** Whether to generate IO code */
|
||||
boolean genio() default true;
|
||||
}
|
||||
|
||||
/** Indicates an internal interface for entity components. */
|
||||
|
|
@ -153,26 +215,9 @@ public class Annotations{
|
|||
PacketPriority priority() default PacketPriority.normal;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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)
|
||||
@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,7 +1,9 @@
|
|||
package mindustry.annotations;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.files.*;
|
||||
import arc.struct.Array;
|
||||
import arc.util.*;
|
||||
import arc.util.Log.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.sun.source.util.*;
|
||||
import mindustry.annotations.util.*;
|
||||
|
|
@ -9,11 +11,13 @@ import mindustry.annotations.util.*;
|
|||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import javax.lang.model.util.*;
|
||||
import javax.tools.Diagnostic.*;
|
||||
import javax.tools.*;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
|
|
@ -30,6 +34,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
|||
protected int round;
|
||||
protected int rounds = 1;
|
||||
protected RoundEnvironment env;
|
||||
protected Fi rootDirectory;
|
||||
|
||||
public static String getMethodName(Element element){
|
||||
return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
|
||||
|
|
@ -40,6 +45,74 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
|||
|| type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char");
|
||||
}
|
||||
|
||||
public static boolean instanceOf(String type, String other){
|
||||
TypeElement a = elementu.getTypeElement(type);
|
||||
TypeElement b = elementu.getTypeElement(other);
|
||||
return a != null && b != null && typeu.isSubtype(a.asType(), b.asType());
|
||||
}
|
||||
|
||||
public static String getDefault(String value){
|
||||
switch(value){
|
||||
case "float":
|
||||
case "double":
|
||||
case "int":
|
||||
case "long":
|
||||
case "short":
|
||||
case "char":
|
||||
case "byte":
|
||||
return "0";
|
||||
case "boolean":
|
||||
return "false";
|
||||
default:
|
||||
return "null";
|
||||
}
|
||||
}
|
||||
|
||||
//in bytes
|
||||
public static int typeSize(String kind){
|
||||
switch(kind){
|
||||
case "boolean":
|
||||
case "byte":
|
||||
return 1;
|
||||
case "short":
|
||||
return 2;
|
||||
case "float":
|
||||
case "char":
|
||||
case "int":
|
||||
return 4;
|
||||
case "long":
|
||||
return 8;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid primitive type: " + kind + "");
|
||||
}
|
||||
}
|
||||
|
||||
public static String simpleName(String str){
|
||||
return str.contains(".") ? str.substring(str.lastIndexOf('.') + 1) : str;
|
||||
}
|
||||
|
||||
public static TypeName tname(String name) throws Exception{
|
||||
Constructor<TypeName> cons = TypeName.class.getDeclaredConstructor(String.class);
|
||||
cons.setAccessible(true);
|
||||
return cons.newInstance(name);
|
||||
}
|
||||
|
||||
public static TypeName tname(Class<?> c){
|
||||
return ClassName.get(c).box();
|
||||
}
|
||||
|
||||
public static TypeVariableName getTVN(TypeParameterElement element) {
|
||||
String name = element.getSimpleName().toString();
|
||||
List<? extends TypeMirror> boundsMirrors = element.getBounds();
|
||||
|
||||
List<TypeName> boundsTypeNames = new ArrayList<>();
|
||||
for (TypeMirror typeMirror : boundsMirrors) {
|
||||
boundsTypeNames.add(TypeName.get(typeMirror));
|
||||
}
|
||||
|
||||
return TypeVariableName.get(name, boundsTypeNames.toArray(new TypeName[0]));
|
||||
}
|
||||
|
||||
public static void write(TypeSpec.Builder builder) throws Exception{
|
||||
write(builder, null);
|
||||
}
|
||||
|
|
@ -70,6 +143,10 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
|||
}
|
||||
}
|
||||
|
||||
public Array<Selement> elements(Class<? extends Annotation> type){
|
||||
return Array.with(env.getElementsAnnotatedWith(type)).map(Selement::new);
|
||||
}
|
||||
|
||||
public Array<Stype> types(Class<? extends Annotation> type){
|
||||
return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof TypeElement)
|
||||
.map(e -> new Stype((TypeElement)e));
|
||||
|
|
@ -85,12 +162,12 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
|||
.map(e -> new Smethod((ExecutableElement)e));
|
||||
}
|
||||
|
||||
public void err(String message){
|
||||
public static void err(String message){
|
||||
messager.printMessage(Kind.ERROR, message);
|
||||
Log.err("[CODEGEN ERROR] " +message);
|
||||
}
|
||||
|
||||
public void err(String message, Element elem){
|
||||
public static void err(String message, Element elem){
|
||||
messager.printMessage(Kind.ERROR, message, elem);
|
||||
Log.err("[CODEGEN ERROR] " + message + ": " + elem);
|
||||
}
|
||||
|
|
@ -108,15 +185,32 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
|||
elementu = env.getElementUtils();
|
||||
filer = env.getFiler();
|
||||
messager = env.getMessager();
|
||||
|
||||
Log.setLogLevel(LogLevel.info);
|
||||
|
||||
if(System.getProperty("debug") != null){
|
||||
Log.setLogLevel(LogLevel.debug);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
|
||||
if(round++ >= rounds) return false; //only process 1 round
|
||||
if(rootDirectory == null){
|
||||
try{
|
||||
String path = Fi.get(filer.getResource(StandardLocation.CLASS_OUTPUT, "no", "no")
|
||||
.toUri().toURL().toString().substring(OS.isWindows ? 6 : "file:".length()))
|
||||
.parent().parent().parent().parent().parent().parent().parent().toString().replace("%20", " ");
|
||||
rootDirectory = Fi.get(path);
|
||||
}catch(IOException e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
this.env = roundEnv;
|
||||
try{
|
||||
process(roundEnv);
|
||||
}catch(Exception e){
|
||||
}catch(Throwable e){
|
||||
e.printStackTrace();
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,185 @@
|
|||
package mindustry.annotations.entity;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.serialization.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
import static mindustry.annotations.BaseProcessor.instanceOf;
|
||||
|
||||
public class EntityIO{
|
||||
final static Json json = new Json();
|
||||
|
||||
final ClassSerializer serializer;
|
||||
final String name;
|
||||
final TypeSpec.Builder type;
|
||||
final Fi directory;
|
||||
final Array<Revision> revisions = new Array<>();
|
||||
|
||||
boolean write;
|
||||
MethodSpec.Builder method;
|
||||
ObjectSet<String> presentFields = new ObjectSet<>();
|
||||
|
||||
EntityIO(String name, TypeSpec.Builder type, ClassSerializer serializer, Fi directory){
|
||||
this.directory = directory;
|
||||
this.type = type;
|
||||
this.serializer = serializer;
|
||||
this.name = name;
|
||||
|
||||
directory.mkdirs();
|
||||
|
||||
//load old revisions
|
||||
for(Fi fi : directory.list()){
|
||||
revisions.add(json.fromJson(Revision.class, fi));
|
||||
}
|
||||
|
||||
//next revision to be used
|
||||
int nextRevision = revisions.isEmpty() ? 0 : revisions.max(r -> r.version).version + 1;
|
||||
|
||||
//resolve preferred field order based on fields that fit
|
||||
Array<FieldSpec> fields = Array.with(type.fieldSpecs).select(spec ->
|
||||
!spec.hasModifier(Modifier.TRANSIENT) &&
|
||||
!spec.hasModifier(Modifier.STATIC) &&
|
||||
!spec.hasModifier(Modifier.FINAL) &&
|
||||
(spec.type.isPrimitive() || serializer.has(spec.type.toString())));
|
||||
|
||||
//sort to keep order
|
||||
fields.sortComparing(f -> f.name);
|
||||
|
||||
//keep track of fields present in the entity
|
||||
presentFields.addAll(fields.map(f -> f.name));
|
||||
|
||||
//add new revision if it doesn't match or there are no revisions
|
||||
if(revisions.isEmpty() || !revisions.peek().equal(fields)){
|
||||
revisions.add(new Revision(nextRevision, fields.map(f -> new RevisionField(f.name, f.type.toString(), f.type.isPrimitive() ? BaseProcessor.typeSize(f.type.toString()) : -1))));
|
||||
//write revision
|
||||
directory.child(nextRevision + ".json").writeString(json.toJson(revisions.peek()));
|
||||
}
|
||||
}
|
||||
|
||||
void write(MethodSpec.Builder method, boolean write) throws Exception{
|
||||
this.method = method;
|
||||
this.write = write;
|
||||
|
||||
//subclasses *have* to call this method
|
||||
method.addAnnotation(CallSuper.class);
|
||||
|
||||
if(write){
|
||||
//write short revision
|
||||
st("write.s($L)", revisions.peek().version);
|
||||
//write uses most recent revision
|
||||
for(RevisionField field : revisions.peek().fields){
|
||||
io(field.type, "this." + field.name);
|
||||
}
|
||||
}else{
|
||||
//read revision
|
||||
st("short REV = read.s()");
|
||||
|
||||
for(int i = 0; i < revisions.size; i++){
|
||||
//check for the right revision
|
||||
Revision rev = revisions.get(i);
|
||||
if(i == 0){
|
||||
cont("if(REV == $L)", rev.version);
|
||||
}else{
|
||||
ncont("else if(REV == $L)", rev.version);
|
||||
}
|
||||
|
||||
//add code for reading revision
|
||||
for(RevisionField field : rev.fields){
|
||||
//if the field doesn't exist, the result will be an empty string, it won't get assigned
|
||||
io(field.type, presentFields.contains(field.name) ? "this." + field.name + " = " : "");
|
||||
}
|
||||
}
|
||||
|
||||
//throw exception on illegal revisions
|
||||
ncont("else");
|
||||
st("throw new IllegalArgumentException(\"Unknown revision '\" + REV + \"' for entity type '" + name + "'\")");
|
||||
econt();
|
||||
}
|
||||
}
|
||||
|
||||
private void io(String type, String field) throws Exception{
|
||||
if(BaseProcessor.isPrimitive(type)){
|
||||
s(type.equals("boolean") ? "bool" : type.charAt(0) + "", field);
|
||||
}else if(instanceOf(type, "mindustry.ctype.Content")){
|
||||
if(write){
|
||||
s("s", field + ".id");
|
||||
}else{
|
||||
st(field + "mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type).toLowerCase().replace("type", ""));
|
||||
}
|
||||
}else if(serializer.writers.containsKey(type) && write){
|
||||
st("$L(write, $L)", serializer.writers.get(type), field);
|
||||
}else if(serializer.readers.containsKey(type) && !write){
|
||||
st("$L$L(read)", field, serializer.readers.get(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void cont(String text, Object... fmt){
|
||||
method.beginControlFlow(text, fmt);
|
||||
}
|
||||
|
||||
private void econt(){
|
||||
method.endControlFlow();
|
||||
}
|
||||
|
||||
private void ncont(String text, Object... fmt){
|
||||
method.nextControlFlow(text, fmt);
|
||||
}
|
||||
|
||||
private void st(String text, Object... args){
|
||||
method.addStatement(text, args);
|
||||
}
|
||||
|
||||
private void s(String type, String field){
|
||||
if(write){
|
||||
method.addStatement("write.$L($L)", type, field);
|
||||
}else{
|
||||
method.addStatement("$Lread.$L()", field, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Revision{
|
||||
int version;
|
||||
Array<RevisionField> fields;
|
||||
|
||||
Revision(int version, Array<RevisionField> fields){
|
||||
this.version = version;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
Revision(){}
|
||||
|
||||
/** @return whether these two revisions are compatible */
|
||||
boolean equal(Array<FieldSpec> specs){
|
||||
if(fields.size != specs.size) return false;
|
||||
|
||||
for(int i = 0; i < fields.size; i++){
|
||||
RevisionField field = fields.get(i);
|
||||
FieldSpec spec = specs.get(i);
|
||||
//TODO when making fields, their primitive size may be overwritten by an annotation; check for that
|
||||
if(!(field.type.equals(spec.type.toString()) && (!spec.type.isPrimitive() || BaseProcessor.typeSize(spec.type.toString()) == field.size))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RevisionField{
|
||||
String name, type;
|
||||
int size; //in bytes
|
||||
|
||||
RevisionField(String name, String type, int size){
|
||||
this.name = name;
|
||||
this.size = size;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
RevisionField(){}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,762 @@
|
|||
package mindustry.annotations.entity;
|
||||
|
||||
import arc.*;
|
||||
import arc.files.*;
|
||||
import arc.func.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.*;
|
||||
import arc.util.io.*;
|
||||
import arc.util.pooling.Pool.*;
|
||||
import arc.util.pooling.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.squareup.javapoet.TypeSpec.*;
|
||||
import com.sun.source.tree.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
@SupportedAnnotationTypes({
|
||||
"mindustry.annotations.Annotations.EntityDef",
|
||||
"mindustry.annotations.Annotations.GroupDef",
|
||||
"mindustry.annotations.Annotations.EntityInterface",
|
||||
"mindustry.annotations.Annotations.BaseComponent",
|
||||
"mindustry.annotations.Annotations.TypeIOHandler"
|
||||
})
|
||||
public class EntityProcess extends BaseProcessor{
|
||||
Array<EntityDefinition> definitions = new Array<>();
|
||||
Array<GroupDefinition> groupDefs = new Array<>();
|
||||
Array<Stype> baseComponents;
|
||||
ObjectMap<String, Stype> componentNames = new ObjectMap<>();
|
||||
ObjectMap<Stype, Array<Stype>> componentDependencies = new ObjectMap<>();
|
||||
ObjectMap<Selement, Array<Stype>> defComponents = new ObjectMap<>();
|
||||
ObjectMap<Svar, String> varInitializers = new ObjectMap<>();
|
||||
ObjectMap<Smethod, String> methodBlocks = new ObjectMap<>();
|
||||
ObjectSet<String> imports = new ObjectSet<>();
|
||||
Array<Selement> allGroups = new Array<>();
|
||||
Array<Selement> allDefs = new Array<>();
|
||||
Array<Stype> allInterfaces = new Array<>();
|
||||
ClassSerializer serializer;
|
||||
|
||||
{
|
||||
rounds = 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void process(RoundEnvironment env) throws Exception{
|
||||
allGroups.addAll(elements(GroupDef.class));
|
||||
allDefs.addAll(elements(EntityDef.class));
|
||||
allInterfaces.addAll(types(EntityInterface.class));
|
||||
|
||||
//round 1: generate component interfaces
|
||||
if(round == 1){
|
||||
serializer = TypeIOResolver.resolve(this);
|
||||
baseComponents = types(BaseComponent.class);
|
||||
Array<Stype> allComponents = types(Component.class);
|
||||
|
||||
//store code
|
||||
for(Stype component : allComponents){
|
||||
for(Svar f : component.fields()){
|
||||
VariableTree tree = f.tree();
|
||||
|
||||
//add initializer if it exists
|
||||
if(tree.getInitializer() != null){
|
||||
String init = tree.getInitializer().toString();
|
||||
varInitializers.put(f, init);
|
||||
}
|
||||
}
|
||||
|
||||
for(Smethod elem : component.methods()){
|
||||
if(elem.is(Modifier.ABSTRACT) || elem.is(Modifier.NATIVE)) continue;
|
||||
//get all statements in the method, store them
|
||||
methodBlocks.put(elem, elem.tree().getBody().toString());
|
||||
}
|
||||
}
|
||||
|
||||
//store components
|
||||
for(Stype type : allComponents){
|
||||
componentNames.put(type.name(), type);
|
||||
}
|
||||
|
||||
//add component imports
|
||||
for(Stype comp : allComponents){
|
||||
imports.addAll(getImports(comp.e));
|
||||
}
|
||||
|
||||
//create component interfaces
|
||||
for(Stype component : allComponents){
|
||||
TypeSpec.Builder inter = TypeSpec.interfaceBuilder(interfaceName(component))
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(EntityInterface.class);
|
||||
|
||||
//implement extra interfaces these components may have, e.g. position
|
||||
for(Stype extraInterface : component.interfaces().select(i -> !isCompInterface(i))){
|
||||
//javapoet completely chokes on this if I add `addSuperInterface` or create the type name with TypeName.get
|
||||
inter.superinterfaces.add(tname(extraInterface.fullName()));
|
||||
}
|
||||
|
||||
//implement super interfaces
|
||||
Array<Stype> depends = getDependencies(component);
|
||||
for(Stype type : depends){
|
||||
inter.addSuperinterface(ClassName.get(packageName, interfaceName(type)));
|
||||
}
|
||||
|
||||
ObjectSet<String> signatures = new ObjectSet<>();
|
||||
|
||||
//add utility methods to interface
|
||||
for(Smethod method : component.methods()){
|
||||
//skip private methods, those are for internal use.
|
||||
if(method.isAny(Modifier.PRIVATE, Modifier.STATIC)) continue;
|
||||
|
||||
//keep track of signatures used to prevent dupes
|
||||
signatures.add(method.e.toString());
|
||||
|
||||
inter.addMethod(MethodSpec.methodBuilder(method.name())
|
||||
.addExceptions(method.thrownt())
|
||||
.addTypeVariables(method.typeVariables().map(TypeVariableName::get))
|
||||
.returns(method.ret().toString().equals("void") ? TypeName.VOID : method.retn())
|
||||
.addParameters(method.params().map(v -> ParameterSpec.builder(v.tname(), v.name())
|
||||
.build())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build());
|
||||
}
|
||||
|
||||
for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.has(Import.class))){
|
||||
String cname = field.name();
|
||||
|
||||
//getter
|
||||
if(!signatures.contains(cname + "()")){
|
||||
inter.addMethod(MethodSpec.methodBuilder(cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addAnnotations(Array.with(field.annotations()).select(a -> a.toString().contains("Null")).map(AnnotationSpec::get))
|
||||
.returns(field.tname()).build());
|
||||
}
|
||||
|
||||
//setter
|
||||
if(!field.is(Modifier.FINAL) && !signatures.contains(cname + "(" + field.mirror().toString() + ")") &&
|
||||
!field.annotations().contains(f -> f.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){
|
||||
inter.addMethod(MethodSpec.methodBuilder(cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
|
||||
.addParameter(ParameterSpec.builder(field.tname(), field.name())
|
||||
.addAnnotations(Array.with(field.annotations())
|
||||
.select(a -> a.toString().contains("Null")).map(AnnotationSpec::get)).build()).build());
|
||||
}
|
||||
}
|
||||
|
||||
write(inter);
|
||||
|
||||
//LOGGING
|
||||
|
||||
Log.debug("&gGenerating interface for " + component.name());
|
||||
|
||||
for(TypeName tn : inter.superinterfaces){
|
||||
Log.debug("&g> &lbextends {0}", simpleName(tn.toString()));
|
||||
}
|
||||
|
||||
//log methods generated
|
||||
for(MethodSpec spec : inter.methodSpecs){
|
||||
Log.debug("&g> > &c{0} {1}({2})", simpleName(spec.returnType.toString()), spec.name, Array.with(spec.parameters).toString(", ", p -> simpleName(p.type.toString()) + " " + p.name));
|
||||
}
|
||||
|
||||
Log.debug("");
|
||||
}
|
||||
|
||||
//generate special render layer interfaces
|
||||
for(DrawLayer layer : DrawLayer.values()){
|
||||
//create the DrawLayer interface that entities need to implement
|
||||
String name = "DrawLayer" + Strings.capitalize(layer.name()) + "c";
|
||||
TypeSpec.Builder inter = TypeSpec.interfaceBuilder(name)
|
||||
.addSuperinterface(tname(packageName + ".Entityc"))
|
||||
.addSuperinterface(tname(packageName + ".Drawc"))
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(EntityInterface.class);
|
||||
inter.addMethod(MethodSpec.methodBuilder("draw" + Strings.capitalize(layer.name())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build());
|
||||
write(inter);
|
||||
}
|
||||
}else if(round == 2){ //round 2: get component classes and generate interfaces for them
|
||||
|
||||
//parse groups
|
||||
for(Selement<?> group : allGroups){
|
||||
GroupDef an = group.annotation(GroupDef.class);
|
||||
Array<Stype> types = types(an, GroupDef::value).map(this::interfaceToComp);
|
||||
Array<Stype> collides = types(an, GroupDef::collide);
|
||||
groupDefs.add(new GroupDefinition(group.name(), ClassName.bestGuess(packageName + "." + interfaceName(types.first())), types, an.spatial(), an.mapping(), collides));
|
||||
}
|
||||
|
||||
//add special generated groups
|
||||
for(DrawLayer layer : DrawLayer.values()){
|
||||
String name = "DrawLayer" + Strings.capitalize(layer.name()) + "c";
|
||||
//create group definition with no components directly
|
||||
GroupDefinition def = new GroupDefinition(layer.name(), ClassName.bestGuess(packageName + "." + name), Array.with(), false, false, new Array<>(0));
|
||||
//add manual inclusions of entities to be added to this group
|
||||
def.manualInclusions.addAll(allDefs.select(s -> allComponents(s).contains(comp -> comp.interfaces().contains(in -> in.name().equals(name)))));
|
||||
groupDefs.add(def);
|
||||
}
|
||||
|
||||
ObjectMap<String, Selement> usedNames = new ObjectMap<>();
|
||||
ObjectMap<Selement, ObjectSet<String>> extraNames = new ObjectMap<>();
|
||||
|
||||
//look at each definition
|
||||
for(Selement<?> type : allDefs){
|
||||
EntityDef ann = type.annotation(EntityDef.class);
|
||||
boolean isFinal = ann.isFinal();
|
||||
|
||||
if(type.isType() && (!type.name().endsWith("Def") && !type.name().endsWith("Comp"))){
|
||||
err("All entity def names must end with 'Def'/'Comp'", type.e);
|
||||
}
|
||||
|
||||
String name = type.isType() ?
|
||||
type.name().replace("Def", "Entity").replace("Comp", "Entity") :
|
||||
createName(type);
|
||||
|
||||
//skip double classes
|
||||
if(usedNames.containsKey(name)){
|
||||
extraNames.getOr(usedNames.get(name), ObjectSet::new).add(type.name());
|
||||
continue;
|
||||
}
|
||||
|
||||
usedNames.put(name, type);
|
||||
extraNames.getOr(type, ObjectSet::new).add(name);
|
||||
if(!type.isType()){
|
||||
extraNames.getOr(type, ObjectSet::new).add(type.name());
|
||||
}
|
||||
|
||||
TypeSpec.Builder builder = TypeSpec.classBuilder(name).addModifiers(Modifier.PUBLIC);
|
||||
if(isFinal) builder.addModifiers(Modifier.FINAL);
|
||||
|
||||
Array<Stype> components = allComponents(type);
|
||||
Array<GroupDefinition> groups = groupDefs.select(g -> (!g.components.isEmpty() && !g.components.contains(s -> !components.contains(s))) || g.manualInclusions.contains(type));
|
||||
ObjectMap<String, Array<Smethod>> methods = new ObjectMap<>();
|
||||
ObjectMap<FieldSpec, Svar> specVariables = new ObjectMap<>();
|
||||
ObjectSet<String> usedFields = new ObjectSet<>();
|
||||
|
||||
//add serialize() boolean
|
||||
builder.addMethod(MethodSpec.methodBuilder("serialize").addModifiers(Modifier.PUBLIC, Modifier.FINAL).returns(boolean.class).addStatement("return " + ann.serialize()).build());
|
||||
|
||||
//add all components
|
||||
for(Stype comp : components){
|
||||
|
||||
//write fields to the class; ignoring transient ones
|
||||
Array<Svar> fields = comp.fields().select(f -> !f.has(Import.class));
|
||||
for(Svar f : fields){
|
||||
if(!usedFields.add(f.name())){
|
||||
err("Field '" + f.name() + "' of component '" + comp.name() + "' re-defines a field in entity '" + type.name() + "'");
|
||||
continue;
|
||||
}
|
||||
|
||||
FieldSpec.Builder fbuilder = FieldSpec.builder(f.tname(), f.name());
|
||||
//keep statics/finals
|
||||
if(f.is(Modifier.STATIC)){
|
||||
fbuilder.addModifiers(Modifier.STATIC);
|
||||
if(f.is(Modifier.FINAL)) fbuilder.addModifiers(Modifier.FINAL);
|
||||
}
|
||||
//add transient modifier for serialization
|
||||
if(f.is(Modifier.TRANSIENT)){
|
||||
fbuilder.addModifiers(Modifier.TRANSIENT);
|
||||
}
|
||||
//add initializer if it exists
|
||||
if(varInitializers.containsKey(f)){
|
||||
fbuilder.initializer(varInitializers.get(f));
|
||||
}
|
||||
|
||||
if(!isFinal) fbuilder.addModifiers(Modifier.PROTECTED);
|
||||
fbuilder.addAnnotations(f.annotations().map(AnnotationSpec::get));
|
||||
builder.addField(fbuilder.build());
|
||||
specVariables.put(builder.fieldSpecs.get(builder.fieldSpecs.size() - 1), f);
|
||||
}
|
||||
|
||||
//get all utility methods from components
|
||||
for(Smethod elem : comp.methods()){
|
||||
methods.getOr(elem.toString(), Array::new).add(elem);
|
||||
}
|
||||
}
|
||||
|
||||
//override toString method
|
||||
builder.addMethod(MethodSpec.methodBuilder("toString")
|
||||
.addAnnotation(Override.class)
|
||||
.returns(String.class)
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addStatement("return $S + $L", name + "#", "id").build());
|
||||
|
||||
EntityIO io = new EntityIO(type.name(), builder, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(name));
|
||||
|
||||
//add all methods from components
|
||||
for(ObjectMap.Entry<String, Array<Smethod>> entry : methods){
|
||||
if(entry.value.contains(m -> m.has(Replace.class))){
|
||||
//check replacements
|
||||
if(entry.value.count(m -> m.has(Replace.class)) > 1){
|
||||
err("Type " + type + " has multiple components replacing method " + entry.key + ".");
|
||||
}
|
||||
Smethod base = entry.value.find(m -> m.has(Replace.class));
|
||||
entry.value.clear();
|
||||
entry.value.add(base);
|
||||
}
|
||||
|
||||
//check multi return
|
||||
if(entry.value.count(m -> !m.isAny(Modifier.NATIVE, Modifier.ABSTRACT) && !m.isVoid()) > 1){
|
||||
err("Type " + type + " has multiple components implementing non-void method " + entry.key + ".");
|
||||
}
|
||||
|
||||
entry.value.sort(Structs.comps(Structs.comparingFloat(m -> m.has(MethodPriority.class) ? m.annotation(MethodPriority.class).value() : 0), Structs.comparing(Selement::name)));
|
||||
|
||||
//representative method
|
||||
Smethod first = entry.value.first();
|
||||
|
||||
//skip internal impl
|
||||
if(first.has(InternalImpl.class)){
|
||||
continue;
|
||||
}
|
||||
|
||||
//build method using same params/returns
|
||||
MethodSpec.Builder mbuilder = MethodSpec.methodBuilder(first.name()).addModifiers(first.is(Modifier.PRIVATE) ? Modifier.PRIVATE : Modifier.PUBLIC);
|
||||
if(isFinal) mbuilder.addModifiers(Modifier.FINAL);
|
||||
if(first.is(Modifier.STATIC)) mbuilder.addModifiers(Modifier.STATIC);
|
||||
mbuilder.addTypeVariables(first.typeVariables().map(TypeVariableName::get));
|
||||
mbuilder.returns(first.retn());
|
||||
mbuilder.addExceptions(first.thrownt());
|
||||
|
||||
for(Svar var : first.params()){
|
||||
mbuilder.addParameter(var.tname(), var.name());
|
||||
}
|
||||
|
||||
//only write the block if it's a void method with several entries
|
||||
boolean writeBlock = first.ret().toString().equals("void") && entry.value.size > 1;
|
||||
|
||||
if((entry.value.first().is(Modifier.ABSTRACT) || entry.value.first().is(Modifier.NATIVE)) && entry.value.size == 1 && !entry.value.first().has(InternalImpl.class)){
|
||||
err(entry.value.first().up().getSimpleName() + "#" + entry.value.first() + " is an abstract method and must be implemented in some component", type);
|
||||
}
|
||||
|
||||
//SPECIAL CASE: inject group add/remove code
|
||||
if(first.name().equals("add") || first.name().equals("remove")){
|
||||
mbuilder.addStatement("if(added == $L) return", first.name().equals("add"));
|
||||
|
||||
for(GroupDefinition def : groups){
|
||||
//remove/add from each group, assume imported
|
||||
mbuilder.addStatement("Groups.$L.$L(this)", def.name, first.name());
|
||||
}
|
||||
}
|
||||
|
||||
//SPECIAL CASE: I/O code
|
||||
//note that serialization is generated even for non-serializing entities for manual usage
|
||||
if((first.name().equals("read") || first.name().equals("write")) && ann.genio()){
|
||||
io.write(mbuilder, first.name().equals("write"));
|
||||
}
|
||||
|
||||
for(Smethod elem : entry.value){
|
||||
if(elem.is(Modifier.ABSTRACT) || elem.is(Modifier.NATIVE) || !methodBlocks.containsKey(elem)) continue;
|
||||
|
||||
//get all statements in the method, copy them over
|
||||
String str = methodBlocks.get(elem);
|
||||
//name for code blocks in the methods
|
||||
String blockName = elem.up().getSimpleName().toString().toLowerCase().replace("comp", "");
|
||||
|
||||
//skip empty blocks
|
||||
if(str.replace("{", "").replace("\n", "").replace("}", "").replace("\t", "").replace(" ", "").isEmpty()){
|
||||
continue;
|
||||
}
|
||||
|
||||
//wrap scope to prevent variable leakage
|
||||
if(writeBlock){
|
||||
//replace return; with block break
|
||||
str = str.replace("return;", "break " + blockName + ";");
|
||||
mbuilder.addCode(blockName + ": {\n");
|
||||
}
|
||||
|
||||
//trim block
|
||||
str = str.substring(2, str.length() - 1);
|
||||
|
||||
//make sure to remove braces here
|
||||
mbuilder.addCode(str);
|
||||
|
||||
//end scope
|
||||
if(writeBlock) mbuilder.addCode("}\n");
|
||||
}
|
||||
|
||||
//add free code to remove methods - always at the end
|
||||
//this only gets called next frame.
|
||||
if(first.name().equals("remove") && ann.pooled()){
|
||||
mbuilder.addStatement("$T.app.post(() -> $T.free(this))", Core.class, Pools.class);
|
||||
}
|
||||
|
||||
builder.addMethod(mbuilder.build());
|
||||
}
|
||||
|
||||
//add pool reset method and implement Poolable
|
||||
if(ann.pooled()){
|
||||
builder.addSuperinterface(Poolable.class);
|
||||
//implement reset()
|
||||
MethodSpec.Builder resetBuilder = MethodSpec.methodBuilder("reset").addModifiers(Modifier.PUBLIC);
|
||||
for(FieldSpec spec : builder.fieldSpecs){
|
||||
Svar variable = specVariables.get(spec);
|
||||
if(variable.isAny(Modifier.STATIC, Modifier.FINAL)) continue;
|
||||
|
||||
if(spec.type.isPrimitive()){
|
||||
//set to primitive default
|
||||
resetBuilder.addStatement("$L = $L", spec.name, varInitializers.containsKey(variable) ? varInitializers.get(variable) : getDefault(spec.type.toString()));
|
||||
}else{
|
||||
//set to default null
|
||||
if(!varInitializers.containsKey(variable)){
|
||||
resetBuilder.addStatement("$L = null", spec.name);
|
||||
} //else... TODO reset if poolable
|
||||
}
|
||||
}
|
||||
|
||||
builder.addMethod(resetBuilder.build());
|
||||
}
|
||||
|
||||
//make constructor private
|
||||
builder.addMethod(MethodSpec.constructorBuilder().addModifiers(Modifier.PROTECTED).build());
|
||||
|
||||
//add create() method
|
||||
builder.addMethod(MethodSpec.methodBuilder("create").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(tname(packageName + "." + name))
|
||||
.addStatement(ann.pooled() ? "return Pools.obtain($L.class, " +name +"::new)" : "return new $L()", name).build());
|
||||
|
||||
definitions.add(new EntityDefinition(packageName + "." + name, builder, type, components, groups));
|
||||
}
|
||||
|
||||
//generate groups
|
||||
TypeSpec.Builder groupsBuilder = TypeSpec.classBuilder("Groups").addModifiers(Modifier.PUBLIC);
|
||||
MethodSpec.Builder groupInit = MethodSpec.methodBuilder("init").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
for(GroupDefinition group : groupDefs){
|
||||
//Stype ctype = group.components.first();
|
||||
//class names for interface/group
|
||||
ClassName itype = group.baseType;
|
||||
ClassName groupc = ClassName.bestGuess("mindustry.entities.EntityGroup");
|
||||
|
||||
//add field...
|
||||
groupsBuilder.addField(ParameterizedTypeName.get(
|
||||
ClassName.bestGuess("mindustry.entities.EntityGroup"), itype), group.name, Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
groupInit.addStatement("$L = new $T<>($L.class, $L, $L)", group.name, groupc, itype, group.spatial, group.mapping);
|
||||
}
|
||||
|
||||
//write the groups
|
||||
groupsBuilder.addMethod(groupInit.build());
|
||||
|
||||
//add method for resizing all necessary groups
|
||||
MethodSpec.Builder groupResize = MethodSpec.methodBuilder("resize")
|
||||
.addParameter(TypeName.FLOAT, "x").addParameter(TypeName.FLOAT, "y").addParameter(TypeName.FLOAT, "w").addParameter(TypeName.FLOAT, "h")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
MethodSpec.Builder groupUpdate = MethodSpec.methodBuilder("update")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
//method resize
|
||||
for(GroupDefinition group : groupDefs){
|
||||
if(group.spatial){
|
||||
groupResize.addStatement("$L.resize(x, y, w, h)", group.name);
|
||||
groupUpdate.addStatement("$L.updatePhysics()", group.name);
|
||||
}
|
||||
}
|
||||
|
||||
groupUpdate.addStatement("all.update()");
|
||||
|
||||
for(GroupDefinition group : groupDefs){
|
||||
for(Stype collide : group.collides){
|
||||
groupUpdate.addStatement("$L.collide($L)", group.name, collide.name());
|
||||
}
|
||||
}
|
||||
|
||||
for(DrawLayer layer : DrawLayer.values()){
|
||||
MethodSpec.Builder groupDraw = MethodSpec.methodBuilder("draw" + Strings.capitalize(layer.name()))
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
groupDraw.addStatement("$L.draw($L::$L)", layer.name(), "DrawLayer" + Strings.capitalize(layer.name()) + "c", "draw" + Strings.capitalize(layer.name()));
|
||||
groupsBuilder.addMethod(groupDraw.build());
|
||||
}
|
||||
|
||||
groupsBuilder.addMethod(groupResize.build());
|
||||
groupsBuilder.addMethod(groupUpdate.build());
|
||||
|
||||
write(groupsBuilder);
|
||||
|
||||
//load map of sync IDs
|
||||
StringMap map = new StringMap();
|
||||
Fi idProps = rootDirectory.child("annotations/src/main/resources/classids.properties");
|
||||
if(!idProps.exists()) idProps.writeString("");
|
||||
PropertiesUtils.load(map, idProps.reader());
|
||||
//next ID to be used in generation
|
||||
Integer max = map.values().toArray().map(Integer::parseInt).max(i -> i);
|
||||
int maxID = max == null ? 0 : max + 1;
|
||||
|
||||
//assign IDs
|
||||
definitions.sort(Structs.comparing(t -> t.base.toString()));
|
||||
for(EntityDefinition def : definitions){
|
||||
String name = def.base.fullName();
|
||||
if(map.containsKey(name)){
|
||||
def.classID = map.getInt(name);
|
||||
}else{
|
||||
def.classID = maxID++;
|
||||
map.put(name, def.classID + "");
|
||||
}
|
||||
}
|
||||
|
||||
OrderedMap<String, String> res = new OrderedMap<>();
|
||||
res.putAll(map);
|
||||
res.orderedKeys().sort();
|
||||
|
||||
//write assigned IDs
|
||||
PropertiesUtils.store(res, idProps.writer(false), "Maps entity names to IDs. Autogenerated.");
|
||||
|
||||
//build mapping class for sync IDs
|
||||
TypeSpec.Builder idBuilder = TypeSpec.classBuilder("EntityMapping").addModifiers(Modifier.PUBLIC)
|
||||
.addField(FieldSpec.builder(TypeName.get(Prov[].class), "idMap", Modifier.PRIVATE, Modifier.STATIC).initializer("new Prov[256]").build())
|
||||
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class),
|
||||
tname(String.class), tname(Prov.class)),
|
||||
"nameMap", Modifier.PRIVATE, Modifier.STATIC).initializer("new ObjectMap<>()").build())
|
||||
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(Prov.class)).addParameter(int.class, "id").addStatement("return idMap[id]").build())
|
||||
.addMethod(MethodSpec.methodBuilder("map").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(Prov.class)).addParameter(String.class, "name").addStatement("return nameMap.get(name)").build());
|
||||
|
||||
CodeBlock.Builder idStore = CodeBlock.builder();
|
||||
|
||||
//store the mappings
|
||||
for(EntityDefinition def : definitions){
|
||||
//store mapping
|
||||
idStore.addStatement("idMap[$L] = $L::new", def.classID, def.name);
|
||||
extraNames.get(def.base).each(extra -> {
|
||||
idStore.addStatement("nameMap.put($S, $L::new)", extra, def.name);
|
||||
});
|
||||
|
||||
//return mapping
|
||||
def.builder.addMethod(MethodSpec.methodBuilder("classId").addAnnotation(Override.class)
|
||||
.returns(int.class).addModifiers(Modifier.PUBLIC).addStatement("return " + def.classID).build());
|
||||
}
|
||||
|
||||
|
||||
idBuilder.addStaticBlock(idStore.build());
|
||||
|
||||
write(idBuilder);
|
||||
}else{
|
||||
//round 3: generate actual classes and implement interfaces
|
||||
|
||||
//implement each definition
|
||||
for(EntityDefinition def : definitions){
|
||||
|
||||
//get interface for each component
|
||||
for(Stype comp : def.components){
|
||||
|
||||
//implement the interface
|
||||
Stype inter = allInterfaces.find(i -> i.name().equals(interfaceName(comp)));
|
||||
if(inter == null){
|
||||
err("Failed to generate interface for", comp);
|
||||
return;
|
||||
}
|
||||
|
||||
def.builder.addSuperinterface(inter.tname());
|
||||
|
||||
//generate getter/setter for each method
|
||||
for(Smethod method : inter.methods()){
|
||||
String var = method.name();
|
||||
FieldSpec field = Array.with(def.builder.fieldSpecs).find(f -> f.name.equals(var));
|
||||
//make sure it's a real variable AND that the component doesn't already implement it with custom logic
|
||||
if(field == null || comp.methods().contains(m -> m.simpleString().equals(method.simpleString()))) continue;
|
||||
|
||||
//getter
|
||||
if(!method.isVoid()){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).addModifiers(Modifier.FINAL).build());
|
||||
}
|
||||
|
||||
//setter
|
||||
if(method.isVoid() && !Array.with(field.annotations).contains(f -> f.type.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addModifiers(Modifier.FINAL).addStatement("this." + var + " = " + var).build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
write(def.builder, imports.asArray());
|
||||
}
|
||||
|
||||
//store nulls
|
||||
TypeSpec.Builder nullsBuilder = TypeSpec.classBuilder("Nulls").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL);
|
||||
|
||||
//create mock types of all components
|
||||
for(Stype interf : allInterfaces){
|
||||
//indirect interfaces to implement methods for
|
||||
Array<Stype> dependencies = interf.allInterfaces().and(interf);
|
||||
Array<Smethod> methods = dependencies.flatMap(Stype::methods);
|
||||
methods.sortComparing(Object::toString);
|
||||
|
||||
//used method signatures
|
||||
ObjectSet<String> signatures = new ObjectSet<>();
|
||||
|
||||
//create null builder
|
||||
String baseName = interf.name().substring(0, interf.name().length() - 1);
|
||||
String className = "Null" + baseName;
|
||||
TypeSpec.Builder nullBuilder = TypeSpec.classBuilder(className)
|
||||
.addModifiers(Modifier.FINAL);
|
||||
|
||||
nullBuilder.addSuperinterface(interf.tname());
|
||||
|
||||
for(Smethod method : methods){
|
||||
String signature = method.toString();
|
||||
if(signatures.contains(signature)) continue;
|
||||
|
||||
Stype compType = interfaceToComp(method.type());
|
||||
MethodSpec.Builder builder = MethodSpec.overriding(method.e).addModifiers(Modifier.PUBLIC, Modifier.FINAL);
|
||||
|
||||
if(!method.isVoid()){
|
||||
if(method.name().equals("isNull")){
|
||||
builder.addStatement("return true");
|
||||
}else if(method.name().equals("id")){
|
||||
builder.addStatement("return -1");
|
||||
}else{
|
||||
Svar variable = compType == null || method.params().size > 0 ? null : compType.fields().find(v -> v.name().equals(method.name()));
|
||||
if(variable == null || !varInitializers.containsKey(variable)){
|
||||
builder.addStatement("return " + getDefault(method.ret().toString()));
|
||||
}else{
|
||||
String init = varInitializers.get(variable);
|
||||
builder.addStatement("return " + (init.equals("{}") ? "new " + variable.mirror().toString() : "") + init);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nullBuilder.addMethod(builder.build());
|
||||
|
||||
signatures.add(signature);
|
||||
}
|
||||
|
||||
nullsBuilder.addField(FieldSpec.builder(interf.cname(), Strings.camelize(baseName)).initializer("new " + className + "()").addModifiers(Modifier.FINAL, Modifier.STATIC, Modifier.PUBLIC).build());
|
||||
|
||||
write(nullBuilder);
|
||||
}
|
||||
|
||||
write(nullsBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
Array<String> getImports(Element elem){
|
||||
return Array.with(trees.getPath(elem).getCompilationUnit().getImports()).map(Object::toString);
|
||||
}
|
||||
|
||||
/** @return interface for a component type */
|
||||
String interfaceName(Stype comp){
|
||||
String suffix = "Comp";
|
||||
if(!comp.name().endsWith(suffix)){
|
||||
err("All components must have names that end with 'Comp'", comp.e);
|
||||
}
|
||||
return comp.name().substring(0, comp.name().length() - suffix.length()) + "c";
|
||||
}
|
||||
|
||||
/** @return all components that a entity def has */
|
||||
Array<Stype> allComponents(Selement<?> type){
|
||||
if(!defComponents.containsKey(type)){
|
||||
//get base defs
|
||||
Array<Stype> components = types(type.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);
|
||||
ObjectSet<Stype> out = new ObjectSet<>();
|
||||
for(Stype comp : components){
|
||||
//get dependencies for each def, add them
|
||||
out.add(comp);
|
||||
out.addAll(getDependencies(comp));
|
||||
}
|
||||
|
||||
defComponents.put(type, out.asArray());
|
||||
}
|
||||
|
||||
return defComponents.get(type);
|
||||
}
|
||||
|
||||
Array<Stype> getDependencies(Stype component){
|
||||
if(!componentDependencies.containsKey(component)){
|
||||
ObjectSet<Stype> out = new ObjectSet<>();
|
||||
//add base component interfaces
|
||||
out.addAll(component.interfaces().select(this::isCompInterface).map(this::interfaceToComp));
|
||||
//remove self interface
|
||||
out.remove(component);
|
||||
|
||||
//out now contains the base dependencies; finish constructing the tree
|
||||
ObjectSet<Stype> result = new ObjectSet<>();
|
||||
for(Stype type : out){
|
||||
result.add(type);
|
||||
result.addAll(getDependencies(type));
|
||||
}
|
||||
|
||||
if(component.annotation(BaseComponent.class) == null){
|
||||
result.addAll(baseComponents);
|
||||
}
|
||||
|
||||
//remove it again just in case
|
||||
out.remove(component);
|
||||
componentDependencies.put(component, result.asArray());
|
||||
}
|
||||
|
||||
return componentDependencies.get(component);
|
||||
}
|
||||
|
||||
boolean isCompInterface(Stype type){
|
||||
return interfaceToComp(type) != null;
|
||||
}
|
||||
|
||||
Stype interfaceToComp(Stype type){
|
||||
String name = type.name().substring(0, type.name().length() - 1) + "Comp";
|
||||
return componentNames.get(name);
|
||||
}
|
||||
|
||||
String createName(Selement<?> elem){
|
||||
Array<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);;
|
||||
comps.sortComparing(Selement::name);
|
||||
return comps.toString("", s -> s.name().replace("Comp", "")) + "Entity";
|
||||
}
|
||||
|
||||
boolean isComponent(Stype type){
|
||||
return type.annotation(Component.class) != null;
|
||||
}
|
||||
|
||||
<T extends Annotation> Array<Stype> types(T t, Cons<T> consumer){
|
||||
try{
|
||||
consumer.get(t);
|
||||
}catch(MirroredTypesException e){
|
||||
return Array.with(e.getTypeMirrors()).map(Stype::of);
|
||||
}
|
||||
throw new IllegalArgumentException("Missing types.");
|
||||
}
|
||||
|
||||
class GroupDefinition{
|
||||
final String name;
|
||||
final ClassName baseType;
|
||||
final Array<Stype> components;
|
||||
final Array<Stype> collides;
|
||||
final boolean spatial, mapping;
|
||||
final ObjectSet<Selement> manualInclusions = new ObjectSet<>();
|
||||
|
||||
public GroupDefinition(String name, ClassName bestType, Array<Stype> components, boolean spatial, boolean mapping, Array<Stype> collides){
|
||||
this.baseType = bestType;
|
||||
this.components = components;
|
||||
this.name = name;
|
||||
this.spatial = spatial;
|
||||
this.mapping = mapping;
|
||||
this.collides = collides;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
class EntityDefinition{
|
||||
final Array<GroupDefinition> groups;
|
||||
final Array<Stype> components;
|
||||
final TypeSpec.Builder builder;
|
||||
final Selement base;
|
||||
final String name;
|
||||
int classID;
|
||||
|
||||
public EntityDefinition(String name, Builder builder, Selement base, Array<Stype> components, Array<GroupDefinition> groups){
|
||||
this.builder = builder;
|
||||
this.name = name;
|
||||
this.base = base;
|
||||
this.groups = groups;
|
||||
this.components = components;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString(){
|
||||
return "Definition{" +
|
||||
"groups=" + groups +
|
||||
"components=" + components +
|
||||
", base=" + base +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,29 +5,21 @@ import arc.scene.style.*;
|
|||
import arc.struct.*;
|
||||
import arc.util.serialization.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.*;
|
||||
import javax.tools.*;
|
||||
import java.util.*;
|
||||
|
||||
@SupportedAnnotationTypes("mindustry.annotations.Annotations.StyleDefaults")
|
||||
public class AssetsProcess extends BaseProcessor{
|
||||
private String path;
|
||||
|
||||
@Override
|
||||
public void process(RoundEnvironment env) throws Exception{
|
||||
path = Fi.get(BaseProcessor.filer.createResource(StandardLocation.CLASS_OUTPUT, "no", "no")
|
||||
.toUri().toURL().toString().substring(System.getProperty("os.name").contains("Windows") ? 6 : "file:".length()))
|
||||
.parent().parent().parent().parent().parent().parent().toString();
|
||||
path = path.replace("%20", " ");
|
||||
|
||||
processSounds("Sounds", path + "/assets/sounds", "arc.audio.Sound");
|
||||
processSounds("Musics", path + "/assets/music", "arc.audio.Music");
|
||||
processSounds("Sounds", rootDirectory + "/core/assets/sounds", "arc.audio.Sound");
|
||||
processSounds("Musics", rootDirectory + "/core/assets/music", "arc.audio.Music");
|
||||
processUI(env.getElementsAnnotatedWith(StyleDefaults.class));
|
||||
}
|
||||
|
||||
|
|
@ -38,8 +30,8 @@ public class AssetsProcess extends BaseProcessor{
|
|||
MethodSpec.Builder load = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
MethodSpec.Builder loadStyles = MethodSpec.methodBuilder("loadStyles").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
MethodSpec.Builder icload = MethodSpec.methodBuilder("load").addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
String resources = path + "/assets-raw/sprites/ui";
|
||||
Jval icons = Jval.read(Fi.get(path + "/assets-raw/fontgen/config.json").readString());
|
||||
String resources = rootDirectory + "/core/assets-raw/sprites/ui";
|
||||
Jval icons = Jval.read(Fi.get(rootDirectory + "/core/assets-raw/fontgen/config.json").readString());
|
||||
|
||||
ictype.addField(FieldSpec.builder(ParameterizedTypeName.get(ObjectMap.class, String.class, TextureRegionDrawable.class),
|
||||
"icons", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer("new ObjectMap<>()").build());
|
||||
|
|
@ -105,7 +97,7 @@ public class AssetsProcess extends BaseProcessor{
|
|||
String name = p.nameWithoutExtension();
|
||||
|
||||
if(names.contains(name)){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "Duplicate file name: " + p.toString() + "!");
|
||||
BaseProcessor.err("Duplicate file name: " + p.toString() + "!");
|
||||
}else{
|
||||
names.add(name);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ public class StructProcess extends BaseProcessor{
|
|||
for(TypeElement elem : elements){
|
||||
|
||||
if(!elem.getSimpleName().toString().endsWith("Struct")){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "All classes annotated with @Struct must have their class names end in 'Struct'.", elem);
|
||||
BaseProcessor.err("All classes annotated with @Struct must have their class names end in 'Struct'.", elem);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ public class StructProcess extends BaseProcessor{
|
|||
int structTotalSize = (structSize <= 8 ? 8 : structSize <= 16 ? 16 : structSize <= 32 ? 32 : 64);
|
||||
|
||||
if(variables.size() == 0){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "making a struct with no fields is utterly pointles.", elem);
|
||||
BaseProcessor.err("making a struct with no fields is utterly pointles.", elem);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +133,7 @@ public class StructProcess extends BaseProcessor{
|
|||
JavaFile.builder(packageName, classBuilder.build()).build().writeTo(BaseProcessor.filer);
|
||||
}catch(IllegalArgumentException e){
|
||||
e.printStackTrace();
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, e.getMessage(), elem);
|
||||
BaseProcessor.err(e.getMessage(), elem);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,91 +0,0 @@
|
|||
package mindustry.annotations.remote;
|
||||
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.Annotations.ReadClass;
|
||||
import mindustry.annotations.Annotations.WriteClass;
|
||||
|
||||
import javax.annotation.processing.RoundEnvironment;
|
||||
import javax.lang.model.element.Element;
|
||||
import javax.lang.model.type.MirroredTypeException;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import java.util.HashMap;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 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.messager.printMessage(Kind.ERROR, "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.messager.printMessage(Kind.ERROR, "Writer method does not have an accompanying reader: ", writer);
|
||||
}else if(count > 1){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "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,11 @@ package mindustry.annotations.remote;
|
|||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.remote.IOFinder.*;
|
||||
import mindustry.annotations.util.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.*;
|
||||
import java.util.*;
|
||||
import java.util.stream.*;
|
||||
|
||||
|
|
@ -15,8 +15,7 @@ import java.util.stream.*;
|
|||
/** The annotation processor for generating remote method call code. */
|
||||
@SupportedAnnotationTypes({
|
||||
"mindustry.annotations.Annotations.Remote",
|
||||
"mindustry.annotations.Annotations.WriteClass",
|
||||
"mindustry.annotations.Annotations.ReadClass",
|
||||
"mindustry.annotations.Annotations.TypeIOHandler"
|
||||
})
|
||||
public class RemoteProcess extends BaseProcessor{
|
||||
/** Maximum size of each event packet. */
|
||||
|
|
@ -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
|
||||
|
|
@ -72,12 +71,12 @@ public class RemoteProcess extends BaseProcessor{
|
|||
|
||||
//check for static
|
||||
if(!element.getModifiers().contains(Modifier.STATIC) || !element.getModifiers().contains(Modifier.PUBLIC)){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "All @Remote methods must be public and static: ", element);
|
||||
err("All @Remote methods must be public and static: ", element);
|
||||
}
|
||||
|
||||
//can't generate none methods
|
||||
if(annotation.targets() == Loc.none){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "A @Remote method's targets() cannot be equal to 'none':", element);
|
||||
err("A @Remote method's targets() cannot be equal to 'none':", element);
|
||||
}
|
||||
|
||||
//get and create class entry if needed
|
||||
|
|
@ -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,24 +1,21 @@
|
|||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.remote.IOFinder.ClassSerializer;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.Modifier;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.lang.reflect.*;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -29,8 +26,7 @@ public class RemoteReadGenerator{
|
|||
* @param packageName Full target package name.
|
||||
* @param needsPlayer Whether this read method requires a reference to the player sender.
|
||||
*/
|
||||
public void generateFor(List<MethodEntry> entries, String className, String packageName, boolean needsPlayer)
|
||||
throws IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, IOException{
|
||||
public void generateFor(List<MethodEntry> entries, String className, String packageName, boolean needsPlayer) throws Exception{
|
||||
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
|
||||
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
|
||||
|
|
@ -38,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);
|
||||
|
||||
|
|
@ -48,7 +44,7 @@ public class RemoteReadGenerator{
|
|||
Constructor<TypeName> cons = TypeName.class.getDeclaredConstructor(String.class);
|
||||
cons.setAccessible(true);
|
||||
|
||||
TypeName playerType = cons.newInstance("mindustry.entities.type.Player");
|
||||
TypeName playerType = cons.newInstance("mindustry.gen.Playerc");
|
||||
//add player parameter
|
||||
readMethod.addParameter(playerType, "player");
|
||||
}
|
||||
|
|
@ -80,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.get(typeName);
|
||||
String ser = serializers.readers.get(typeName, SerializerResolver.locate(entry.element, var.asType(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "No @ReadClass method to read class type: '" + typeName + "'", 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
|
||||
|
|
@ -119,7 +111,7 @@ public class RemoteReadGenerator{
|
|||
if(entry.forward && entry.where.isServer && needsPlayer){
|
||||
//call forwarded method
|
||||
readBlock.addStatement(packageName + "." + entry.className + "." + entry.element.getSimpleName() +
|
||||
"__forward(player.con" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")");
|
||||
"__forward(player.con()" + (varResult.length() == 0 ? "" : ", ") + varResult.toString() + ")");
|
||||
}
|
||||
|
||||
readBlock.nextControlFlow("catch (java.lang.Exception e)");
|
||||
|
|
|
|||
|
|
@ -1,23 +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.Annotations.Loc;
|
||||
import mindustry.annotations.remote.IOFinder.ClassSerializer;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.Kind;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.io.*;
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -30,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){
|
||||
|
|
@ -74,12 +77,12 @@ public class RemoteWriteGenerator{
|
|||
//validate client methods to make sure
|
||||
if(methodEntry.where.isClient){
|
||||
if(elem.getParameters().isEmpty()){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "Client invoke methods must have a first parameter of type Player.", elem);
|
||||
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!elem.getParameters().get(0).asType().toString().equals("mindustry.entities.type.Player")){
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "Client invoke methods should have a first parameter of type Player.", elem);
|
||||
if(!elem.getParameters().get(0).asType().toString().equals("Playerc")){
|
||||
BaseProcessor.err("Client invoke methods should have a first parameter of type Playerc", elem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -129,14 +132,14 @@ 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));
|
||||
|
||||
for(int i = 0; i < elem.getParameters().size(); i++){
|
||||
//first argument is skipped as it is always the player caller
|
||||
|
|
@ -146,8 +149,12 @@ public class RemoteWriteGenerator{
|
|||
|
||||
VariableElement var = elem.getParameters().get(i);
|
||||
|
||||
//add parameter to method
|
||||
method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString());
|
||||
try{
|
||||
//add parameter to method
|
||||
method.addParameter(TypeName.get(var.asType()), var.getSimpleName().toString());
|
||||
}catch(Throwable t){
|
||||
throw new RuntimeException("Error parsing method " + methodEntry.targetMethod);
|
||||
}
|
||||
|
||||
//name of parameter
|
||||
String varName = var.getSimpleName().toString();
|
||||
|
|
@ -164,23 +171,18 @@ 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.get(typeName);
|
||||
String ser = serializers.writers.get(typeName, SerializerResolver.locate(elem, var.asType(), true));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.messager.printMessage(Kind.ERROR, "No @WriteClass method to write class type: '" + typeName + "'", var);
|
||||
BaseProcessor.err("No @WriteClass method to write class type: '" + typeName + "'", var);
|
||||
return;
|
||||
}
|
||||
|
||||
//add statement for writing it
|
||||
method.addStatement(ser.writeMethod + "(TEMP_BUFFER, " + varName + ")");
|
||||
method.addStatement(ser + "(WRITE, " + varName + ")");
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
|
|
@ -188,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
|
||||
public class SerializerResolver{
|
||||
|
||||
public static String locate(ExecutableElement elem, TypeMirror mirror, boolean write){
|
||||
//generic type
|
||||
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;
|
||||
}
|
||||
|
||||
private static boolean isEntity(TypeMirror mirror){
|
||||
return !mirror.toString().contains(".") && mirror.toString().endsWith("c");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,287 @@
|
|||
package mindustry.annotations.util;
|
||||
|
||||
import com.sun.tools.javac.code.*;
|
||||
import com.sun.tools.javac.code.Attribute.Array;
|
||||
import com.sun.tools.javac.code.Attribute.Enum;
|
||||
import com.sun.tools.javac.code.Attribute.Error;
|
||||
import com.sun.tools.javac.code.Attribute.Visitor;
|
||||
import com.sun.tools.javac.code.Attribute.*;
|
||||
import com.sun.tools.javac.code.Type;
|
||||
import com.sun.tools.javac.code.Symbol.*;
|
||||
import com.sun.tools.javac.code.Type.ArrayType;
|
||||
import com.sun.tools.javac.util.List;
|
||||
import com.sun.tools.javac.util.*;
|
||||
import sun.reflect.annotation.*;
|
||||
|
||||
import javax.lang.model.type.*;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.util.Map.*;
|
||||
import java.lang.Class;
|
||||
|
||||
//replaces the standard Java AnnotationProxyMaker with one that doesn't crash
|
||||
//thanks, oracle.
|
||||
@SuppressWarnings({"sunapi", "unchecked"})
|
||||
public class AnnotationProxyMaker{
|
||||
private final Compound anno;
|
||||
private final Class<? extends Annotation> annoType;
|
||||
|
||||
private AnnotationProxyMaker(Compound var1, Class<? extends Annotation> var2){
|
||||
this.anno = var1;
|
||||
this.annoType = var2;
|
||||
}
|
||||
|
||||
public static <A extends Annotation> A generateAnnotation(Compound var0, Class<A> var1){
|
||||
AnnotationProxyMaker var2 = new AnnotationProxyMaker(var0, var1);
|
||||
return (A)var1.cast(var2.generateAnnotation());
|
||||
}
|
||||
|
||||
private Annotation generateAnnotation(){
|
||||
return AnnotationParser.annotationForMap(this.annoType, this.getAllReflectedValues());
|
||||
}
|
||||
|
||||
private Map<String, Object> getAllReflectedValues(){
|
||||
LinkedHashMap var1 = new LinkedHashMap();
|
||||
Iterator var2 = this.getAllValues().entrySet().iterator();
|
||||
|
||||
while(var2.hasNext()){
|
||||
Entry var3 = (Entry)var2.next();
|
||||
MethodSymbol var4 = (MethodSymbol)var3.getKey();
|
||||
Object var5 = this.generateValue(var4, (Attribute)var3.getValue());
|
||||
if(var5 != null){
|
||||
var1.put(var4.name.toString(), var5);
|
||||
}
|
||||
}
|
||||
|
||||
return var1;
|
||||
}
|
||||
|
||||
private Map<MethodSymbol, Attribute> getAllValues(){
|
||||
LinkedHashMap var1 = new LinkedHashMap();
|
||||
ClassSymbol var2 = (ClassSymbol)this.anno.type.tsym;
|
||||
|
||||
for(com.sun.tools.javac.code.Scope.Entry var3 = var2.members().elems; var3 != null; var3 = var3.sibling){
|
||||
if(var3.sym.kind == 16){
|
||||
MethodSymbol var4 = (MethodSymbol)var3.sym;
|
||||
Attribute var5 = var4.getDefaultValue();
|
||||
if(var5 != null){
|
||||
var1.put(var4, var5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Iterator var6 = this.anno.values.iterator();
|
||||
|
||||
while(var6.hasNext()){
|
||||
Pair var7 = (Pair)var6.next();
|
||||
var1.put(var7.fst, var7.snd);
|
||||
}
|
||||
|
||||
return var1;
|
||||
}
|
||||
|
||||
private Object generateValue(MethodSymbol var1, Attribute var2){
|
||||
AnnotationProxyMaker.ValueVisitor var3 = new AnnotationProxyMaker.ValueVisitor(var1);
|
||||
return var3.getValue(var2);
|
||||
}
|
||||
|
||||
private static final class MirroredTypesExceptionProxy extends ExceptionProxy{
|
||||
static final long serialVersionUID = 269L;
|
||||
private transient List<TypeMirror> types;
|
||||
private final String typeStrings;
|
||||
|
||||
MirroredTypesExceptionProxy(List<TypeMirror> var1){
|
||||
this.types = var1;
|
||||
this.typeStrings = var1.toString();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return this.typeStrings;
|
||||
}
|
||||
|
||||
public int hashCode(){
|
||||
return (this.types != null ? this.types : this.typeStrings).hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object var1){
|
||||
return this.types != null && var1 instanceof AnnotationProxyMaker.MirroredTypesExceptionProxy && this.types.equals(((AnnotationProxyMaker.MirroredTypesExceptionProxy)var1).types);
|
||||
}
|
||||
|
||||
protected RuntimeException generateException(){
|
||||
return new MirroredTypesException(this.types);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException{
|
||||
var1.defaultReadObject();
|
||||
this.types = null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MirroredTypeExceptionProxy extends ExceptionProxy{
|
||||
static final long serialVersionUID = 269L;
|
||||
private transient TypeMirror type;
|
||||
private final String typeString;
|
||||
|
||||
MirroredTypeExceptionProxy(TypeMirror var1){
|
||||
this.type = var1;
|
||||
this.typeString = var1.toString();
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return this.typeString;
|
||||
}
|
||||
|
||||
public int hashCode(){
|
||||
return (this.type != null ? this.type : this.typeString).hashCode();
|
||||
}
|
||||
|
||||
public boolean equals(Object var1){
|
||||
return this.type != null && var1 instanceof AnnotationProxyMaker.MirroredTypeExceptionProxy && this.type.equals(((AnnotationProxyMaker.MirroredTypeExceptionProxy)var1).type);
|
||||
}
|
||||
|
||||
protected RuntimeException generateException(){
|
||||
return new MirroredTypeException(this.type);
|
||||
}
|
||||
|
||||
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException{
|
||||
var1.defaultReadObject();
|
||||
this.type = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ValueVisitor implements Visitor{
|
||||
private MethodSymbol meth;
|
||||
private Class<?> returnClass;
|
||||
private Object value;
|
||||
|
||||
ValueVisitor(MethodSymbol var2){
|
||||
this.meth = var2;
|
||||
}
|
||||
|
||||
Object getValue(Attribute var1){
|
||||
Method var2;
|
||||
try{
|
||||
var2 = AnnotationProxyMaker.this.annoType.getMethod(this.meth.name.toString());
|
||||
}catch(NoSuchMethodException var4){
|
||||
return null;
|
||||
}
|
||||
|
||||
this.returnClass = var2.getReturnType();
|
||||
var1.accept(this);
|
||||
if(!(this.value instanceof ExceptionProxy) && !AnnotationType.invocationHandlerReturnType(this.returnClass).isInstance(this.value)){
|
||||
this.typeMismatch(var2, var1);
|
||||
}
|
||||
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public void visitConstant(Constant var1){
|
||||
this.value = var1.getValue();
|
||||
}
|
||||
|
||||
public void visitClass(com.sun.tools.javac.code.Attribute.Class var1){
|
||||
this.value = new AnnotationProxyMaker.MirroredTypeExceptionProxy(var1.classType);
|
||||
}
|
||||
|
||||
public void visitArray(Array var1){
|
||||
Name var2 = ((ArrayType)var1.type).elemtype.tsym.getQualifiedName();
|
||||
int var6;
|
||||
if(var2.equals(var2.table.names.java_lang_Class)){
|
||||
ListBuffer var14 = new ListBuffer();
|
||||
Attribute[] var15 = var1.values;
|
||||
int var16 = var15.length;
|
||||
|
||||
for(var6 = 0; var6 < var16; ++var6){
|
||||
Attribute var7 = var15[var6];
|
||||
Type var8 = var7 instanceof UnresolvedClass ? ((UnresolvedClass)var7).classType : ((com.sun.tools.javac.code.Attribute.Class)var7).classType;
|
||||
var14.append(var8);
|
||||
}
|
||||
|
||||
this.value = new AnnotationProxyMaker.MirroredTypesExceptionProxy(var14.toList());
|
||||
}else{
|
||||
int var3 = var1.values.length;
|
||||
Class var4 = this.returnClass;
|
||||
this.returnClass = this.returnClass.getComponentType();
|
||||
|
||||
try{
|
||||
Object var5 = java.lang.reflect.Array.newInstance(this.returnClass, var3);
|
||||
|
||||
for(var6 = 0; var6 < var3; ++var6){
|
||||
var1.values[var6].accept(this);
|
||||
if(this.value == null || this.value instanceof ExceptionProxy){
|
||||
return;
|
||||
}
|
||||
|
||||
try{
|
||||
java.lang.reflect.Array.set(var5, var6, this.value);
|
||||
}catch(IllegalArgumentException var12){
|
||||
this.value = null;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.value = var5;
|
||||
}finally{
|
||||
this.returnClass = var4;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void visitEnum(Enum var1){
|
||||
if(this.returnClass.isEnum()){
|
||||
String var2 = var1.value.toString();
|
||||
|
||||
try{
|
||||
this.value = java.lang.Enum.valueOf((Class)this.returnClass, var2);
|
||||
}catch(IllegalArgumentException var4){
|
||||
this.value = new EnumConstantNotPresentExceptionProxy((Class)this.returnClass, var2);
|
||||
}
|
||||
}else{
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void visitCompound(Compound var1){
|
||||
try{
|
||||
Class var2 = this.returnClass.asSubclass(Annotation.class);
|
||||
this.value = AnnotationProxyMaker.generateAnnotation(var1, var2);
|
||||
}catch(ClassCastException var3){
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void visitError(Error var1){
|
||||
if(var1 instanceof UnresolvedClass){
|
||||
this.value = new AnnotationProxyMaker.MirroredTypeExceptionProxy(((UnresolvedClass)var1).classType);
|
||||
}else{
|
||||
this.value = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void typeMismatch(Method var1, final Attribute var2){
|
||||
class AnnotationTypeMismatchExceptionProxy extends ExceptionProxy{
|
||||
static final long serialVersionUID = 269L;
|
||||
final transient Method method;
|
||||
|
||||
AnnotationTypeMismatchExceptionProxy(Method var2x){
|
||||
this.method = var2x;
|
||||
}
|
||||
|
||||
public String toString(){
|
||||
return "<error>";
|
||||
}
|
||||
|
||||
protected RuntimeException generateException(){
|
||||
return new AnnotationTypeMismatchException(this.method, var2.type.toString());
|
||||
}
|
||||
}
|
||||
|
||||
this.value = new AnnotationTypeMismatchExceptionProxy(var1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,15 @@
|
|||
package mindustry.annotations.util;
|
||||
|
||||
import arc.struct.Array;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.sun.tools.javac.code.Attribute.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import java.lang.Class;
|
||||
import java.lang.annotation.*;
|
||||
import java.lang.reflect.*;
|
||||
|
||||
public class Selement<T extends Element>{
|
||||
public final T e;
|
||||
|
|
@ -13,6 +18,57 @@ public class Selement<T extends Element>{
|
|||
this.e = e;
|
||||
}
|
||||
|
||||
public Array<Selement<?>> enclosed(){
|
||||
return Array.with(e.getEnclosedElements()).map(Selement::new);
|
||||
}
|
||||
|
||||
public String fullName(){
|
||||
return e.toString();
|
||||
}
|
||||
|
||||
public Stype asType(){
|
||||
return new Stype((TypeElement)e);
|
||||
}
|
||||
|
||||
public Svar asVar(){
|
||||
return new Svar((VariableElement)e);
|
||||
}
|
||||
|
||||
public Smethod asMethod(){
|
||||
return new Smethod((ExecutableElement)e);
|
||||
}
|
||||
|
||||
public boolean isVar(){
|
||||
return e instanceof VariableElement;
|
||||
}
|
||||
|
||||
public boolean isType(){
|
||||
return e instanceof TypeElement;
|
||||
}
|
||||
|
||||
public boolean isMethod(){
|
||||
return e instanceof ExecutableElement;
|
||||
}
|
||||
|
||||
public Array<? extends AnnotationMirror> annotations(){
|
||||
return Array.with(e.getAnnotationMirrors());
|
||||
}
|
||||
|
||||
public <A extends Annotation> A annotation(Class<A> annotation){
|
||||
try{
|
||||
Method m = com.sun.tools.javac.code.AnnoConstruct.class.getDeclaredMethod("getAttribute", Class.class);
|
||||
m.setAccessible(true);
|
||||
Compound compound = (Compound)m.invoke(e, annotation);
|
||||
return compound == null ? null : AnnotationProxyMaker.generateAnnotation(compound, annotation);
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public <A extends Annotation> boolean has(Class<A> annotation){
|
||||
return annotation(annotation) != null;
|
||||
}
|
||||
|
||||
public Element up(){
|
||||
return e.getEnclosingElement();
|
||||
}
|
||||
|
|
@ -45,6 +101,6 @@ public class Selement<T extends Element>{
|
|||
|
||||
@Override
|
||||
public boolean equals(Object o){
|
||||
return o != null && o.getClass() == getClass() && ((Selement)o).e.equals(e);
|
||||
return o != null && o.getClass() == getClass() && e == ((Selement)o).e;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,10 +14,21 @@ public class Smethod extends Selement<ExecutableElement>{
|
|||
super(executableElement);
|
||||
}
|
||||
|
||||
public boolean isAny(Modifier... mod){
|
||||
for(Modifier m : mod){
|
||||
if(is(m)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean is(Modifier mod){
|
||||
return e.getModifiers().contains(mod);
|
||||
}
|
||||
|
||||
public Stype type(){
|
||||
return new Stype((TypeElement)up());
|
||||
}
|
||||
|
||||
public Array<TypeMirror> thrown(){
|
||||
return Array.with(e.getThrownTypes()).as(TypeMirror.class);
|
||||
}
|
||||
|
|
@ -34,6 +45,10 @@ public class Smethod extends Selement<ExecutableElement>{
|
|||
return Array.with(e.getParameters()).map(Svar::new);
|
||||
}
|
||||
|
||||
public boolean isVoid(){
|
||||
return ret().toString().equals("void");
|
||||
}
|
||||
|
||||
public TypeMirror ret(){
|
||||
return e.getReturnType();
|
||||
}
|
||||
|
|
@ -45,4 +60,8 @@ public class Smethod extends Selement<ExecutableElement>{
|
|||
public MethodTree tree(){
|
||||
return BaseProcessor.trees.getTree(e);
|
||||
}
|
||||
|
||||
public String simpleString(){
|
||||
return name() + "(" + params().toString(", ", p -> BaseProcessor.simpleName(p.mirror().toString())) + ")";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import mindustry.annotations.*;
|
|||
|
||||
import javax.lang.model.element.*;
|
||||
import javax.lang.model.type.*;
|
||||
import java.lang.annotation.*;
|
||||
|
||||
public class Stype extends Selement<TypeElement>{
|
||||
|
||||
|
|
@ -17,28 +16,30 @@ public class Stype extends Selement<TypeElement>{
|
|||
return new Stype((TypeElement)BaseProcessor.typeu.asElement(mirror));
|
||||
}
|
||||
|
||||
public String fullName(){
|
||||
return mirror().toString();
|
||||
}
|
||||
|
||||
public Array<Stype> interfaces(){
|
||||
return Array.with(e.getInterfaces()).map(Stype::of);
|
||||
}
|
||||
|
||||
public Array<Stype> allInterfaces(){
|
||||
return interfaces().flatMap(s -> s.allInterfaces().and(s)).distinct();
|
||||
}
|
||||
|
||||
public Array<Stype> superclasses(){
|
||||
Array<Stype> out = new Array<>();
|
||||
Stype sup = superclass();
|
||||
while(!sup.name().equals("Object")){
|
||||
out.add(sup);
|
||||
sup = sup.superclass();
|
||||
}
|
||||
return out;
|
||||
return Array.with(BaseProcessor.typeu.directSupertypes(mirror())).map(Stype::of);
|
||||
}
|
||||
|
||||
public Array<Stype> allSuperclasses(){
|
||||
return superclasses().flatMap(s -> s.allSuperclasses().and(s)).distinct();
|
||||
}
|
||||
|
||||
public Stype superclass(){
|
||||
return new Stype((TypeElement)BaseProcessor.typeu.asElement(BaseProcessor.typeu.directSupertypes(mirror()).get(0)));
|
||||
}
|
||||
|
||||
public <A extends Annotation> A annotation(Class<A> annotation){
|
||||
return e.getAnnotation(annotation);
|
||||
}
|
||||
|
||||
public Array<Svar> fields(){
|
||||
return Array.with(e.getEnclosedElements()).select(e -> e instanceof VariableElement).map(e -> new Svar((VariableElement)e));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,13 @@ public class Svar extends Selement<VariableElement>{
|
|||
super(e);
|
||||
}
|
||||
|
||||
public boolean isAny(Modifier... mods){
|
||||
for(Modifier m : mods){
|
||||
if(is(m)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean is(Modifier mod){
|
||||
return e.getModifiers().contains(mod);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
package mindustry.annotations.util;
|
||||
|
||||
import arc.struct.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public boolean has(String type){
|
||||
return writers.containsKey(type) && readers.containsKey(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
17
annotations/src/main/resources/classids.properties
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
#Maps entity names to IDs. Autogenerated.
|
||||
|
||||
dagger=0
|
||||
draug=10
|
||||
mindustry.entities.def.BulletComp=1
|
||||
mindustry.entities.def.DecalComp=2
|
||||
mindustry.entities.def.FireComp=3
|
||||
mindustry.entities.def.GroundEffectComp=4
|
||||
mindustry.entities.def.PlayerComp=5
|
||||
mindustry.entities.def.PuddleComp=6
|
||||
mindustry.entities.def.StandardEffectComp=7
|
||||
mindustry.entities.def.TileComp=8
|
||||
mindustry.type.Weather.WeatherComp=13
|
||||
phantom=11
|
||||
spirit=12
|
||||
vanguard=9
|
||||
wraith=14
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:drownTime,type:float,size:4},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:rotation,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:damage,type:float,size:4},{name:data,type:java.lang.Object,size:-1},{name:lifetime,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:time,type:float,size:4},{name:type,type:mindustry.entities.bullet.BulletType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:color,type:arc.graphics.Color,size:-1},{name:lifetime,type:float,size:4},{name:rotation,type:float,size:4},{name:time,type:float,size:4},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:baseFlammability,type:float,size:4},{name:block,type:mindustry.world.Block,size:-1},{name:lifetime,type:float,size:4},{name:puddleFlammability,type:float,size:4},{name:tile,type:mindustry.world.Tile,size:-1},{name:time,type:float,size:4},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:color,type:arc.graphics.Color,size:-1},{name:data,type:java.lang.Object,size:-1},{name:lifetime,type:float,size:4},{name:offsetX,type:float,size:4},{name:offsetY,type:float,size:4},{name:rotation,type:float,size:4},{name:time,type:float,size:4},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:baseRotation,type:float,size:4},{name:drownTime,type:float,size:4},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:rotation,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:drownTime,type:float,size:4},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:mineTile,type:mindustry.world.Tile,size:-1},{name:rotation,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:admin,type:boolean,size:1},{name:color,type:arc.graphics.Color,size:-1},{name:lastText,type:java.lang.String,size:-1},{name:mouseX,type:float,size:4},{name:mouseY,type:float,size:4},{name:name,type:java.lang.String,size:-1},{name:team,type:mindustry.game.Team,size:-1},{name:textFadeTime,type:float,size:4},{name:typing,type:boolean,size:1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:accepting,type:float,size:4},{name:amount,type:float,size:4},{name:generation,type:int,size:4},{name:lastRipple,type:float,size:4},{name:liquid,type:mindustry.type.Liquid,size:-1},{name:tile,type:mindustry.world.Tile,size:-1},{name:updateTime,type:float,size:4},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:color,type:arc.graphics.Color,size:-1},{name:data,type:java.lang.Object,size:-1},{name:lifetime,type:float,size:4},{name:offsetX,type:float,size:4},{name:offsetY,type:float,size:4},{name:rotation,type:float,size:4},{name:time,type:float,size:4},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:health,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:drownTime,type:float,size:4},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:rotation,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:drownTime,type:float,size:4},{name:elevation,type:float,size:4},{name:health,type:float,size:4},{name:rotation,type:float,size:4},{name:team,type:mindustry.game.Team,size:-1},{name:type,type:mindustry.type.UnitType,size:-1},{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -0,0 +1 @@
|
|||
{fields:[{name:x,type:float,size:4},{name:y,type:float,size:4}]}
|
||||
|
|
@ -163,6 +163,9 @@ allprojects{
|
|||
project(":desktop"){
|
||||
apply plugin: "java"
|
||||
|
||||
compileJava.options.fork = true
|
||||
compileJava.options.compilerArgs += ["-XDignore.symbol.file"]
|
||||
|
||||
dependencies{
|
||||
compile project(":core")
|
||||
|
||||
|
|
@ -249,8 +252,10 @@ project(":core"){
|
|||
compile "org.lz4:lz4-java:1.4.1"
|
||||
compile arcModule("arc-core")
|
||||
compile arcModule("extensions:freetype")
|
||||
compile arcModule("extensions:g3d")
|
||||
compile arcModule("extensions:fx")
|
||||
compile arcModule("extensions:arcnet")
|
||||
compile "org.mozilla:rhino:1.7.11"
|
||||
compile "org.mozilla:rhino-runtime:1.7.12"
|
||||
if(localArc() && debugged()) compile arcModule("extensions:recorder")
|
||||
|
||||
compileOnly project(":annotations")
|
||||
|
|
@ -294,7 +299,7 @@ project(":tools"){
|
|||
compile "com.badlogicgames.gdx:gdx-freetype-platform:$gdxVersion:natives-desktop"
|
||||
compile "org.reflections:reflections:0.9.12"
|
||||
|
||||
compile arcModule("backends:backend-sdl")
|
||||
compile arcModule("backends:backend-headless")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
core/assets-raw/sprites/blocks/distribution/cross.png
Normal file
|
After Width: | Height: | Size: 170 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 513 B |
BIN
core/assets-raw/sprites/blocks/distribution/mass-conveyor.png
Normal file
|
After Width: | Height: | Size: 412 B |
BIN
core/assets-raw/sprites/blocks/environment/cliff.png
Normal file
|
After Width: | Height: | Size: 258 B |
|
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 291 B |
BIN
core/assets-raw/sprites/blocks/environment/slag.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 239 B After Width: | Height: | Size: 290 B |
|
Before Width: | Height: | Size: 240 B After Width: | Height: | Size: 291 B |
|
Before Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 991 B |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
BIN
core/assets-raw/sprites/blocks/mechs/alpha-mech-pad.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
|
Before Width: | Height: | Size: 579 B After Width: | Height: | Size: 579 B |
BIN
core/assets-raw/sprites/blocks/units/ground-factory-top.png
Normal file
|
After Width: | Height: | Size: 663 B |
BIN
core/assets-raw/sprites/blocks/units/ground-factory.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 183 B After Width: | Height: | Size: 242 B |
|
Before Width: | Height: | Size: 178 B After Width: | Height: | Size: 231 B |
|
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 261 B |
|
Before Width: | Height: | Size: 133 B After Width: | Height: | Size: 201 B |
BIN
core/assets-raw/sprites/effects/particle.png
Normal file
|
After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 207 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 265 B |
|
Before Width: | Height: | Size: 266 B |
|
Before Width: | Height: | Size: 143 B After Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 141 B After Width: | Height: | Size: 213 B |
|
Before Width: | Height: | Size: 179 B |
|
Before Width: | Height: | Size: 242 B After Width: | Height: | Size: 213 B |
|
Before Width: | Height: | Size: 423 B |
|
Before Width: | Height: | Size: 180 B |
|
Before Width: | Height: | Size: 164 B |
|
Before Width: | Height: | Size: 299 B |
|
Before Width: | Height: | Size: 440 B |
8
core/assets-raw/sprites/rubble/pack.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
duplicatePadding: true,
|
||||
combineSubdirectories: true,
|
||||
flattenPaths: true,
|
||||
maxWidth: 2048,
|
||||
maxHeight: 2048,
|
||||
fast: true
|
||||
}
|
||||
|
Before Width: | Height: | Size: 237 B After Width: | Height: | Size: 237 B |
|
Before Width: | Height: | Size: 249 B After Width: | Height: | Size: 249 B |
|
Before Width: | Height: | Size: 320 B After Width: | Height: | Size: 320 B |
|
Before Width: | Height: | Size: 349 B After Width: | Height: | Size: 349 B |
|
Before Width: | Height: | Size: 520 B After Width: | Height: | Size: 520 B |
|
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 870 B |
|
Before Width: | Height: | Size: 991 B After Width: | Height: | Size: 991 B |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
BIN
core/assets-raw/sprites/units/alpha-base.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
BIN
core/assets-raw/sprites/units/alpha-leg.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
BIN
core/assets-raw/sprites/units/alpha.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
core/assets-raw/sprites/units/dart.png
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 176 B After Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 170 B After Width: | Height: | Size: 170 B |
|
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
|
Before Width: | Height: | Size: 657 B After Width: | Height: | Size: 657 B |
|
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 270 B |
|
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 528 B |
|
Before Width: | Height: | Size: 361 B After Width: | Height: | Size: 361 B |
|
Before Width: | Height: | Size: 181 B After Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 200 B |
|
Before Width: | Height: | Size: 509 B After Width: | Height: | Size: 509 B |