Partial 7.0 merge - API preview
@ -17,7 +17,8 @@
|
||||
android:usesCleartextTraffic="true"
|
||||
android:appCategory="game"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/ArcTheme" android:fullBackupContent="@xml/backup_rules">
|
||||
android:theme="@style/ArcTheme"
|
||||
android:fullBackupContent="@xml/backup_rules">
|
||||
<meta-data android:name="android.max_aspect" android:value="2.1"/>
|
||||
<activity
|
||||
android:name="mindustry.android.AndroidLauncher"
|
||||
|
@ -20,22 +20,7 @@ configurations{ natives }
|
||||
repositories{
|
||||
mavenCentral()
|
||||
maven{ url "https://maven.google.com" }
|
||||
jcenter() //remove later once google fixes the dependency
|
||||
}
|
||||
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
|
||||
implementation arcModule("backends:backend-android")
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
|
||||
|
||||
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-box2d-android:${getArcHash()}"
|
||||
|
||||
//android dependencies magically disappear during compilation, thanks gradle!
|
||||
def sdkFile = new File((String)findSdkDir(), "/platforms/android-29/android.jar")
|
||||
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
|
||||
jcenter() //remove later once google/JetBrains fixes the dependency
|
||||
}
|
||||
|
||||
task deploy(type: Copy){
|
||||
@ -47,8 +32,8 @@ task deploy(type: Copy){
|
||||
}
|
||||
|
||||
android{
|
||||
buildToolsVersion '29.0.3'
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion '30.0.2'
|
||||
compileSdkVersion 30
|
||||
sourceSets{
|
||||
main{
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
@ -59,9 +44,13 @@ android{
|
||||
assets.srcDirs = ['assets', 'src/main/assets', '../core/assets/']
|
||||
jniLibs.srcDirs = ['libs']
|
||||
}
|
||||
gp{
|
||||
java.srcDirs = ['srcgp']
|
||||
}
|
||||
|
||||
androidTest.setRoot('tests')
|
||||
}
|
||||
|
||||
packagingOptions{
|
||||
exclude 'META-INF/robovm/ios/robovm.xml'
|
||||
}
|
||||
@ -73,7 +62,7 @@ android{
|
||||
|
||||
applicationId "io.anuke.mindustry"
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 29
|
||||
targetSdkVersion 30
|
||||
|
||||
versionName versionNameResult
|
||||
versionCode = (System.getenv("TRAVIS_BUILD_ID") != null ? System.getenv("TRAVIS_BUILD_ID").toInteger() : vcode)
|
||||
@ -109,6 +98,14 @@ android{
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes{
|
||||
all{
|
||||
minifyEnabled = true
|
||||
shrinkResources = true
|
||||
proguardFiles("proguard-rules.pro")
|
||||
}
|
||||
}
|
||||
|
||||
if(project.hasProperty("RELEASE_STORE_FILE") || System.getenv("CI") == "true"){
|
||||
buildTypes{
|
||||
release{
|
||||
@ -116,10 +113,37 @@ android{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Specifies one flavor dimension.
|
||||
flavorDimensions "version"
|
||||
productFlavors{
|
||||
standard{
|
||||
|
||||
}
|
||||
gp{
|
||||
applicationIdSuffix ".gp"
|
||||
versionNameSuffix "-gp"
|
||||
}
|
||||
}
|
||||
}
|
||||
// called every time gradle gets executed, takes the native dependencies of
|
||||
// the natives configuration, and extracts them to the proper libs/ folders
|
||||
// so they get packed with the APK.
|
||||
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
|
||||
implementation arcModule("backends:backend-android")
|
||||
implementation 'com.jakewharton.android.repackaged:dalvik-dx:9.0.0_r3'
|
||||
|
||||
natives "com.github.Anuken.Arc:natives-android:${getArcHash()}"
|
||||
natives "com.github.Anuken.Arc:natives-freetype-android:${getArcHash()}"
|
||||
|
||||
gpImplementation "com.google.android.gms:play-services-games:21.0.0"
|
||||
gpImplementation "com.google.android.gms:play-services-auth:19.0.0"
|
||||
|
||||
//android dependencies magically disappear during compilation, thanks gradle!
|
||||
def sdkFile = new File((String)findSdkDir(), "/platforms/android-29/android.jar")
|
||||
if(sdkFile.exists()) compileOnly files(sdkFile.absolutePath)
|
||||
}
|
||||
|
||||
task copyAndroidNatives(){
|
||||
configurations.natives.files.each{ jar ->
|
||||
copy{
|
||||
@ -131,25 +155,7 @@ task copyAndroidNatives(){
|
||||
}
|
||||
|
||||
task run(type: Exec){
|
||||
def path
|
||||
def localProperties = project.file("../local.properties")
|
||||
if(localProperties.exists()){
|
||||
Properties properties = new Properties()
|
||||
localProperties.withInputStream{ instr ->
|
||||
properties.load(instr)
|
||||
}
|
||||
def sdkDir = properties.getProperty('sdk.dir')
|
||||
if(sdkDir){
|
||||
path = sdkDir
|
||||
}else{
|
||||
path = "$System.env.ANDROID_HOME"
|
||||
}
|
||||
}else{
|
||||
path = "$System.env.ANDROID_HOME"
|
||||
}
|
||||
|
||||
def adb = path + "/platform-tools/adb"
|
||||
commandLine "$adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
|
||||
commandLine "${findSdkDir()}/platform-tools/adb", 'shell', 'am', 'start', '-n', 'io.anuke.mindustry/mindustry.android.AndroidLauncher'
|
||||
}
|
||||
|
||||
if(!project.ext.hasSprites()){
|
||||
|
8
android/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
-dontobfuscate
|
||||
|
||||
#these are essential packages that should not be "optimized" in any way
|
||||
#the main purpose of d8 here is to shrink the absurdly-large google play games libraries
|
||||
-keep class mindustry.** { *; }
|
||||
-keep class arc.** { *; }
|
||||
-keep class net.jpountz.** { *; }
|
||||
-keep class rhino.** { *; }
|
@ -1,6 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="app_name">Mindustry</string>
|
||||
|
||||
</resources>
|
||||
|
@ -1,5 +1,4 @@
|
||||
<resources>
|
||||
|
||||
<style name="ArcTheme" parent="android:Theme">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:colorBackgroundCacheHint">@null</item>
|
||||
@ -8,5 +7,4 @@
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -15,9 +15,11 @@ import arc.func.*;
|
||||
import arc.scene.ui.layout.*;
|
||||
import arc.util.*;
|
||||
import dalvik.system.*;
|
||||
import io.anuke.mindustry.*;
|
||||
import mindustry.*;
|
||||
import mindustry.game.Saves.*;
|
||||
import mindustry.io.*;
|
||||
import mindustry.mod.*;
|
||||
import mindustry.net.*;
|
||||
import mindustry.ui.dialogs.*;
|
||||
|
||||
@ -33,6 +35,9 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
FileChooser chooser;
|
||||
Runnable permCallback;
|
||||
|
||||
Object gpService;
|
||||
Class<?> serviceClass;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState){
|
||||
UncaughtExceptionHandler handler = Thread.getDefaultUncaughtExceptionHandler();
|
||||
@ -50,7 +55,7 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
});
|
||||
|
||||
super.onCreate(savedInstanceState);
|
||||
if(doubleScaleTablets && isTablet(this.getContext())){
|
||||
if(doubleScaleTablets && isTablet(this)){
|
||||
Scl.setAddition(0.5f);
|
||||
}
|
||||
|
||||
@ -63,7 +68,9 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
|
||||
@Override
|
||||
public rhino.Context getScriptContext(){
|
||||
return AndroidRhinoContext.enter(getContext().getCacheDir());
|
||||
rhino.Context result = AndroidRhinoContext.enter(((Context)AndroidLauncher.this).getCacheDir());
|
||||
result.setClassShutter(Scripts::allowClass);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -71,8 +78,8 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassLoader loadJar(Fi jar, String mainClass) throws Exception{
|
||||
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, getClassLoader());
|
||||
public ClassLoader loadJar(Fi jar, ClassLoader parent) throws Exception{
|
||||
return new DexClassLoader(jar.file().getPath(), getFilesDir().getPath(), null, parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -165,9 +172,20 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
|
||||
try{
|
||||
//new external folder
|
||||
Fi data = Core.files.absolute(getContext().getExternalFilesDir(null).getAbsolutePath());
|
||||
Fi data = Core.files.absolute(((Context)this).getExternalFilesDir(null).getAbsolutePath());
|
||||
Core.settings.setDataDirectory(data);
|
||||
|
||||
//delete unused cache folder to free up space
|
||||
try{
|
||||
Fi cache = Core.settings.getDataDirectory().child("cache");
|
||||
if(cache.exists()){
|
||||
cache.deleteDirectory();
|
||||
}
|
||||
}catch(Throwable t){
|
||||
Log.err("Failed to delete cached folder", t);
|
||||
}
|
||||
|
||||
|
||||
//move to internal storage if there's no file indicating that it moved
|
||||
if(!Core.files.local("files_moved").exists()){
|
||||
Log.info("Moving files to external storage...");
|
||||
@ -209,6 +227,24 @@ public class AndroidLauncher extends AndroidApplication{
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume(){
|
||||
super.onResume();
|
||||
|
||||
//TODO enable once GPGS is set up on the GP console
|
||||
if(false && BuildConfig.FLAVOR.equals("gp")){
|
||||
try{
|
||||
if(gpService == null){
|
||||
serviceClass = Class.forName("mindustry.android.GPGameService");
|
||||
gpService = serviceClass.getConstructor().newInstance();
|
||||
}
|
||||
serviceClass.getMethod("onResume", Context.class).invoke(gpService, this);
|
||||
}catch(Exception e){
|
||||
Log.err("Failed to update Google Play Services", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkFiles(Intent intent){
|
||||
try{
|
||||
Uri uri = intent.getData();
|
||||
|
@ -175,7 +175,7 @@ public class AndroidRhinoContext{
|
||||
}catch(IOException e){
|
||||
e.printStackTrace();
|
||||
}
|
||||
android.content.Context context = ((AndroidApplication) Core.app).getContext();
|
||||
android.content.Context context = (android.content.Context)((AndroidApplication)Core.app);
|
||||
return new DexClassLoader(dexFile.getPath(), VERSION.SDK_INT >= 21 ? context.getCodeCacheDir().getPath() : context.getCacheDir().getAbsolutePath(), null, getParent()).loadClass(name);
|
||||
}
|
||||
|
||||
|
40
android/srcgp/mindustry/android/GPGameService.java
Normal file
@ -0,0 +1,40 @@
|
||||
package mindustry.android;
|
||||
|
||||
import android.content.*;
|
||||
import arc.util.*;
|
||||
import com.google.android.gms.auth.api.signin.*;
|
||||
import com.google.android.gms.games.*;
|
||||
import mindustry.service.*;
|
||||
|
||||
public class GPGameService extends GameService{
|
||||
private GoogleSignInAccount account;
|
||||
|
||||
public void onResume(Context context){
|
||||
Log.info("[GooglePlayService] Resuming.");
|
||||
|
||||
GoogleSignInAccount current = GoogleSignIn.getLastSignedInAccount(context);
|
||||
|
||||
GoogleSignInOptions options =
|
||||
new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_GAMES_SIGN_IN)
|
||||
.requestScopes(Games.SCOPE_GAMES_SNAPSHOTS)
|
||||
.build();
|
||||
|
||||
if(GoogleSignIn.hasPermissions(current, options.getScopeArray())){
|
||||
this.account = current;
|
||||
Log.info("Already signed in to Google Play Games.");
|
||||
}else{
|
||||
GoogleSignIn.getClient(context, options).silentSignIn().addOnCompleteListener(complete -> {
|
||||
if(!complete.isSuccessful()){
|
||||
if(complete.getException() != null){
|
||||
Log.err("Failed to sign in to Google Play Games.", complete.getException());
|
||||
}else{
|
||||
Log.warn("Failed to sign in to Google Play Games.");
|
||||
}
|
||||
}else{
|
||||
this.account = complete.getResult();
|
||||
Log.info("Signed in to Google Play Games.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -118,7 +118,7 @@ public class Annotations{
|
||||
/**
|
||||
* The region name to load. Variables can be used:
|
||||
* "@" -> block name
|
||||
* "$size" -> block size
|
||||
* "@size" -> block size
|
||||
* "#" "#1" "#2" -> index number, for arrays
|
||||
* */
|
||||
String value();
|
||||
@ -177,12 +177,12 @@ public class Annotations{
|
||||
//region remote
|
||||
|
||||
public enum PacketPriority{
|
||||
/** Does not get handled unless client is connected. */
|
||||
low,
|
||||
/** Gets put in a queue and processed if not connected. */
|
||||
normal,
|
||||
/** Gets handled immediately, regardless of connection status. */
|
||||
high,
|
||||
/** Does not get handled unless client is connected. */
|
||||
low
|
||||
}
|
||||
|
||||
/** A set of two booleans, one specifying server and one specifying client. */
|
||||
|
@ -2,15 +2,10 @@ package mindustry.annotations;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.Log;
|
||||
import arc.util.Log.*;
|
||||
import arc.util.*;
|
||||
import arc.util.Log.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.sun.source.util.*;
|
||||
import com.sun.tools.javac.model.*;
|
||||
import com.sun.tools.javac.processing.*;
|
||||
import com.sun.tools.javac.tree.*;
|
||||
import com.sun.tools.javac.util.*;
|
||||
import mindustry.annotations.util.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
@ -22,7 +17,6 @@ import javax.tools.Diagnostic.*;
|
||||
import javax.tools.*;
|
||||
import java.io.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
@SupportedSourceVersion(SourceVersion.RELEASE_8)
|
||||
@ -31,19 +25,16 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
public static final String packageName = "mindustry.gen";
|
||||
|
||||
public static Types typeu;
|
||||
public static JavacElements elementu;
|
||||
public static Elements elementu;
|
||||
public static Filer filer;
|
||||
public static Messager messager;
|
||||
public static Trees trees;
|
||||
public static TreeMaker maker;
|
||||
|
||||
protected int round;
|
||||
protected int rounds = 1;
|
||||
protected RoundEnvironment env;
|
||||
protected Fi rootDirectory;
|
||||
|
||||
protected Context context;
|
||||
|
||||
public static String getMethodName(Element element){
|
||||
return ((TypeElement)element.getEnclosingElement()).getQualifiedName().toString() + "." + element.getSimpleName();
|
||||
}
|
||||
@ -186,7 +177,7 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
Log.err("[CODEGEN ERROR] " + message + ": " + elem);
|
||||
}
|
||||
|
||||
public void err(String message, Selement elem){
|
||||
public static void err(String message, Selement elem){
|
||||
err(message, elem.e);
|
||||
}
|
||||
|
||||
@ -194,15 +185,11 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
public synchronized void init(ProcessingEnvironment env){
|
||||
super.init(env);
|
||||
|
||||
JavacProcessingEnvironment javacProcessingEnv = (JavacProcessingEnvironment)env;
|
||||
|
||||
trees = Trees.instance(env);
|
||||
typeu = env.getTypeUtils();
|
||||
elementu = javacProcessingEnv.getElementUtils();
|
||||
elementu = env.getElementUtils();
|
||||
filer = env.getFiler();
|
||||
messager = env.getMessager();
|
||||
context = ((JavacProcessingEnvironment)env).getContext();
|
||||
maker = TreeMaker.instance(javacProcessingEnv.getContext());
|
||||
|
||||
Log.level = LogLevel.info;
|
||||
|
||||
|
@ -132,6 +132,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
.build())).addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT).build());
|
||||
}
|
||||
|
||||
//generate interface getters and setters for all "standard" fields
|
||||
for(Svar field : component.fields().select(e -> !e.is(Modifier.STATIC) && !e.is(Modifier.PRIVATE) && !e.has(Import.class))){
|
||||
String cname = field.name();
|
||||
|
||||
@ -674,11 +675,28 @@ public class EntityProcess extends BaseProcessor{
|
||||
//build mapping class for sync IDs
|
||||
TypeSpec.Builder idBuilder = TypeSpec.classBuilder("EntityMapping").addModifiers(Modifier.PUBLIC)
|
||||
.addField(FieldSpec.builder(TypeName.get(Prov[].class), "idMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new Prov[256]").build())
|
||||
|
||||
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(ObjectMap.class),
|
||||
tname(String.class), tname(Prov.class)),
|
||||
"nameMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new ObjectMap<>()").build())
|
||||
|
||||
.addField(FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(IntMap.class), tname(String.class)),
|
||||
"customIdMap", Modifier.PUBLIC, Modifier.STATIC).initializer("new IntMap<>()").build())
|
||||
|
||||
.addMethod(MethodSpec.methodBuilder("register").addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.returns(TypeName.get(int.class))
|
||||
.addParameter(String.class, "name").addParameter(Prov.class, "constructor")
|
||||
.addStatement("int next = arc.util.Structs.indexOf(idMap, v -> v == null)")
|
||||
.addStatement("idMap[next] = constructor")
|
||||
.addStatement("nameMap.put(name, constructor)")
|
||||
.addStatement("customIdMap.put(next, name)")
|
||||
.addStatement("return next")
|
||||
.addJavadoc("Use this method for obtaining a classId for custom modded unit types. Only call this once for each type. Modded types should return this id in their overridden classId method.")
|
||||
.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());
|
||||
|
||||
@ -707,11 +725,6 @@ public class EntityProcess extends BaseProcessor{
|
||||
}else{
|
||||
//round 3: generate actual classes and implement interfaces
|
||||
|
||||
//write base classes
|
||||
for(TypeSpec.Builder b : baseClasses){
|
||||
write(b, imports.asArray());
|
||||
}
|
||||
|
||||
//implement each definition
|
||||
for(EntityDefinition def : definitions){
|
||||
|
||||
@ -736,6 +749,12 @@ public class EntityProcess extends BaseProcessor{
|
||||
|
||||
if(def.legacy) continue;
|
||||
|
||||
@Nullable TypeSpec.Builder superclass = null;
|
||||
|
||||
if(def.extend != null){
|
||||
superclass = baseClasses.find(b -> (packageName + "." + Reflect.get(b, "name")).equals(def.extend.toString()));
|
||||
}
|
||||
|
||||
//generate getter/setter for each method
|
||||
for(Smethod method : inter.methods()){
|
||||
String var = method.name();
|
||||
@ -743,14 +762,36 @@ public class EntityProcess extends BaseProcessor{
|
||||
//make sure it's a real variable AND that the component doesn't already implement it somewhere with custom logic
|
||||
if(field == null || methodNames.contains(method.simpleString())) continue;
|
||||
|
||||
MethodSpec result = null;
|
||||
|
||||
//getter
|
||||
if(!method.isVoid()){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("return " + var).build());
|
||||
result = MethodSpec.overriding(method.e).addStatement("return " + var).build();
|
||||
}
|
||||
|
||||
//setter
|
||||
if(method.isVoid() && !Seq.with(field.annotations).contains(f -> f.type.toString().equals("@mindustry.annotations.Annotations.ReadOnly"))){
|
||||
def.builder.addMethod(MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build());
|
||||
result = MethodSpec.overriding(method.e).addStatement("this." + var + " = " + var).build();
|
||||
}
|
||||
|
||||
//add getter/setter to parent class, if possible. when this happens, skip adding getters setters *here* because they are defined in the superclass.
|
||||
if(result != null && superclass != null){
|
||||
FieldSpec superField = Seq.with(superclass.fieldSpecs).find(f -> f.name.equals(var));
|
||||
|
||||
//found the right field, try to check for the method already existing now
|
||||
if(superField != null){
|
||||
MethodSpec fr = result;
|
||||
MethodSpec targetMethod = Seq.with(superclass.methodSpecs).find(m -> m.name.equals(var) && m.returnType.equals(fr.returnType));
|
||||
//if the method isn't added yet, add it. in any case, skip.
|
||||
if(targetMethod == null){
|
||||
superclass.addMethod(result);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if(result != null){
|
||||
def.builder.addMethod(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -758,9 +799,16 @@ public class EntityProcess extends BaseProcessor{
|
||||
write(def.builder, imports.asArray());
|
||||
}
|
||||
|
||||
//write base classes last
|
||||
for(TypeSpec.Builder b : baseClasses){
|
||||
write(b, imports.asArray());
|
||||
}
|
||||
|
||||
//TODO nulls were an awful idea
|
||||
//store nulls
|
||||
TypeSpec.Builder nullsBuilder = TypeSpec.classBuilder("Nulls").addModifiers(Modifier.PUBLIC).addModifiers(Modifier.FINAL);
|
||||
ObjectSet<String> nullList = ObjectSet.with("unit", "blockUnit");
|
||||
//TODO should be dynamic
|
||||
ObjectSet<String> nullList = ObjectSet.with("unit");
|
||||
|
||||
//create mock types of all components
|
||||
for(Stype interf : allInterfaces){
|
||||
@ -918,7 +966,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
}
|
||||
|
||||
String createName(Selement<?> elem){
|
||||
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);;
|
||||
Seq<Stype> comps = types(elem.annotation(EntityDef.class), EntityDef::value).map(this::interfaceToComp);
|
||||
comps.sortComparing(Selement::name);
|
||||
return comps.toString("", s -> s.name().replace("Comp", ""));
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ public class AssetsProcess extends BaseProcessor{
|
||||
|
||||
texIcons.each((key, val) -> {
|
||||
String[] split = val.split("\\|");
|
||||
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "");
|
||||
String name = Strings.kebabToCamel(split[1]).replace("Medium", "").replace("Icon", "").replace("Ui", "");
|
||||
if(SourceVersion.isKeyword(name) || name.equals("char")) name += "i";
|
||||
|
||||
ichtype.addField(FieldSpec.builder(char.class, name, Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).addJavadoc(String.format("\\u%04x", Integer.parseInt(key))).initializer("'" + ((char)Integer.parseInt(key)) + "'").build());
|
||||
|
@ -1,154 +0,0 @@
|
||||
package mindustry.annotations.impl;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.source.util.*;
|
||||
import com.sun.tools.javac.code.Scope;
|
||||
import com.sun.tools.javac.code.*;
|
||||
import com.sun.tools.javac.code.Symbol.*;
|
||||
import com.sun.tools.javac.code.Type.*;
|
||||
import com.sun.tools.javac.tree.*;
|
||||
import com.sun.tools.javac.tree.JCTree.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.*;
|
||||
import javax.lang.model.element.*;
|
||||
import javax.tools.Diagnostic.*;
|
||||
import java.lang.annotation.*;
|
||||
import java.util.*;
|
||||
|
||||
@SupportedAnnotationTypes({"java.lang.Override"})
|
||||
public class CallSuperProcess extends AbstractProcessor{
|
||||
private Trees trees;
|
||||
|
||||
@Override
|
||||
public void init(ProcessingEnvironment pe){
|
||||
super.init(pe);
|
||||
trees = Trees.instance(pe);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv){
|
||||
for(Element e : roundEnv.getElementsAnnotatedWith(Override.class)){
|
||||
if(e.getAnnotation(OverrideCallSuper.class) != null) return false;
|
||||
|
||||
CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner();
|
||||
codeScanner.methodName = e.getSimpleName().toString();
|
||||
|
||||
TreePath tp = trees.getPath(e.getEnclosingElement());
|
||||
codeScanner.scan(tp, trees);
|
||||
|
||||
if(codeScanner.callSuperUsed){
|
||||
List list = codeScanner.method.getBody().getStatements();
|
||||
|
||||
if(!doesCallSuper(list, codeScanner.methodName)){
|
||||
processingEnv.getMessager().printMessage(Kind.ERROR, "Overriding method '" + codeScanner.methodName + "' must explicitly call super method from its parent class.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean doesCallSuper(List list, String methodName){
|
||||
for(Object object : list){
|
||||
if(object instanceof JCTree.JCExpressionStatement){
|
||||
JCTree.JCExpressionStatement expr = (JCExpressionStatement)object;
|
||||
String exprString = expr.toString();
|
||||
if(exprString.startsWith("super." + methodName) && exprString.endsWith(");")) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SourceVersion getSupportedSourceVersion(){
|
||||
return SourceVersion.RELEASE_8;
|
||||
}
|
||||
|
||||
static class CodeAnalyzerTreeScanner extends TreePathScanner<Object, Trees>{
|
||||
String methodName;
|
||||
MethodTree method;
|
||||
boolean callSuperUsed;
|
||||
|
||||
@Override
|
||||
public Object visitClass(ClassTree classTree, Trees trees){
|
||||
Tree extendTree = classTree.getExtendsClause();
|
||||
|
||||
if(extendTree instanceof JCTypeApply){ //generic classes case
|
||||
JCTypeApply generic = (JCTypeApply)extendTree;
|
||||
extendTree = generic.clazz;
|
||||
}
|
||||
|
||||
if(extendTree instanceof JCIdent){
|
||||
JCIdent tree = (JCIdent)extendTree;
|
||||
|
||||
if(tree == null || tree.sym == null) return super.visitClass(classTree, trees);
|
||||
|
||||
com.sun.tools.javac.code.Scope members = tree.sym.members();
|
||||
|
||||
if(checkScope(members))
|
||||
return super.visitClass(classTree, trees);
|
||||
|
||||
if(checkSuperTypes((ClassType)tree.type))
|
||||
return super.visitClass(classTree, trees);
|
||||
|
||||
}
|
||||
callSuperUsed = false;
|
||||
|
||||
return super.visitClass(classTree, trees);
|
||||
}
|
||||
|
||||
public boolean checkSuperTypes(ClassType type){
|
||||
if(type.supertype_field != null && type.supertype_field.tsym != null){
|
||||
if(checkScope(type.supertype_field.tsym.members()))
|
||||
return true;
|
||||
else
|
||||
return checkSuperTypes((ClassType)type.supertype_field);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean checkScope(Scope members){
|
||||
Iterable<Symbol> it;
|
||||
try{
|
||||
it = (Iterable<Symbol>)members.getClass().getMethod("getElements").invoke(members);
|
||||
}catch(Throwable t){
|
||||
try{
|
||||
it = (Iterable<Symbol>)members.getClass().getMethod("getSymbols").invoke(members);
|
||||
}catch(Exception e){
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
for(Symbol s : it){
|
||||
|
||||
if(s instanceof MethodSymbol){
|
||||
MethodSymbol ms = (MethodSymbol)s;
|
||||
|
||||
if(ms.getSimpleName().toString().equals(methodName)){
|
||||
Annotation annotation = ms.getAnnotation(CallSuper.class);
|
||||
if(annotation != null){
|
||||
callSuperUsed = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object visitMethod(MethodTree methodTree, Trees trees){
|
||||
if(methodTree.getName().toString().equals(methodName))
|
||||
method = methodTree;
|
||||
|
||||
return super.visitMethod(methodTree, trees);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
@Override
|
||||
public void process(RoundEnvironment env) throws Exception{
|
||||
TypeSpec.Builder regionClass = TypeSpec.classBuilder("ContentRegions")
|
||||
.addAnnotation(AnnotationSpec.builder(SuppressWarnings.class).addMember("value", "\"deprecation\"").build())
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder("loadRegions")
|
||||
.addParameter(tname("mindustry.ctype.MappableContent"), "content")
|
||||
@ -34,7 +35,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
}
|
||||
|
||||
for(Entry<Stype, Seq<Svar>> entry : fieldMap){
|
||||
method.beginControlFlow("if(content instanceof $T)", entry.key.tname());
|
||||
method.beginControlFlow("if(content instanceof $L)", entry.key.fullName());
|
||||
|
||||
for(Svar field : entry.value){
|
||||
Load an = field.annotation(Load.class);
|
||||
@ -45,7 +46,7 @@ public class LoadRegionProcessor extends BaseProcessor{
|
||||
|
||||
//not an array
|
||||
if(dims == 0){
|
||||
method.addStatement("(($T)content).$L = $T.atlas.find($L$L)", entry.key.tname(), field.name(), Core.class, parse(an.value()), fallbackString);
|
||||
method.addStatement("(($L)content).$L = $T.atlas.find($L$L)", entry.key.fullName(), field.name(), Core.class, parse(an.value()), fallbackString);
|
||||
}else{
|
||||
//is an array, create length string
|
||||
int[] lengths = an.lengths();
|
||||
|
@ -0,0 +1,363 @@
|
||||
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.util.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import java.io.*;
|
||||
|
||||
import static mindustry.annotations.BaseProcessor.*;
|
||||
|
||||
/** Generates code for writing remote invoke packets on the client and server. */
|
||||
public class CallGenerator{
|
||||
|
||||
/** Generates all classes in this list. */
|
||||
public static void generate(ClassSerializer serializer, Seq<MethodEntry> methods) throws IOException{
|
||||
//create builder
|
||||
TypeSpec.Builder callBuilder = TypeSpec.classBuilder(RemoteProcess.callLocation).addModifiers(Modifier.PUBLIC);
|
||||
|
||||
MethodSpec.Builder register = MethodSpec.methodBuilder("registerPackets")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC);
|
||||
|
||||
//go through each method entry in this class
|
||||
for(MethodEntry ent : methods){
|
||||
//builder for the packet type
|
||||
TypeSpec.Builder packet = TypeSpec.classBuilder(ent.packetClassName)
|
||||
.addModifiers(Modifier.PUBLIC);
|
||||
|
||||
packet.superclass(tname("mindustry.net.Packet"));
|
||||
|
||||
//return the correct priority
|
||||
if(ent.priority != PacketPriority.normal){
|
||||
packet.addMethod(MethodSpec.methodBuilder("getPriority")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class).returns(int.class).addStatement("return $L", ent.priority.ordinal())
|
||||
.build());
|
||||
}
|
||||
|
||||
//implement read & write methods
|
||||
packet.addMethod(makeWriter(ent, serializer));
|
||||
packet.addMethod(makeReader(ent, serializer));
|
||||
|
||||
//generate handlers
|
||||
if(ent.where.isClient){
|
||||
packet.addMethod(writeHandleMethod(ent, false));
|
||||
}
|
||||
|
||||
if(ent.where.isServer){
|
||||
packet.addMethod(writeHandleMethod(ent, true));
|
||||
}
|
||||
|
||||
//register packet
|
||||
register.addStatement("mindustry.net.Net.registerPacket($L.$L::new)", packageName, ent.packetClassName);
|
||||
|
||||
//add fields to the type
|
||||
for(Svar param : ent.element.params()){
|
||||
packet.addField(param.tname(), param.name(), Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
//write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method
|
||||
if(ent.where.isClient || ent.target.isAll){
|
||||
writeCallMethod(callBuilder, ent, true, false);
|
||||
}
|
||||
|
||||
//write the 'send event to one player' variant, which is only applicable on the server
|
||||
if(ent.where.isServer && ent.target.isOne){
|
||||
writeCallMethod(callBuilder, ent, false, false);
|
||||
}
|
||||
|
||||
//write the forwarded method version
|
||||
if(ent.where.isServer && ent.forward){
|
||||
writeCallMethod(callBuilder, ent, true, true);
|
||||
}
|
||||
|
||||
//write the completed packet class
|
||||
JavaFile.builder(packageName, packet.build()).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
|
||||
callBuilder.addMethod(register.build());
|
||||
|
||||
//build and write resulting class
|
||||
TypeSpec spec = callBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
|
||||
private static MethodSpec makeWriter(MethodEntry ent, ClassSerializer serializer){
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder("write")
|
||||
.addParameter(Writes.class, "WRITE")
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
|
||||
Seq<Svar> params = ent.element.params();
|
||||
|
||||
for(int i = 0; i < params.size; i++){
|
||||
//first argument is skipped as it is always the player caller
|
||||
if(!ent.where.isServer && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
Svar var = params.get(i);
|
||||
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//name of parameter type
|
||||
String typeName = var.mirror().toString();
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads the player anyway
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
builder.beginControlFlow("if(mindustry.Vars.net.server())");
|
||||
}
|
||||
|
||||
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
|
||||
builder.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializer.writers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), true));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No method to write class type: '" + typeName + "'", var);
|
||||
}
|
||||
|
||||
//add statement for writing it
|
||||
builder.addStatement(ser + "(WRITE, " + varName + ")");
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
builder.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static MethodSpec makeReader(MethodEntry ent, ClassSerializer serializer){
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder("read")
|
||||
.addParameter(Reads.class, "READ")
|
||||
.addModifiers(Modifier.PUBLIC).addAnnotation(Override.class);
|
||||
Seq<Svar> params = ent.element.params();
|
||||
|
||||
//go through each parameter
|
||||
for(int i = 0; i < params.size; i++){
|
||||
Svar var = params.get(i);
|
||||
|
||||
//first argument is skipped as it is always the player caller
|
||||
if(!ent.where.isServer && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only read the player when the CLIENT is receiving data, since the client is the only one who cares about the player anyway
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
builder.beginControlFlow("if(mindustry.Vars.net.client())");
|
||||
}
|
||||
|
||||
//full type name of parameter
|
||||
String typeName = var.mirror().toString();
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//capitalized version of type name for reading primitives
|
||||
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
|
||||
|
||||
//write primitives automatically
|
||||
if(BaseProcessor.isPrimitive(typeName)){
|
||||
builder.addStatement("$L = READ.$L()", varName, pname);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializer.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(ent.element.e, var.mirror(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No read method to read class type '" + typeName + "' in method " + ent.targetMethod + "; " + serializer.readers, var);
|
||||
}
|
||||
|
||||
//add statement for reading it
|
||||
builder.addStatement("$L = $L(READ)", varName, ser);
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
builder.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
/** Creates a specific variant for a method entry. */
|
||||
private static void writeCallMethod(TypeSpec.Builder classBuilder, MethodEntry ent, boolean toAll, boolean forwarded){
|
||||
Smethod elem = ent.element;
|
||||
Seq<Svar> params = elem.params();
|
||||
|
||||
//create builder
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder(elem.name() + (forwarded ? "__forward" : "")) //add except suffix when forwarding
|
||||
.addModifiers(Modifier.STATIC)
|
||||
.returns(void.class);
|
||||
|
||||
//forwarded methods aren't intended for use, and are not public
|
||||
if(!forwarded){
|
||||
method.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
//validate client methods to make sure
|
||||
if(ent.where.isClient){
|
||||
if(params.isEmpty()){
|
||||
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!params.get(0).mirror().toString().contains("Player")){
|
||||
BaseProcessor.err("Client invoke methods should have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//if toAll is false, it's a 'send to one player' variant, so add the player as a parameter
|
||||
if(!toAll){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "playerConnection");
|
||||
}
|
||||
|
||||
//add sender to ignore
|
||||
if(forwarded){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "exceptConnection");
|
||||
}
|
||||
|
||||
//call local method if applicable, shouldn't happen when forwarding method as that already happens by default
|
||||
if(!forwarded && ent.local != Loc.none){
|
||||
//add in local checks
|
||||
if(ent.local != Loc.both){
|
||||
method.beginControlFlow("if(" + getCheckString(ent.local) + " || !mindustry.Vars.net.active())");
|
||||
}
|
||||
|
||||
//concatenate parameters
|
||||
int index = 0;
|
||||
StringBuilder results = new StringBuilder();
|
||||
for(Svar var : params){
|
||||
//special case: calling local-only methods uses the local player
|
||||
if(index == 0 && ent.where == Loc.client){
|
||||
results.append("mindustry.Vars.player");
|
||||
}else{
|
||||
results.append(var.name());
|
||||
}
|
||||
if(index != params.size - 1) results.append(", ");
|
||||
index++;
|
||||
}
|
||||
|
||||
//add the statement to call it
|
||||
method.addStatement("$N." + elem.name() + "(" + results + ")",
|
||||
((TypeElement)elem.up()).getQualifiedName().toString());
|
||||
|
||||
if(ent.local != Loc.both){
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
//start control flow to check if it's actually client/server so no netcode is called
|
||||
method.beginControlFlow("if(" + getCheckString(ent.where) + ")");
|
||||
|
||||
//add statement to create packet from pool
|
||||
method.addStatement("$1T packet = new $1T()", tname("mindustry.gen." + ent.packetClassName));
|
||||
|
||||
method.addTypeVariables(Seq.with(elem.e.getTypeParameters()).map(BaseProcessor::getTVN));
|
||||
|
||||
for(int i = 0; i < params.size; i++){
|
||||
//first argument is skipped as it is always the player caller
|
||||
if((!ent.where.isServer) && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
Svar var = params.get(i);
|
||||
|
||||
method.addParameter(var.tname(), var.name());
|
||||
|
||||
//name of parameter
|
||||
String varName = var.name();
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads it
|
||||
boolean writePlayerSkipCheck = ent.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
method.beginControlFlow("if(mindustry.Vars.net.server())");
|
||||
}
|
||||
|
||||
method.addStatement("packet.$L = $L", varName, varName);
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
String sendString;
|
||||
|
||||
if(forwarded){ //forward packet
|
||||
if(!ent.local.isClient){ //if the client doesn't get it called locally, forward it back after validation
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{
|
||||
sendString = "mindustry.Vars.net.sendExcept(exceptConnection, ";
|
||||
}
|
||||
}else if(toAll){ //send to all players / to server
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{ //send to specific client from server
|
||||
sendString = "playerConnection.send(";
|
||||
}
|
||||
|
||||
//send the actual packet
|
||||
method.addStatement(sendString + "packet, " + (!ent.unreliable) + ")");
|
||||
|
||||
|
||||
//end check for server/client
|
||||
method.endControlFlow();
|
||||
|
||||
//add method to class, finally
|
||||
classBuilder.addMethod(method.build());
|
||||
}
|
||||
|
||||
private static String getCheckString(Loc loc){
|
||||
return
|
||||
loc.isClient && loc.isServer ? "mindustry.Vars.net.server() || mindustry.Vars.net.client()" :
|
||||
loc.isClient ? "mindustry.Vars.net.client()" :
|
||||
loc.isServer ? "mindustry.Vars.net.server()" : "false";
|
||||
}
|
||||
|
||||
/** Generates handleServer / handleClient methods. */
|
||||
public static MethodSpec writeHandleMethod(MethodEntry ent, boolean isClient){
|
||||
|
||||
//create main method builder
|
||||
MethodSpec.Builder builder = MethodSpec.methodBuilder(isClient ? "handleClient" : "handleServer")
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addAnnotation(Override.class)
|
||||
.returns(void.class);
|
||||
|
||||
Smethod elem = ent.element;
|
||||
Seq<Svar> params = elem.params();
|
||||
|
||||
if(!isClient){
|
||||
//add player parameter
|
||||
builder.addParameter(ClassName.get("mindustry.net", "NetConnection"), "con");
|
||||
|
||||
//skip if player is invalid
|
||||
builder.beginControlFlow("if(con.player == null || con.kicked)");
|
||||
builder.addStatement("return");
|
||||
builder.endControlFlow();
|
||||
|
||||
//make sure to use the actual player who sent the packet
|
||||
builder.addStatement("mindustry.gen.Player player = con.player");
|
||||
}
|
||||
|
||||
//execute the relevant method before the forward
|
||||
//if it throws a ValidateException, the method won't be forwarded
|
||||
builder.addStatement("$N." + elem.name() + "(" + params.toString(", ", s -> s.name()) + ")", ((TypeElement)elem.up()).getQualifiedName().toString());
|
||||
|
||||
//call forwarded method, don't forward on the client reader
|
||||
if(ent.forward && ent.where.isServer && !isClient){
|
||||
//call forwarded method
|
||||
builder.addStatement("$L.$L.$L__forward(con, $L)", packageName, ent.className, elem.name(), params.toString(", ", s -> s.name()));
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/** Represents a class witha list method entries to include in it. */
|
||||
public class ClassEntry{
|
||||
/** All methods in this generated class. */
|
||||
public final ArrayList<MethodEntry> methods = new ArrayList<>();
|
||||
/** Simple class name. */
|
||||
public final String name;
|
||||
|
||||
public ClassEntry(String name){
|
||||
this.name = name;
|
||||
}
|
||||
}
|
@ -1,8 +1,7 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import mindustry.annotations.Annotations.*;
|
||||
|
||||
import javax.lang.model.element.ExecutableElement;
|
||||
import mindustry.annotations.util.*;
|
||||
|
||||
/** Class that repesents a remote method to be constructed and put into a class. */
|
||||
public class MethodEntry{
|
||||
@ -10,6 +9,8 @@ public class MethodEntry{
|
||||
public final String className;
|
||||
/** Fully qualified target method to call. */
|
||||
public final String targetMethod;
|
||||
/** Simple name of the generated packet class. */
|
||||
public final String packetClassName;
|
||||
/** Whether this method can be called on a client/server. */
|
||||
public final Loc where;
|
||||
/**
|
||||
@ -26,12 +27,13 @@ public class MethodEntry{
|
||||
/** Unique method ID. */
|
||||
public final int id;
|
||||
/** The element method associated with this entry. */
|
||||
public final ExecutableElement element;
|
||||
public final Smethod element;
|
||||
/** The assigned packet priority. Only used in clients. */
|
||||
public final PacketPriority priority;
|
||||
|
||||
public MethodEntry(String className, String targetMethod, Loc where, Variant target,
|
||||
Loc local, boolean unreliable, boolean forward, int id, ExecutableElement element, PacketPriority priority){
|
||||
public MethodEntry(String className, String targetMethod, String packetClassName, Loc where, Variant target,
|
||||
Loc local, boolean unreliable, boolean forward, int id, Smethod element, PacketPriority priority){
|
||||
this.packetClassName = packetClassName;
|
||||
this.className = className;
|
||||
this.forward = forward;
|
||||
this.targetMethod = targetMethod;
|
||||
|
@ -1,7 +1,7 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import arc.util.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.*;
|
||||
@ -9,7 +9,6 @@ import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.annotation.processing.*;
|
||||
import javax.lang.model.element.*;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/** The annotation processor for generating remote method call code. */
|
||||
@ -18,106 +17,58 @@ import java.util.*;
|
||||
"mindustry.annotations.Annotations.TypeIOHandler"
|
||||
})
|
||||
public class RemoteProcess extends BaseProcessor{
|
||||
/** Maximum size of each event packet. */
|
||||
public static final int maxPacketSize = 8192;
|
||||
/** Warning on top of each autogenerated file. */
|
||||
public static final String autogenWarning = "Autogenerated file. Do not modify!\n";
|
||||
|
||||
/** Name of class that handles reading and invoking packets on the server. */
|
||||
private static final String readServerName = "RemoteReadServer";
|
||||
/** Name of class that handles reading and invoking packets on the client. */
|
||||
private static final String readClientName = "RemoteReadClient";
|
||||
/** Simple class name of generated class name. */
|
||||
private static final String callLocation = "Call";
|
||||
|
||||
//class serializers
|
||||
private ClassSerializer serializer;
|
||||
//all elements with the Remote annotation
|
||||
private Seq<Smethod> elements;
|
||||
//map of all classes to generate by name
|
||||
private HashMap<String, ClassEntry> classMap;
|
||||
//list of all method entries
|
||||
private Seq<MethodEntry> methods;
|
||||
//list of all method entries
|
||||
private Seq<ClassEntry> classes;
|
||||
|
||||
{
|
||||
rounds = 2;
|
||||
}
|
||||
public static final String callLocation = "Call";
|
||||
|
||||
@Override
|
||||
public void process(RoundEnvironment roundEnv) throws Exception{
|
||||
//round 1: find all annotations, generate *writers*
|
||||
if(round == 1){
|
||||
//get serializers
|
||||
serializer = TypeIOResolver.resolve(this);
|
||||
//last method ID used
|
||||
int lastMethodID = 0;
|
||||
//find all elements with the Remote annotation
|
||||
elements = methods(Remote.class);
|
||||
//map of all classes to generate by name
|
||||
classMap = new HashMap<>();
|
||||
//list of all method entries
|
||||
methods = new Seq<>();
|
||||
//list of all method entries
|
||||
classes = new Seq<>();
|
||||
//get serializers
|
||||
//class serializers
|
||||
ClassSerializer serializer = TypeIOResolver.resolve(this);
|
||||
//last method ID used
|
||||
int lastMethodID = 0;
|
||||
//find all elements with the Remote annotation
|
||||
//all elements with the Remote annotation
|
||||
Seq<Smethod> elements = methods(Remote.class);
|
||||
//list of all method entries
|
||||
Seq<MethodEntry> methods = new Seq<>();
|
||||
|
||||
Seq<Smethod> orderedElements = elements.copy();
|
||||
orderedElements.sort((a, b) -> -a.toString().compareTo(b.toString()));
|
||||
Seq<Smethod> orderedElements = elements.copy();
|
||||
orderedElements.sortComparing(Selement::toString);
|
||||
|
||||
//create methods
|
||||
for(Smethod element : orderedElements){
|
||||
Remote annotation = element.annotation(Remote.class);
|
||||
//create methods
|
||||
for(Smethod element : orderedElements){
|
||||
Remote annotation = element.annotation(Remote.class);
|
||||
|
||||
//check for static
|
||||
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
|
||||
err("All @Remote methods must be public and static", element);
|
||||
}
|
||||
|
||||
//can't generate none methods
|
||||
if(annotation.targets() == Loc.none){
|
||||
err("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.e), annotation.targets(), annotation.variants(),
|
||||
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++, element.e, annotation.priority());
|
||||
|
||||
entry.methods.add(method);
|
||||
methods.add(method);
|
||||
//check for static
|
||||
if(!element.is(Modifier.STATIC) || !element.is(Modifier.PUBLIC)){
|
||||
err("All @Remote methods must be public and static", element);
|
||||
}
|
||||
|
||||
//create read/write generators
|
||||
RemoteWriteGenerator writegen = new RemoteWriteGenerator(serializer);
|
||||
//can't generate none methods
|
||||
if(annotation.targets() == Loc.none){
|
||||
err("A @Remote method's targets() cannot be equal to 'none'", element);
|
||||
}
|
||||
|
||||
//generate the methods to invoke (write)
|
||||
writegen.generateFor(classes, packageName);
|
||||
}else if(round == 2){ //round 2: generate all *readers*
|
||||
RemoteReadGenerator readgen = new RemoteReadGenerator(serializer);
|
||||
String packetName = Strings.capitalize(element.name()) + "CallPacket";
|
||||
int[] index = {1};
|
||||
|
||||
//generate server readers
|
||||
readgen.generateFor(methods.select(method -> method.where.isClient), readServerName, packageName, true);
|
||||
//generate client readers
|
||||
readgen.generateFor(methods.select(method -> method.where.isServer), readClientName, packageName, false);
|
||||
while(methods.contains(m -> m.packetClassName.equals(packetName + (index[0] == 1 ? "" : index[0])))){
|
||||
index[0] ++;
|
||||
}
|
||||
|
||||
//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", Arrays.hashCode(methods.map(m -> m.element).toArray())).build());
|
||||
//create and add entry
|
||||
MethodEntry method = new MethodEntry(
|
||||
callLocation, BaseProcessor.getMethodName(element.e), packetName + (index[0] == 1 ? "" : index[0]),
|
||||
annotation.targets(), annotation.variants(),
|
||||
annotation.called(), annotation.unreliable(), annotation.forward(), lastMethodID++,
|
||||
element, annotation.priority()
|
||||
);
|
||||
|
||||
//build and write resulting hash class
|
||||
TypeSpec spec = hashBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
methods.add(method);
|
||||
}
|
||||
|
||||
//generate the methods to invoke, as well as the packet classes
|
||||
CallGenerator.generate(serializer, methods);
|
||||
}
|
||||
}
|
||||
|
@ -1,129 +0,0 @@
|
||||
package mindustry.annotations.remote;
|
||||
|
||||
import arc.struct.*;
|
||||
import arc.util.io.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
/** Generates code for reading remote invoke packets on the client and server. */
|
||||
public class RemoteReadGenerator{
|
||||
private final ClassSerializer serializers;
|
||||
|
||||
/** Creates a read generator that uses the supplied serializer setup. */
|
||||
public RemoteReadGenerator(ClassSerializer serializers){
|
||||
this.serializers = serializers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a class for reading remote invoke packets.
|
||||
* @param entries List of methods to use.
|
||||
* @param className Simple target class name.
|
||||
* @param packageName Full target package name.
|
||||
* @param needsPlayer Whether this read method requires a reference to the player sender.
|
||||
*/
|
||||
public void generateFor(Seq<MethodEntry> entries, String className, String packageName, boolean needsPlayer) throws Exception{
|
||||
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC);
|
||||
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
|
||||
|
||||
//create main method builder
|
||||
MethodSpec.Builder readMethod = MethodSpec.methodBuilder("readPacket")
|
||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||
.addParameter(Reads.class, "read") //buffer to read form
|
||||
.addParameter(int.class, "id") //ID of method type to read
|
||||
.returns(void.class);
|
||||
|
||||
if(needsPlayer){
|
||||
//add player parameter
|
||||
readMethod.addParameter(ClassName.get(packageName, "Player"), "player");
|
||||
}
|
||||
|
||||
CodeBlock.Builder readBlock = CodeBlock.builder(); //start building block of code inside read method
|
||||
boolean started = false; //whether an if() statement has been written yet
|
||||
|
||||
for(MethodEntry entry : entries){
|
||||
//write if check for this entry ID
|
||||
if(!started){
|
||||
started = true;
|
||||
readBlock.beginControlFlow("if(id == " + entry.id + ")");
|
||||
}else{
|
||||
readBlock.nextControlFlow("else if(id == " + entry.id + ")");
|
||||
}
|
||||
|
||||
readBlock.beginControlFlow("try");
|
||||
|
||||
//concatenated list of variable names for method invocation
|
||||
StringBuilder varResult = new StringBuilder();
|
||||
|
||||
//go through each parameter
|
||||
for(int i = 0; i < entry.element.getParameters().size(); i++){
|
||||
VariableElement var = entry.element.getParameters().get(i);
|
||||
|
||||
if(!needsPlayer || i != 0){ //if client, skip first parameter since it's always of type player and doesn't need to be read
|
||||
//full type name of parameter
|
||||
String typeName = var.asType().toString();
|
||||
//name of parameter
|
||||
String varName = var.getSimpleName().toString();
|
||||
//captialized version of type name for reading primitives
|
||||
String pname = typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "";
|
||||
|
||||
//write primitives automatically
|
||||
if(BaseProcessor.isPrimitive(typeName)){
|
||||
readBlock.addStatement("$L $L = read.$L()", typeName, varName, pname);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializers.readers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(entry.element, var.asType(), false));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
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 + "(read)");
|
||||
}
|
||||
|
||||
//append variable name to string builder
|
||||
varResult.append(var.getSimpleName());
|
||||
if(i != entry.element.getParameters().size() - 1) varResult.append(", ");
|
||||
}else{
|
||||
varResult.append("player");
|
||||
if(i != entry.element.getParameters().size() - 1) varResult.append(", ");
|
||||
}
|
||||
}
|
||||
|
||||
//execute the relevant method before the forward
|
||||
//if it throws a ValidateException, the method won't be forwarded
|
||||
readBlock.addStatement("$N." + entry.element.getSimpleName() + "(" + varResult.toString() + ")", ((TypeElement)entry.element.getEnclosingElement()).getQualifiedName().toString());
|
||||
|
||||
//call forwarded method, don't forward on the client reader
|
||||
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() + ")");
|
||||
}
|
||||
|
||||
readBlock.nextControlFlow("catch (java.lang.Exception e)");
|
||||
readBlock.addStatement("throw new java.lang.RuntimeException(\"Failed to read remote method '" + entry.element.getSimpleName() + "'!\", e)");
|
||||
readBlock.endControlFlow();
|
||||
}
|
||||
|
||||
//end control flow if necessary
|
||||
if(started){
|
||||
readBlock.nextControlFlow("else");
|
||||
readBlock.addStatement("throw new $1N(\"Invalid read method ID: \" + id + \"\")", RuntimeException.class.getName()); //handle invalid method IDs
|
||||
readBlock.endControlFlow();
|
||||
}
|
||||
|
||||
//add block and method to class
|
||||
readMethod.addCode(readBlock.build());
|
||||
classBuilder.addMethod(readMethod.build());
|
||||
|
||||
//build and write resulting class
|
||||
TypeSpec spec = classBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
}
|
@ -1,228 +0,0 @@
|
||||
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.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
import java.io.*;
|
||||
|
||||
/** Generates code for writing remote invoke packets on the client and server. */
|
||||
public class RemoteWriteGenerator{
|
||||
private final ClassSerializer serializers;
|
||||
|
||||
/** Creates a write generator that uses the supplied serializer setup. */
|
||||
public RemoteWriteGenerator(ClassSerializer serializers){
|
||||
this.serializers = serializers;
|
||||
}
|
||||
|
||||
/** Generates all classes in this list. */
|
||||
public void generateFor(Seq<ClassEntry> entries, String packageName) throws IOException{
|
||||
|
||||
for(ClassEntry entry : entries){
|
||||
//create builder
|
||||
TypeSpec.Builder classBuilder = TypeSpec.classBuilder(entry.name).addModifiers(Modifier.PUBLIC);
|
||||
classBuilder.addJavadoc(RemoteProcess.autogenWarning);
|
||||
|
||||
//add temporary write buffer
|
||||
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){
|
||||
//write the 'send event to all players' variant: always happens for clients, but only happens if 'all' is enabled on the server method
|
||||
if(methodEntry.where.isClient || methodEntry.target.isAll){
|
||||
writeMethodVariant(classBuilder, methodEntry, true, false);
|
||||
}
|
||||
|
||||
//write the 'send event to one player' variant, which is only applicable on the server
|
||||
if(methodEntry.where.isServer && methodEntry.target.isOne){
|
||||
writeMethodVariant(classBuilder, methodEntry, false, false);
|
||||
}
|
||||
|
||||
//write the forwarded method version
|
||||
if(methodEntry.where.isServer && methodEntry.forward){
|
||||
writeMethodVariant(classBuilder, methodEntry, true, true);
|
||||
}
|
||||
}
|
||||
|
||||
//build and write resulting class
|
||||
TypeSpec spec = classBuilder.build();
|
||||
JavaFile.builder(packageName, spec).build().writeTo(BaseProcessor.filer);
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a specific variant for a method entry. */
|
||||
private void writeMethodVariant(TypeSpec.Builder classBuilder, MethodEntry methodEntry, boolean toAll, boolean forwarded){
|
||||
ExecutableElement elem = methodEntry.element;
|
||||
|
||||
//create builder
|
||||
MethodSpec.Builder method = MethodSpec.methodBuilder(elem.getSimpleName().toString() + (forwarded ? "__forward" : "")) //add except suffix when forwarding
|
||||
.addModifiers(Modifier.STATIC)
|
||||
.returns(void.class);
|
||||
|
||||
//forwarded methods aren't intended for use, and are not public
|
||||
if(!forwarded){
|
||||
method.addModifiers(Modifier.PUBLIC);
|
||||
}
|
||||
|
||||
//validate client methods to make sure
|
||||
if(methodEntry.where.isClient){
|
||||
if(elem.getParameters().isEmpty()){
|
||||
BaseProcessor.err("Client invoke methods must have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!elem.getParameters().get(0).asType().toString().contains("Player")){
|
||||
BaseProcessor.err("Client invoke methods should have a first parameter of type Player", elem);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//if toAll is false, it's a 'send to one player' variant, so add the player as a parameter
|
||||
if(!toAll){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "playerConnection");
|
||||
}
|
||||
|
||||
//add sender to ignore
|
||||
if(forwarded){
|
||||
method.addParameter(ClassName.bestGuess("mindustry.net.NetConnection"), "exceptConnection");
|
||||
}
|
||||
|
||||
//call local method if applicable, shouldn't happen when forwarding method as that already happens by default
|
||||
if(!forwarded && methodEntry.local != Loc.none){
|
||||
//add in local checks
|
||||
if(methodEntry.local != Loc.both){
|
||||
method.beginControlFlow("if(" + getCheckString(methodEntry.local) + " || !mindustry.Vars.net.active())");
|
||||
}
|
||||
|
||||
//concatenate parameters
|
||||
int index = 0;
|
||||
StringBuilder results = new StringBuilder();
|
||||
for(VariableElement var : elem.getParameters()){
|
||||
//special case: calling local-only methods uses the local player
|
||||
if(index == 0 && methodEntry.where == Loc.client){
|
||||
results.append("mindustry.Vars.player");
|
||||
}else{
|
||||
results.append(var.getSimpleName());
|
||||
}
|
||||
if(index != elem.getParameters().size() - 1) results.append(", ");
|
||||
index++;
|
||||
}
|
||||
|
||||
//add the statement to call it
|
||||
method.addStatement("$N." + elem.getSimpleName() + "(" + results.toString() + ")",
|
||||
((TypeElement)elem.getEnclosingElement()).getQualifiedName().toString());
|
||||
|
||||
if(methodEntry.local != Loc.both){
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
//start control flow to check if it's actually client/server so no netcode is called
|
||||
method.beginControlFlow("if(" + getCheckString(methodEntry.where) + ")");
|
||||
|
||||
//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 priority
|
||||
method.addStatement("packet.priority = (byte)" + methodEntry.priority.ordinal());
|
||||
//assign method ID
|
||||
method.addStatement("packet.type = (byte)" + methodEntry.id);
|
||||
//reset stream
|
||||
method.addStatement("OUT.reset()");
|
||||
|
||||
method.addTypeVariables(Seq.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
|
||||
if((!methodEntry.where.isServer/* || methodEntry.mode == Loc.both*/) && i == 0){
|
||||
continue;
|
||||
}
|
||||
|
||||
VariableElement var = elem.getParameters().get(i);
|
||||
|
||||
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();
|
||||
//name of parameter type
|
||||
String typeName = var.asType().toString();
|
||||
//captialized version of type name for writing primitives
|
||||
String capName = typeName.equals("byte") ? "" : Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1);
|
||||
//special case: method can be called from anywhere to anywhere
|
||||
//thus, only write the player when the SERVER is writing data, since the client is the only one who reads it
|
||||
boolean writePlayerSkipCheck = methodEntry.where == Loc.both && i == 0;
|
||||
|
||||
if(writePlayerSkipCheck){ //write begin check
|
||||
method.beginControlFlow("if(mindustry.Vars.net.server())");
|
||||
}
|
||||
|
||||
if(BaseProcessor.isPrimitive(typeName)){ //check if it's a primitive, and if so write it
|
||||
method.addStatement("WRITE.$L($L)", typeName.equals("boolean") ? "bool" : typeName.charAt(0) + "", varName);
|
||||
}else{
|
||||
//else, try and find a serializer
|
||||
String ser = serializers.writers.get(typeName.replace("mindustry.gen.", ""), SerializerResolver.locate(elem, var.asType(), true));
|
||||
|
||||
if(ser == null){ //make sure a serializer exists!
|
||||
BaseProcessor.err("No @WriteClass method to write class type: '" + typeName + "'", var);
|
||||
return;
|
||||
}
|
||||
|
||||
//add statement for writing it
|
||||
method.addStatement(ser + "(WRITE, " + varName + ")");
|
||||
}
|
||||
|
||||
if(writePlayerSkipCheck){ //write end check
|
||||
method.endControlFlow();
|
||||
}
|
||||
}
|
||||
|
||||
//assign packet bytes
|
||||
method.addStatement("packet.bytes = OUT.getBytes()");
|
||||
//assign packet length
|
||||
method.addStatement("packet.length = OUT.size()");
|
||||
|
||||
String sendString;
|
||||
|
||||
if(forwarded){ //forward packet
|
||||
if(!methodEntry.local.isClient){ //if the client doesn't get it called locally, forward it back after validation
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{
|
||||
sendString = "mindustry.Vars.net.sendExcept(exceptConnection, ";
|
||||
}
|
||||
}else if(toAll){ //send to all players / to server
|
||||
sendString = "mindustry.Vars.net.send(";
|
||||
}else{ //send to specific client from server
|
||||
sendString = "playerConnection.send(";
|
||||
}
|
||||
|
||||
//send the actual packet
|
||||
method.addStatement(sendString + "packet, " +
|
||||
(methodEntry.unreliable ? "mindustry.net.Net.SendMode.udp" : "mindustry.net.Net.SendMode.tcp") + ")");
|
||||
|
||||
|
||||
//end check for server/client
|
||||
method.endControlFlow();
|
||||
|
||||
//add method to class, finally
|
||||
classBuilder.addMethod(method.build());
|
||||
}
|
||||
|
||||
private String getCheckString(Loc loc){
|
||||
return loc.isClient && loc.isServer ? "mindustry.Vars.net.server() || mindustry.Vars.net.client()" :
|
||||
loc.isClient ? "mindustry.Vars.net.client()" :
|
||||
loc.isServer ? "mindustry.Vars.net.server()" : "false";
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package mindustry.annotations.util;
|
||||
|
||||
import com.sun.source.tree.*;
|
||||
import com.sun.tools.javac.tree.JCTree.*;
|
||||
import mindustry.annotations.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
@ -16,10 +15,6 @@ public class Svar extends Selement<VariableElement>{
|
||||
return up().asType().toString() + "#" + super.toString().replace("mindustry.gen.", "");
|
||||
}
|
||||
|
||||
public JCVariableDecl jtree(){
|
||||
return (JCVariableDecl)BaseProcessor.elementu.getTree(e);
|
||||
}
|
||||
|
||||
public Stype enclosingType(){
|
||||
return new Stype((TypeElement)up());
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ mindustry.entities.comp.PosTeamDef=28
|
||||
mindustry.entities.comp.PuddleComp=13
|
||||
mindustry.type.Weather.WeatherStateComp=14
|
||||
mindustry.world.blocks.campaign.LaunchPad.LaunchPayloadComp=15
|
||||
mindustry.world.blocks.campaign.PayloadLaunchPad.LargeLaunchPayloadComp=34
|
||||
mindustry.world.blocks.defense.ForceProjector.ForceDrawComp=22
|
||||
mono=16
|
||||
nova=17
|
||||
|
@ -0,0 +1 @@
|
||||
{fields:[{name:lifetime,type:float},{name:payload,type:mindustry.world.blocks.payloads.Payload},{name:team,type:mindustry.game.Team},{name:time,type:float},{name:x,type:float},{name:y,type:float}]}
|
59
build.gradle
@ -29,18 +29,18 @@ plugins{
|
||||
}
|
||||
|
||||
allprojects{
|
||||
apply plugin: 'maven'
|
||||
apply plugin: 'maven-publish'
|
||||
|
||||
version = 'release'
|
||||
group = 'com.github.Anuken'
|
||||
|
||||
ext{
|
||||
versionNumber = '6'
|
||||
if(!project.hasProperty("versionModifier")) versionModifier = 'release'
|
||||
versionNumber = '7'
|
||||
if(!project.hasProperty("versionModifier")) versionModifier = 'pre-alpha'
|
||||
if(!project.hasProperty("versionType")) versionType = 'official'
|
||||
appName = 'Mindustry'
|
||||
steamworksVersion = '891ed912791e01fe9ee6237a6497e5212b85c256'
|
||||
rhinoVersion = '378626d8abc552bba57864358358045d2f2dbe9b'
|
||||
steamworksVersion = '0b86023401880bb5e586bc404bedbaae9b1f1c94'
|
||||
rhinoVersion = '099aed6c82f8094b3ba39a273b8d2ba7bdcc6443'
|
||||
|
||||
loadVersionProps = {
|
||||
return new Properties().with{p -> p.load(file('../core/assets/version.properties').newReader()); return p }
|
||||
@ -88,13 +88,11 @@ allprojects{
|
||||
}
|
||||
|
||||
hasSprites = {
|
||||
return new File(rootDir, "core/assets/sprites/sprites.atlas").exists()
|
||||
return new File(rootDir, "core/assets/sprites/sprites.aatls").exists()
|
||||
}
|
||||
|
||||
getModifierString = {
|
||||
if(versionModifier != "release"){
|
||||
return "[${versionModifier.toUpperCase()}]"
|
||||
}
|
||||
if(versionModifier != "release") return "[${versionModifier.toUpperCase()}]"
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -112,8 +110,7 @@ allprojects{
|
||||
def v = System.getenv("ANDROID_HOME")
|
||||
if(v != null) return v
|
||||
//rootDir is null here, amazing. brilliant.
|
||||
def file = new File("local.properties")
|
||||
if(!file.exists()) file = new File("../local.properties")
|
||||
def file = new File(rootDir, "local.properties")
|
||||
def props = new Properties().with{p -> p.load(file.newReader()); return p }
|
||||
return props.get("sdk.dir")
|
||||
}
|
||||
@ -200,10 +197,20 @@ allprojects{
|
||||
|
||||
tasks.withType(JavaCompile){
|
||||
targetCompatibility = 8
|
||||
sourceCompatibility = 14
|
||||
//TODO fix dynamically, this is a hack
|
||||
if(System.getProperty("user.name") == "anuke"){
|
||||
sourceCompatibility = JavaVersion.VERSION_15
|
||||
}else{
|
||||
sourceCompatibility = JavaVersion.VERSION_14
|
||||
}
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs += ["-Xlint:deprecation"]
|
||||
dependsOn clearCache
|
||||
|
||||
options.forkOptions.jvmArgs.addAll([
|
||||
'--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED',
|
||||
'--add-opens=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED'
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@ -211,15 +218,14 @@ configure(project(":annotations")){
|
||||
tasks.withType(JavaCompile){
|
||||
targetCompatibility = 8
|
||||
sourceCompatibility = 8
|
||||
options.fork = true
|
||||
}
|
||||
}
|
||||
|
||||
//compile with java 8 compatibility for everything except the annotation project
|
||||
configure(subprojects - project(":annotations")){
|
||||
tasks.withType(JavaCompile){
|
||||
if(JavaVersion.current() != JavaVersion.VERSION_1_8){
|
||||
options.compilerArgs.addAll(['--release', '8', '--enable-preview'])
|
||||
}
|
||||
options.compilerArgs.addAll(['--release', '8', '--enable-preview'])
|
||||
|
||||
doFirst{
|
||||
options.compilerArgs = options.compilerArgs.findAll{it != '--enable-preview' }
|
||||
@ -242,9 +248,9 @@ project(":desktop"){
|
||||
|
||||
dependencies{
|
||||
implementation project(":core")
|
||||
implementation arcModule("extensions:discord")
|
||||
implementation arcModule("natives:natives-desktop")
|
||||
implementation arcModule("natives:natives-freetype-desktop")
|
||||
implementation 'com.github.MinnDevelopment:java-discord-rpc:v2.0.1'
|
||||
|
||||
if(debugged()) implementation project(":debug")
|
||||
|
||||
@ -360,14 +366,16 @@ project(":core"){
|
||||
dependencies{
|
||||
compileJava.dependsOn(preGen)
|
||||
|
||||
api "org.lz4:lz4-java:1.4.1"
|
||||
api "org.lz4:lz4-java:1.7.1"
|
||||
api arcModule("arc-core")
|
||||
api arcModule("extensions:flabel")
|
||||
api arcModule("extensions:freetype")
|
||||
api arcModule("extensions:g3d")
|
||||
api arcModule("extensions:fx")
|
||||
api arcModule("extensions:arcnet")
|
||||
api "com.github.Anuken:rhino:$rhinoVersion"
|
||||
if(localArc() && debugged()) api arcModule("extensions:recorder")
|
||||
if(localArc()) api arcModule(":extensions:packer")
|
||||
|
||||
annotationProcessor 'com.github.Anuken:jabel:34e4c172e65b3928cd9eabe1993654ea79c409cd'
|
||||
compileOnly project(":annotations")
|
||||
@ -425,7 +433,7 @@ project(":tests"){
|
||||
test{
|
||||
useJUnitPlatform()
|
||||
workingDir = new File("../core/assets")
|
||||
testLogging {
|
||||
testLogging{
|
||||
exceptionFormat = 'full'
|
||||
showStandardStreams = true
|
||||
}
|
||||
@ -453,6 +461,21 @@ project(":annotations"){
|
||||
}
|
||||
}
|
||||
|
||||
configure([":core", ":desktop", ":server", ":tools"].collect{project(it)}){
|
||||
java{
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
publishing{
|
||||
publications{
|
||||
maven(MavenPublication){
|
||||
from components.java
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task deployAll{
|
||||
task cleanDeployOutput{
|
||||
doFirst{
|
||||
|
Before Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 186 B |
BIN
core/assets-raw/sprites/blocks/campaign/payload-launch-pad.png
Normal file
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 119 B |
After Width: | Height: | Size: 149 B |
After Width: | Height: | Size: 121 B |
After Width: | Height: | Size: 121 B |
After Width: | Height: | Size: 121 B |
After Width: | Height: | Size: 156 B |
After Width: | Height: | Size: 139 B |
After Width: | Height: | Size: 115 B |
After Width: | Height: | Size: 142 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 322 B |
After Width: | Height: | Size: 165 B |
After Width: | Height: | Size: 322 B |
BIN
core/assets-raw/sprites/blocks/distribution/ducts/duct-top-0.png
Normal file
After Width: | Height: | Size: 329 B |
BIN
core/assets-raw/sprites/blocks/distribution/ducts/duct-top-1.png
Normal file
After Width: | Height: | Size: 364 B |
BIN
core/assets-raw/sprites/blocks/distribution/ducts/duct-top-2.png
Normal file
After Width: | Height: | Size: 442 B |
BIN
core/assets-raw/sprites/blocks/distribution/ducts/duct-top-3.png
Normal file
After Width: | Height: | Size: 560 B |
BIN
core/assets-raw/sprites/blocks/distribution/ducts/duct-top-4.png
Normal file
After Width: | Height: | Size: 413 B |
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 478 B |
Before Width: | Height: | Size: 236 B After Width: | Height: | Size: 364 B |
Before Width: | Height: | Size: 219 B After Width: | Height: | Size: 321 B |
Before Width: | Height: | Size: 243 B After Width: | Height: | Size: 380 B |
Before Width: | Height: | Size: 631 B After Width: | Height: | Size: 1001 B |
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 393 B |
Before Width: | Height: | Size: 137 B |
BIN
core/assets-raw/sprites/blocks/environment/env-error.png
Normal file
After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 232 B After Width: | Height: | Size: 254 B |
Before Width: | Height: | Size: 221 B After Width: | Height: | Size: 343 B |
BIN
core/assets-raw/sprites/blocks/fire/fire0.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire1.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire10.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire11.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire12.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire13.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire14.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire15.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire16.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire17.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire18.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire19.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire2.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire20.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire21.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire22.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire23.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire24.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire25.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire26.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire27.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire28.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire29.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire3.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire30.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire31.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire32.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire33.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire34.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire35.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire36.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire37.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire38.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire39.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire4.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire5.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire6.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire7.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire8.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
core/assets-raw/sprites/blocks/fire/fire9.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 162 B After Width: | Height: | Size: 156 B |
Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 248 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 279 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |