diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java index 8124930825..6441cacf2f 100644 --- a/annotations/src/main/java/mindustry/annotations/Annotations.java +++ b/annotations/src/main/java/mindustry/annotations/Annotations.java @@ -3,10 +3,40 @@ package mindustry.annotations; import java.lang.annotation.*; public class Annotations{ + //region entity interfaces + + /** Indicates multiple inheritance on a component type. */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + public @interface Depends{ + Class[] value(); + } + + /** Indicates that a component def is present on all entities. */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + public @interface BaseComponent{ + } + + /** Indicates an entity definition. */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + public @interface EntityDef{ + Class[] value(); + } + + /** Indicates an internal interface for entity components. */ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.SOURCE) + public @interface EntityInterface{ + } + + //endregion + //region misc. utility @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) - public @interface StyleDefaults { + public @interface StyleDefaults{ } /** Indicates that a method should always call its super version. */ @@ -16,10 +46,10 @@ public class Annotations{ } - /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class.*/ + /** Annotation that allows overriding CallSuper annotation. To be used on method that overrides method with CallSuper annotation from parent class. */ @Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) - public @interface OverrideCallSuper { + public @interface OverrideCallSuper{ } /** Marks a class as serializable. */ @@ -29,6 +59,9 @@ public class Annotations{ } + //endregion + //region struct + /** Marks a class as a special value type struct. Class name must end in 'Struct'. */ @Target(ElementType.TYPE) @Retention(RetentionPolicy.SOURCE) @@ -44,6 +77,9 @@ public class Annotations{ int value(); } + //endregion + //region remote + public enum PacketPriority{ /** Gets put in a queue and processed if not connected. */ normal, @@ -138,4 +174,6 @@ public class Annotations{ public @interface ReadClass{ Class value(); } + + //endregion } diff --git a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java index effbb72400..17e4238e30 100644 --- a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java @@ -1,9 +1,16 @@ package mindustry.annotations; +import arc.struct.*; +import com.squareup.javapoet.*; +import com.sun.source.util.*; +import mindustry.annotations.util.*; + import javax.annotation.processing.*; import javax.lang.model.*; import javax.lang.model.element.*; import javax.lang.model.util.*; +import javax.tools.Diagnostic.*; +import java.lang.annotation.*; import java.util.*; @SupportedSourceVersion(SourceVersion.RELEASE_8) @@ -15,8 +22,11 @@ public abstract class BaseProcessor extends AbstractProcessor{ public static Elements elementu; public static Filer filer; public static Messager messager; + public static Trees trees; protected int round; + protected int rounds = 1; + protected RoundEnvironment env; public static String getMethodName(Element element){ return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName(); @@ -27,19 +37,48 @@ public abstract class BaseProcessor extends AbstractProcessor{ || type.equals("long") || type.equals("float") || type.equals("double") || type.equals("char"); } - @Override - public synchronized void init(ProcessingEnvironment processingEnv){ - super.init(processingEnv); + public static void write(TypeSpec.Builder builder) throws Exception{ + JavaFile.builder(packageName, builder.build()).build().writeTo(BaseProcessor.filer); + } - typeu = processingEnv.getTypeUtils(); - elementu = processingEnv.getElementUtils(); - filer = processingEnv.getFiler(); - messager = processingEnv.getMessager(); + public Array types(Class type){ + return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof TypeElement) + .map(e -> new Stype((TypeElement)e)); + } + + public Array fields(Class type){ + return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof VariableElement) + .map(e -> new Svar((VariableElement)e)); + } + + public Array methods(Class type){ + return Array.with(env.getElementsAnnotatedWith(type)).select(e -> e instanceof ExecutableElement) + .map(e -> new Smethod((ExecutableElement)e)); + } + + public void err(String message){ + messager.printMessage(Kind.ERROR, message); + } + + public void err(String message, Element elem){ + messager.printMessage(Kind.ERROR, message, elem); + } + + @Override + public synchronized void init(ProcessingEnvironment env){ + super.init(env); + + trees = Trees.instance(env); + typeu = env.getTypeUtils(); + elementu = env.getElementUtils(); + filer = env.getFiler(); + messager = env.getMessager(); } @Override public boolean process(Set annotations, RoundEnvironment roundEnv){ - if(round++ != 0) return false; //only process 1 round + if(round++ >= rounds) return false; //only process 1 round + this.env = roundEnv; try{ process(roundEnv); }catch(Exception e){ diff --git a/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java b/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java new file mode 100644 index 0000000000..664cf6a802 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java @@ -0,0 +1,258 @@ +package mindustry.annotations.impl; + +import arc.struct.*; +import arc.util.*; +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 javax.annotation.processing.*; +import javax.lang.model.element.*; +import javax.lang.model.type.*; + +@SupportedAnnotationTypes({ +"mindustry.annotations.Annotations.EntityDef", +"mindustry.annotations.Annotations.EntityInterface", +"mindustry.annotations.Annotations.BaseComponent" +}) +public class EntityProcess extends BaseProcessor{ + Array definitions = new Array<>(); + Array baseComponents; + ObjectMap> componentDependencies = new ObjectMap<>(); + ObjectMap> defComponents = new ObjectMap<>(); + + { + rounds = 2; + } + + @Override + public void process(RoundEnvironment env) throws Exception{ + + //round 1: get component classes and generate interfaces for them + if(round == 1){ + baseComponents = types(BaseComponent.class); + Array allDefs = types(EntityDef.class); + + ObjectSet allComponents = new ObjectSet<>(); + + //find all components used... + for(Stype type : allDefs){ + allComponents.addAll(allComponents(type)); + } + + //add all components w/ dependencies + allComponents.addAll(types(Depends.class)); + + //create component interfaces + for(Stype component : allComponents){ + TypeSpec.Builder inter = TypeSpec.interfaceBuilder(interfaceName(component)).addModifiers(Modifier.PUBLIC).addAnnotation(EntityInterface.class); + + for(Stype extraInterface : component.interfaces()){ + inter.addSuperinterface(extraInterface.mirror()); + } + + Array depends = getDependencies(component); + for(Stype type : depends){ + inter.addSuperinterface(ClassName.get(packageName, interfaceName(type))); + } + + for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.is(Modifier.TRANSIENT))){ + String cname = Strings.capitalize(field.name()); + //getter + inter.addMethod(MethodSpec.methodBuilder("get" + cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).returns(field.tname()).build()); + //setter + if(!field.is(Modifier.FINAL)) inter.addMethod(MethodSpec.methodBuilder("set" + cname).addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC).addParameter(field.tname(), field.name()).build()); + } + + //add utility methods to interface + for(Smethod method : component.methods()){ + inter.addMethod(MethodSpec.methodBuilder(method.name()) + .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()); + } + + write(inter); + } + + //look at each definition + for(Stype type : allDefs){ + if(!type.name().endsWith("Def")){ + err("All entity def names must end with 'Def'", type.e); + } + String name = type.name().replace("Def", "Gen"); //TODO remove 'gen' + TypeSpec.Builder builder = TypeSpec.classBuilder(name).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + + Array components = allComponents(type); + ObjectMap> methods = new ObjectMap<>(); + + //add all components + for(Stype comp : components){ + + //write fields to the class; ignoring transient ones + Array fields = comp.fields().select(f -> !f.is(Modifier.TRANSIENT)); + for(Svar f : fields){ + VariableTree tree = f.tree(); + FieldSpec.Builder fbuilder = FieldSpec.builder(f.tname(), f.name(), Modifier.PUBLIC); + //add initializer if it exists + if(tree.getInitializer() != null){ + fbuilder.initializer(tree.getInitializer().toString()); + } + builder.addField(fbuilder.build()); + } + + //get all utility methods from components + for(Smethod elem : comp.methods()){ + methods.getOr(elem.toString(), Array::new).add(elem); + } + } + + //add all methods from components + for(ObjectMap.Entry> entry : methods){ + //representative method + Smethod first = entry.value.first(); + //build method using same params/returns + MethodSpec.Builder mbuilder = MethodSpec.methodBuilder(first.name()).addModifiers(Modifier.PUBLIC, Modifier.FINAL); + mbuilder.addTypeVariables(first.typeVariables().map(TypeVariableName::get)); + mbuilder.returns(first.retn()); + + for(Svar var : first.params()){ + mbuilder.addParameter(var.tname(), var.name()); + } + + boolean returns = !first.ret().toString().equals("void"); + + for(Smethod elem : entry.value){ + //wrap scope to prevent variable leakage + if(!returns) mbuilder.beginControlFlow(""); + + //get all statements in the method, copy them over + MethodTree methodTree = elem.tree(); + BlockTree blockTree = methodTree.getBody(); + for(StatementTree st : blockTree.getStatements()){ + String state = st.toString(); + mbuilder.addStatement(state.substring(0, state.length() - 1)); + } + + //end scope + if(!returns) mbuilder.endControlFlow(); + } + + builder.addMethod(mbuilder.build()); + } + + definitions.add(new Definition(builder, type)); + + } + }else{ + //round 2: generate actual classes and implement interfaces + Array interfaces = types(EntityInterface.class); + + //implement each definition + for(Definition def : definitions){ + Array components = allComponents(def.base); + + //get interface for each component + for(Stype comp : components){ + //implement the interface + Stype inter = interfaces.find(i -> i.name().equals(interfaceName(comp))); + def.builder.addSuperinterface(inter.tname()); + + //generate getter/setter for each method + for(Smethod method : inter.methods()){ + if(method.name().length() <= 3) continue; + + String var = Strings.camelize(method.name().substring(3)); + if(method.name().startsWith("get")){ + def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).build()); + }else if(method.name().startsWith("set")){ + def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build()); + } + } + } + + write(def.builder); + } + } + } + + 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 allComponents(Stype type){ + if(!defComponents.containsKey(type)){ + //get base defs + Array components = Array.with(mirrors(type)).map(Stype::of); + ObjectSet 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 getDependencies(Stype component){ + if(!componentDependencies.containsKey(component)){ + ObjectSet out = new ObjectSet<>(); + out.addAll(component.superclasses()); + + //get dependency classes + if(component.annotation(Depends.class) != null){ + try{ + component.annotation(Depends.class).value(); + }catch(MirroredTypesException e){ + out.addAll(Array.with(e.getTypeMirrors()).map(Stype::of)); + } + } + + //out now contains the base dependencies; finish constructing the tree + ObjectSet result = new ObjectSet<>(); + for(Stype type : out){ + result.add(type); + result.addAll(getDependencies(type)); + } + + if(component.annotation(BaseComponent.class) == null){ + result.addAll(baseComponents); + } + + componentDependencies.put(component, result.asArray()); + } + + return componentDependencies.get(component); + } + + TypeMirror[] mirrors(Stype type){ + try{ + type.annotation(EntityDef.class).value(); + }catch(MirroredTypesException e){ + return e.getTypeMirrors().toArray(new TypeMirror[0]); + } + throw new IllegalArgumentException("Missing components: " + type); + } + + class Definition{ + final TypeSpec.Builder builder; + final Stype base; + + public Definition(Builder builder, Stype base){ + this.builder = builder; + this.base = base; + } + } +} diff --git a/annotations/src/main/java/mindustry/annotations/remote/RemoteProcess.java b/annotations/src/main/java/mindustry/annotations/remote/RemoteProcess.java index b54db3ee15..8abd0b82b8 100644 --- a/annotations/src/main/java/mindustry/annotations/remote/RemoteProcess.java +++ b/annotations/src/main/java/mindustry/annotations/remote/RemoteProcess.java @@ -42,96 +42,83 @@ public class RemoteProcess extends BaseProcessor{ //list of all method entries private ArrayList classes; + { + rounds = 2; + } + @Override - public boolean process(Set annotations, RoundEnvironment roundEnv){ - if(round > 1) return false; //only process 2 rounds + public void process(RoundEnvironment roundEnv) throws Exception{ + //round 1: find all annotations, generate *writers* + if(round == 1){ + //get serializers + serializers = new IOFinder().findSerializers(roundEnv); + //last method ID used + int lastMethodID = 0; + //find all elements with the Remote annotation + elements = roundEnv.getElementsAnnotatedWith(Remote.class); + //map of all classes to generate by name + classMap = new HashMap<>(); + //list of all method entries + methods = new ArrayList<>(); + //list of all method entries + classes = new ArrayList<>(); - round++; + List orderedElements = new ArrayList<>(elements); + orderedElements.sort(Comparator.comparing(Object::toString)); - try{ + //create methods + for(Element element : orderedElements){ + Remote annotation = element.getAnnotation(Remote.class); - //round 1: find all annotations, generate *writers* - if(round == 1){ - //get serializers - serializers = new IOFinder().findSerializers(roundEnv); - //last method ID used - int lastMethodID = 0; - //find all elements with the Remote annotation - elements = roundEnv.getElementsAnnotatedWith(Remote.class); - //map of all classes to generate by name - classMap = new HashMap<>(); - //list of all method entries - methods = new ArrayList<>(); - //list of all method entries - classes = new ArrayList<>(); - - List orderedElements = new ArrayList<>(elements); - orderedElements.sort(Comparator.comparing(Object::toString)); - - //create methods - for(Element element : orderedElements){ - Remote annotation = element.getAnnotation(Remote.class); - - //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); - } - - //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); - } - - //get and create class entry if needed - if(!classMap.containsKey(callLocation)){ - ClassEntry clas = new ClassEntry(callLocation); - classMap.put(callLocation, clas); - classes.add(clas); - } - - ClassEntry entry = classMap.get(callLocation); - - //create and add entry - MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element), annotation.targets(), annotation.variants(), - annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, (ExecutableElement)element, annotation.priority()); - - entry.methods.add(method); - methods.add(method); + //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); } - //create read/write generators - RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); + //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); + } - //generate the methods to invoke (write) - writegen.generateFor(classes, packageName); + //get and create class entry if needed + if(!classMap.containsKey(callLocation)){ + ClassEntry clas = new ClassEntry(callLocation); + classMap.put(callLocation, clas); + classes.add(clas); + } - return true; - }else if(round == 2){ //round 2: generate all *readers* - RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); + ClassEntry entry = classMap.get(callLocation); - //generate server readers - readgen.generateFor(methods.stream().filter(method -> method.where.isClient).collect(Collectors.toList()), readServerName, packageName, true); - //generate client readers - readgen.generateFor(methods.stream().filter(method -> method.where.isServer).collect(Collectors.toList()), readClientName, packageName, false); + //create and add entry + MethodEntry method = new MethodEntry(entry.name, BaseProcessor.getMethodName(element), annotation.targets(), annotation.variants(), + annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, (ExecutableElement)element, annotation.priority()); - //create class for storing unique method hash - TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC); - hashBuilder.addJavadoc(autogenWarning); - hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) - .initializer("$1L", Objects.hash(methods)).build()); - - //build and write resulting hash class - TypeSpec spec = hashBuilder.build(); - JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer); - - return true; + entry.methods.add(method); + methods.add(method); } - }catch(Exception e){ - e.printStackTrace(); - throw new RuntimeException(e); - } + //create read/write generators + RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializers); - return false; + //generate the methods to invoke (write) + writegen.generateFor(classes, packageName); + }else if(round == 2){ //round 2: generate all *readers* + RemoteReadGenerator readgen = new RemoteReadGenerator(serializers); + + //generate server readers + readgen.generateFor(methods.stream().filter(method -> method.where.isClient).collect(Collectors.toList()), readServerName, packageName, true); + //generate client readers + readgen.generateFor(methods.stream().filter(method -> method.where.isServer).collect(Collectors.toList()), readClientName, packageName, false); + + //create class for storing unique method hash + TypeSpec.Builder hashBuilder = TypeSpec.classBuilder("MethodHash").addModifiers(Modifier.PUBLIC); + hashBuilder.addJavadoc(autogenWarning); + hashBuilder.addField(FieldSpec.builder(int.class, "HASH", Modifier.STATIC, Modifier.PUBLIC, Modifier.FINAL) + .initializer("$1L", Objects.hash(methods)).build()); + + //build and write resulting hash class + TypeSpec spec = hashBuilder.build(); + JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer); + } } } diff --git a/annotations/src/main/java/mindustry/annotations/util/Selement.java b/annotations/src/main/java/mindustry/annotations/util/Selement.java new file mode 100644 index 0000000000..b9d8aa4e32 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/util/Selement.java @@ -0,0 +1,46 @@ +package mindustry.annotations.util; + +import com.squareup.javapoet.*; +import mindustry.annotations.*; + +import javax.lang.model.element.*; +import javax.lang.model.type.*; + +public class Selement{ + public final T e; + + public Selement(T e){ + this.e = e; + } + + public TypeMirror mirror(){ + return e.asType(); + } + + public TypeName tname(){ + return TypeName.get(mirror()); + } + + public ClassName cname(){ + return ClassName.get((TypeElement)BaseProcessor.typeu.asElement(mirror())); + } + + public String name(){ + return e.getSimpleName().toString(); + } + + @Override + public String toString(){ + return e.toString(); + } + + @Override + public int hashCode(){ + return e.hashCode(); + } + + @Override + public boolean equals(Object o){ + return o != null && o.getClass() == getClass() && ((Selement)o).e.equals(e); + } +} diff --git a/annotations/src/main/java/mindustry/annotations/util/Smethod.java b/annotations/src/main/java/mindustry/annotations/util/Smethod.java new file mode 100644 index 0000000000..8b840c2977 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/util/Smethod.java @@ -0,0 +1,36 @@ +package mindustry.annotations.util; + +import arc.struct.*; +import com.squareup.javapoet.*; +import com.sun.source.tree.*; +import mindustry.annotations.*; + +import javax.lang.model.element.*; +import javax.lang.model.type.*; + +public class Smethod extends Selement{ + + public Smethod(ExecutableElement executableElement){ + super(executableElement); + } + + public Array typeVariables(){ + return Array.with(e.getTypeParameters()).as(TypeParameterElement.class); + } + + public Array params(){ + return Array.with(e.getParameters()).map(Svar::new); + } + + public TypeMirror ret(){ + return e.getReturnType(); + } + + public TypeName retn(){ + return TypeName.get(ret()); + } + + public MethodTree tree(){ + return BaseProcessor.trees.getTree(e); + } +} diff --git a/annotations/src/main/java/mindustry/annotations/util/Stype.java b/annotations/src/main/java/mindustry/annotations/util/Stype.java new file mode 100644 index 0000000000..99b9b9f781 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/util/Stype.java @@ -0,0 +1,60 @@ +package mindustry.annotations.util; + +import arc.struct.*; +import mindustry.annotations.*; + +import javax.lang.model.element.*; +import javax.lang.model.type.*; +import java.lang.annotation.*; + +public class Stype extends Selement{ + + public Stype(TypeElement typeElement){ + super(typeElement); + } + + public static Stype of(TypeMirror mirror){ + return new Stype((TypeElement)BaseProcessor.typeu.asElement(mirror)); + } + + public Array interfaces(){ + return Array.with(e.getInterfaces()).map(Stype::of); + } + + public Array superclasses(){ + Array out = new Array<>(); + Stype sup = superclass(); + while(!sup.name().equals("Object")){ + out.add(sup); + sup = sup.superclass(); + } + return out; + } + + public Stype superclass(){ + return new Stype((TypeElement)BaseProcessor.typeu.asElement(BaseProcessor.typeu.directSupertypes(mirror()).get(0))); + } + + public A annotation(Class annotation){ + return e.getAnnotation(annotation); + } + + public Array fields(){ + return Array.with(e.getEnclosedElements()).select(e -> e instanceof VariableElement).map(e -> new Svar((VariableElement)e)); + } + + public Array methods(){ + return Array.with(e.getEnclosedElements()).select(e -> e instanceof ExecutableElement + && !e.getSimpleName().toString().contains("<")).map(e -> new Smethod((ExecutableElement)e)); + } + + public Array constructors(){ + return Array.with(e.getEnclosedElements()).select(e -> e instanceof ExecutableElement + && e.getSimpleName().toString().contains("<")).map(e -> new Smethod((ExecutableElement)e)); + } + + @Override + public TypeMirror mirror(){ + return e.asType(); + } +} diff --git a/annotations/src/main/java/mindustry/annotations/util/Svar.java b/annotations/src/main/java/mindustry/annotations/util/Svar.java new file mode 100644 index 0000000000..cf58d7a6f5 --- /dev/null +++ b/annotations/src/main/java/mindustry/annotations/util/Svar.java @@ -0,0 +1,21 @@ +package mindustry.annotations.util; + +import com.sun.source.tree.*; +import mindustry.annotations.*; + +import javax.lang.model.element.*; + +public class Svar extends Selement{ + + public Svar(VariableElement e){ + super(e); + } + + public boolean is(Modifier mod){ + return e.getModifiers().contains(mod); + } + + public VariableTree tree(){ + return (VariableTree)BaseProcessor.trees.getTree(e); + } +} diff --git a/core/src/mindustry/entities/Effects.java b/core/src/mindustry/entities/Effects.java index 6f2a04033a..c1f85aceef 100644 --- a/core/src/mindustry/entities/Effects.java +++ b/core/src/mindustry/entities/Effects.java @@ -1,15 +1,14 @@ package mindustry.entities; -import arc.Core; -import arc.struct.Array; -import arc.func.Cons; -import arc.graphics.Color; +import arc.*; +import arc.func.*; +import arc.graphics.*; import arc.graphics.g2d.*; -import arc.math.Mathf; -import arc.math.geom.Position; -import arc.util.pooling.Pools; -import mindustry.entities.type.EffectEntity; -import mindustry.entities.traits.ScaleTrait; +import arc.math.*; +import arc.math.geom.*; +import arc.struct.*; +import arc.util.pooling.*; +import mindustry.entities.type.*; public class Effects{ private static final EffectContainer container = new EffectContainer(); @@ -126,7 +125,7 @@ public class Effects{ } } - public static class EffectContainer implements ScaleTrait{ + public static class EffectContainer implements Scaled{ public float x, y, time, lifetime, rotation; public Color color; public int id; diff --git a/core/src/mindustry/entities/def/EntityDefs.java b/core/src/mindustry/entities/def/EntityDefs.java new file mode 100644 index 0000000000..fc59694193 --- /dev/null +++ b/core/src/mindustry/entities/def/EntityDefs.java @@ -0,0 +1,102 @@ +package mindustry.entities.def; + +import arc.math.geom.*; +import mindustry.annotations.Annotations.*; +import mindustry.entities.bullet.*; +import mindustry.entities.units.*; +import mindustry.gen.*; +import mindustry.net.*; + +class EntityDefs{ + + @EntityDef({UnitComp.class, ConnectionComp.class}) + class PlayerDef{} + + @EntityDef({BulletComp.class, VelComp.class}) + class BulletDef{} + + @Depends({HealthComp.class, VelComp.class, StatusComp.class}) + class UnitComp{ + + } + + class HealthComp{ + float health, maxHealth; + boolean dead; + + float healthf(){ + return health / maxHealth; + } + } + + abstract class PosComp implements Position{ + float x, y; + + void set(float x, float y){ + this.x = x; + this.y = y; + } + } + + @Depends(PosComp.class) + class VelComp{ + //transient fields act as imports from any other component clases; these are ignored by the generator + transient float x, y; + + final Vec2 vel = new Vec2(); + + void update(){ + x += vel.x; + y += vel.y; + vel.scl(0.9f); + } + } + + @Depends(PosComp.class) + class HitboxComp{ + transient float x, y; + + float hitSize; + + boolean collides(Hitboxc other){ + return Intersector.overlapsRect(x - hitSize/2f, y - hitSize/2f, hitSize, hitSize, + other.getX() - other.getHitSize()/2f, other.getY() - other.getHitSize()/2f, other.getHitSize(), other.getHitSize()); + } + } + + class StatusComp{ + final Statuses statuses = new Statuses(); + + void update(){ + statuses.update(null); + } + } + + class ConnectionComp{ + NetConnection connection; + } + + class BulletComp{ + BulletType bullet; + + void init(){ + bullet.init(); + } + } + + @BaseComponent + class EntityComp{ + int id; + + void init(){} + + T as(Class type){ + return (T)this; + } + } + + static void testing(){ + Entityc abullet = new BulletGen(); + Entityc aplayer = new PlayerGen(); + } +} diff --git a/core/src/mindustry/entities/traits/ScaleTrait.java b/core/src/mindustry/entities/traits/ScaleTrait.java deleted file mode 100644 index 2a572659b2..0000000000 --- a/core/src/mindustry/entities/traits/ScaleTrait.java +++ /dev/null @@ -1,43 +0,0 @@ -package mindustry.entities.traits; - -import arc.math.Interpolation; - -public interface ScaleTrait{ - /** 0 to 1. */ - float fin(); - - /** 1 to 0 */ - default float fout(){ - return 1f - fin(); - } - - /** 1 to 0 */ - default float fout(Interpolation i){ - return i.apply(fout()); - } - - /** 1 to 0, ending at the specified margin */ - default float fout(float margin){ - float f = fin(); - if(f >= 1f - margin){ - return 1f - (f - (1f - margin)) / margin; - }else{ - return 1f; - } - } - - /** 0 to 1 **/ - default float fin(Interpolation i){ - return i.apply(fin()); - } - - /** 0 to 1 */ - default float finpow(){ - return Interpolation.pow3Out.apply(fin()); - } - - /** 0 to 1 to 0 */ - default float fslope(){ - return (0.5f - Math.abs(fin() - 0.5f)) * 2f; - } -} diff --git a/core/src/mindustry/entities/traits/TimeTrait.java b/core/src/mindustry/entities/traits/TimeTrait.java index cf89d7dca8..f6c3938e3d 100644 --- a/core/src/mindustry/entities/traits/TimeTrait.java +++ b/core/src/mindustry/entities/traits/TimeTrait.java @@ -1,9 +1,9 @@ package mindustry.entities.traits; -import arc.math.Mathf; +import arc.math.*; import arc.util.Time; -public interface TimeTrait extends ScaleTrait, Entity{ +public interface TimeTrait extends Scaled, Entity{ float lifetime(); diff --git a/core/src/mindustry/entities/type/Bullet.java b/core/src/mindustry/entities/type/Bullet.java index f7e676ecc9..6d9a6eb12e 100644 --- a/core/src/mindustry/entities/type/Bullet.java +++ b/core/src/mindustry/entities/type/Bullet.java @@ -16,7 +16,7 @@ import mindustry.world.*; import static mindustry.Vars.*; -public class Bullet extends SolidEntity implements DamageTrait, ScaleTrait, Poolable, DrawTrait, VelocityTrait, TimeTrait, TeamTrait, AbsorbTrait{ +public class Bullet extends SolidEntity implements DamageTrait, Scaled, Poolable, DrawTrait, VelocityTrait, TimeTrait, TeamTrait, AbsorbTrait{ public Interval timer = new Interval(3); private float lifeScl; diff --git a/core/src/mindustry/entities/type/TimedEntity.java b/core/src/mindustry/entities/type/TimedEntity.java index 413b1c3fd5..17b688002d 100644 --- a/core/src/mindustry/entities/type/TimedEntity.java +++ b/core/src/mindustry/entities/type/TimedEntity.java @@ -1,10 +1,9 @@ package mindustry.entities.type; -import arc.util.pooling.Pool.Poolable; -import mindustry.entities.traits.ScaleTrait; -import mindustry.entities.traits.TimeTrait; +import arc.util.pooling.Pool.*; +import mindustry.entities.traits.*; -public abstract class TimedEntity extends BaseEntity implements ScaleTrait, TimeTrait, Poolable{ +public abstract class TimedEntity extends BaseEntity implements TimeTrait, Poolable{ public float time; @Override