From 5eb3f0f3de68dbcb139cc292b1c44e94f5165371 Mon Sep 17 00:00:00 2001 From: Anuken Date: Sun, 2 Feb 2020 12:25:46 -0500 Subject: [PATCH] Interface + base component support --- .../mindustry/annotations/Annotations.java | 7 +- .../mindustry/annotations/BaseProcessor.java | 9 +++ .../annotations/impl/EntityProcess.java | 33 ++++++++-- .../mindustry/annotations/util/Smethod.java | 4 ++ .../mindustry/annotations/util/Stype.java | 4 ++ .../mindustry/entities/def/EntityDefs.java | 66 +++++++++++++------ 6 files changed, 97 insertions(+), 26 deletions(-) diff --git a/annotations/src/main/java/mindustry/annotations/Annotations.java b/annotations/src/main/java/mindustry/annotations/Annotations.java index df917b4137..6441cacf2f 100644 --- a/annotations/src/main/java/mindustry/annotations/Annotations.java +++ b/annotations/src/main/java/mindustry/annotations/Annotations.java @@ -3,7 +3,6 @@ package mindustry.annotations; import java.lang.annotation.*; public class Annotations{ - //region entity interfaces /** Indicates multiple inheritance on a component type. */ @@ -13,6 +12,12 @@ public class Annotations{ 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) diff --git a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java index 809882a8dd..17e4238e30 100644 --- a/annotations/src/main/java/mindustry/annotations/BaseProcessor.java +++ b/annotations/src/main/java/mindustry/annotations/BaseProcessor.java @@ -9,6 +9,7 @@ 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.*; @@ -55,6 +56,14 @@ public abstract class BaseProcessor extends AbstractProcessor{ .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); diff --git a/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java b/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java index 5877a2876b..f24c8c78af 100644 --- a/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java +++ b/annotations/src/main/java/mindustry/annotations/impl/EntityProcess.java @@ -15,10 +15,12 @@ import javax.lang.model.type.*; @SupportedAnnotationTypes({ "mindustry.annotations.Annotations.EntityDef", -"mindustry.annotations.Annotations.EntityInterface" +"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<>(); @@ -31,6 +33,7 @@ public class EntityProcess extends BaseProcessor{ //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<>(); @@ -42,11 +45,15 @@ public class EntityProcess extends BaseProcessor{ //create component interfaces for(Stype component : allComponents){ - TypeSpec.Builder inter = TypeSpec.interfaceBuilder(component.name() + "c").addModifiers(Modifier.PUBLIC).addAnnotation(EntityInterface.class); + 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, type.name() + "c")); + 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))){ @@ -59,7 +66,9 @@ public class EntityProcess extends BaseProcessor{ //add utility methods to interface for(Smethod method : component.methods()){ - inter.addMethod(MethodSpec.methodBuilder(method.name()).returns(method.ret().toString().equals("void") ? TypeName.VOID : method.retn()) + 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()); } @@ -102,6 +111,7 @@ public class EntityProcess extends BaseProcessor{ 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()){ @@ -143,12 +153,12 @@ public class EntityProcess extends BaseProcessor{ //get interface for each component for(Stype comp : components){ //implement the interface - Stype inter = interfaces.find(i -> i.name().equals(comp.name() + "c")); + 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; + if(method.name().length() <= 3) continue; String var = Strings.camelize(method.name().substring(3)); if(method.name().startsWith("get")){ @@ -164,6 +174,13 @@ public class EntityProcess extends BaseProcessor{ } } + String interfaceName(Stype comp){ + if(!comp.name().endsWith("c")){ + err("All components must have names that end with 'c'.", comp.e); + } + return comp.name().substring(0, comp.name().length() - 1) + "t"; + } + /** @return all components that a entity def has */ Array allComponents(Stype type){ if(!defComponents.containsKey(type)){ @@ -203,6 +220,10 @@ public class EntityProcess extends BaseProcessor{ result.addAll(getDependencies(type)); } + if(component.annotation(BaseComponent.class) == null){ + result.addAll(baseComponents); + } + componentDependencies.put(component, result.asArray()); } diff --git a/annotations/src/main/java/mindustry/annotations/util/Smethod.java b/annotations/src/main/java/mindustry/annotations/util/Smethod.java index 1db941c63a..8b840c2977 100644 --- a/annotations/src/main/java/mindustry/annotations/util/Smethod.java +++ b/annotations/src/main/java/mindustry/annotations/util/Smethod.java @@ -14,6 +14,10 @@ public class Smethod extends Selement{ super(executableElement); } + public Array typeVariables(){ + return Array.with(e.getTypeParameters()).as(TypeParameterElement.class); + } + public Array params(){ return Array.with(e.getParameters()).map(Svar::new); } diff --git a/annotations/src/main/java/mindustry/annotations/util/Stype.java b/annotations/src/main/java/mindustry/annotations/util/Stype.java index b13f13a65d..99b9b9f781 100644 --- a/annotations/src/main/java/mindustry/annotations/util/Stype.java +++ b/annotations/src/main/java/mindustry/annotations/util/Stype.java @@ -17,6 +17,10 @@ public class Stype extends Selement{ 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(); diff --git a/core/src/mindustry/entities/def/EntityDefs.java b/core/src/mindustry/entities/def/EntityDefs.java index 6f11e2c47a..8ed807f94a 100644 --- a/core/src/mindustry/entities/def/EntityDefs.java +++ b/core/src/mindustry/entities/def/EntityDefs.java @@ -1,22 +1,27 @@ package mindustry.entities.def; import arc.math.geom.*; +import arc.util.*; import mindustry.annotations.Annotations.*; +import mindustry.entities.bullet.*; import mindustry.entities.units.*; import mindustry.gen.*; import mindustry.net.*; -public class EntityDefs{ +class EntityDefs{ - @EntityDef({Unit.class, Connection.class}) + @EntityDef({Unitc.class, Connectionc.class}) class PlayerDef{} - @Depends({Health.class, Vel.class, Status.class}) - class Unit{ + @EntityDef({Bulletc.class, Velc.class}) + class BulletDef{} + + @Depends({Healthc.class, Velc.class, Statusc.class}) + class Unitc{ } - class Health{ + class Healthc{ float health, maxHealth; boolean dead; @@ -25,13 +30,18 @@ public class EntityDefs{ } } - class Pos{ + abstract class Posc implements Position{ float x, y; + + void set(float x, float y){ + this.x = x; + this.y = y; + } } - @Depends(Pos.class) - class Vel{ - //transient fields act as imports from any other clases; these are ignored by the generator + @Depends(Posc.class) + class Velc{ + //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(); @@ -43,7 +53,7 @@ public class EntityDefs{ } } - class Status{ + class Statusc{ final Statuses statuses = new Statuses(); void update(){ @@ -51,19 +61,37 @@ public class EntityDefs{ } } - class Connection{ + class Connectionc{ NetConnection connection; } - static void doSomethingWithAConnection(T value){ - value.setX(0); - value.setY(0); - value.getVel().set(100, 100f); - value.setDead(true); - value.getConnection().kick("you are dead"); + class Bulletc{ + BulletType bullet; + + void init(){ + bullet.init(); + } } - static void test(){ - doSomethingWithAConnection(new PlayerGen()); + @BaseComponent + class Entityc{ + int id; + + void init(){} + + T as(Class type){ + return (T)this; + } + } + + static void testing(){ + Entityt abullet = new BulletGen(); + Entityt aplayer = new PlayerGen(); + + if(abullet instanceof Post){ + Log.info("Pos: " + abullet.as(Post.class).getX()); + } + + Log.info(abullet.as(Post.class).dst(aplayer.as(Post.class))); } }