diff --git a/.gitignore b/.gitignore index 59080869ff..12ca21bf90 100644 --- a/.gitignore +++ b/.gitignore @@ -21,7 +21,7 @@ /android/assets/mindustry-saves/ /core/assets/gifexport/ /core/assets/version.properties -/core/assets/locales.json +/core/assets/locales /ios/src/io/anuke/mindustry/gen/ *.gif diff --git a/annotations/src/io/anuke/annotations/Annotations.java b/annotations/src/io/anuke/annotations/Annotations.java index eeef7c2599..4f8381d177 100644 --- a/annotations/src/io/anuke/annotations/Annotations.java +++ b/annotations/src/io/anuke/annotations/Annotations.java @@ -11,6 +11,13 @@ import java.lang.annotation.Target; */ public class Annotations{ + /** Marks a class as serializable.*/ + @Target(ElementType.TYPE) + @Retention(RetentionPolicy.CLASS) + public @interface Serialize{ + + } + public enum PacketPriority{ /** Gets put in a queue and processed if not connected. */ normal, diff --git a/annotations/src/io/anuke/annotations/SerializeAnnotationProcessor.java b/annotations/src/io/anuke/annotations/SerializeAnnotationProcessor.java new file mode 100644 index 0000000000..58aa2abd5b --- /dev/null +++ b/annotations/src/io/anuke/annotations/SerializeAnnotationProcessor.java @@ -0,0 +1,110 @@ +package io.anuke.annotations; + +import com.squareup.javapoet.*; +import io.anuke.annotations.Annotations.Serialize; + +import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.element.VariableElement; +import javax.lang.model.util.ElementFilter; +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; +import java.util.List; +import java.util.Set; + +@SupportedSourceVersion(SourceVersion.RELEASE_8) +@SupportedAnnotationTypes({ +"io.anuke.annotations.Annotations.Serialize" +}) +public class SerializeAnnotationProcessor extends AbstractProcessor{ + /**Target class name.*/ + private static final String className = "Serialization"; + /** Name of the base package to put all the generated classes. */ + private static final String packageName = "io.anuke.mindustry.gen"; + + private int round; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv){ + super.init(processingEnv); + //put all relevant utils into utils class + Utils.typeUtils = processingEnv.getTypeUtils(); + Utils.elementUtils = processingEnv.getElementUtils(); + Utils.filer = processingEnv.getFiler(); + Utils.messager = processingEnv.getMessager(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv){ + if(round++ != 0) return false; //only process 1 round + + try{ + Set elements = ElementFilter.typesIn(roundEnv.getElementsAnnotatedWith(Serialize.class)); + + TypeSpec.Builder classBuilder = TypeSpec.classBuilder(className).addModifiers(Modifier.PUBLIC); + MethodSpec.Builder method = MethodSpec.methodBuilder("init").addModifiers(Modifier.PUBLIC, Modifier.STATIC); + + for(TypeElement elem : elements){ + TypeName type = TypeName.get(elem.asType()); + + TypeSpec.Builder serializer = TypeSpec.anonymousClassBuilder("") + .addSuperinterface(ParameterizedTypeName.get( + ClassName.get(Class.forName("io.anuke.ucore.io.TypeSerializer")), type)); + + MethodSpec.Builder writeMethod = MethodSpec.methodBuilder("write") + .returns(void.class) + .addParameter(DataOutput.class, "stream") + .addParameter(type, "object") + .addAnnotation(Override.class) + .addException(IOException.class) + .addModifiers(Modifier.PUBLIC); + + MethodSpec.Builder readMethod = MethodSpec.methodBuilder("read") + .returns(type) + .addParameter(DataInput.class, "stream") + .addAnnotation(Override.class) + .addException(IOException.class) + .addModifiers(Modifier.PUBLIC); + + readMethod.addStatement("$L object = new $L()", type, type); + + List fields = ElementFilter.fieldsIn(Utils.elementUtils.getAllMembers(elem)); + for(VariableElement field : fields){ + if(field.getModifiers().contains(Modifier.STATIC) || field.getModifiers().contains(Modifier.TRANSIENT) || field.getModifiers().contains(Modifier.PRIVATE)) continue; + + String name = field.getSimpleName().toString(); + String typeName = Utils.typeUtils.erasure(field.asType()).toString().replace('$', '.'); + String capName = Character.toUpperCase(typeName.charAt(0)) + typeName.substring(1); + + if(field.asType().getKind().isPrimitive()){ + writeMethod.addStatement("stream.write" + capName + "(object." + name + ")"); + readMethod.addStatement("object." + name + "= stream.read" + capName + "()"); + }else{ + writeMethod.addStatement("io.anuke.ucore.core.Settings.getSerializer(" + typeName+ ".class).write(stream, object." + name + ")"); + readMethod.addStatement("object." + name + " = (" +typeName+")io.anuke.ucore.core.Settings.getSerializer(" + typeName+ ".class).read(stream)"); + } + } + + readMethod.addStatement("return object"); + + serializer.addMethod(writeMethod.build()); + serializer.addMethod(readMethod.build()); + + method.addStatement("io.anuke.ucore.core.Settings.setSerializer($N, $L)", Utils.elementUtils.getBinaryName(elem).toString().replace('$', '.') + ".class", serializer.build()); + } + + classBuilder.addMethod(method.build()); + + //write result + JavaFile.builder(packageName, classBuilder.build()).build().writeTo(Utils.filer); + + return true; + }catch(Exception e){ + e.printStackTrace(); + throw new RuntimeException(e); + } + } +} diff --git a/build.gradle b/build.gradle index 74603609e1..709973bcb9 100644 --- a/build.gradle +++ b/build.gradle @@ -27,7 +27,7 @@ allprojects { appName = 'Mindustry' gdxVersion = '1.9.8' roboVMVersion = '2.3.0' - uCoreVersion = '7aa05daa277ffb67cda3d2d047c37b2f441e4e4e' + uCoreVersion = '7fafee20b6bf5615e009d2be20d1c1331d1e66c1' getVersionString = { String buildVersion = getBuildVersion() @@ -44,14 +44,13 @@ allprojects { } generateLocales = { - def output = '["en",' + def output = 'en\n' def bundles = new File(project(':core').projectDir, 'assets/bundles/') bundles.listFiles().each { other -> if(other.name == "bundle.properties") return; - output += '"' + other.name.substring("bundle".length() + 1, other.name.lastIndexOf('.')) + '",' + output += other.name.substring("bundle".length() + 1, other.name.lastIndexOf('.')) + "\n" } - output = (output.substring(0, output.size() - 1) + "]") - new File(project(':core').projectDir, 'assets/locales.json').text = output; + new File(project(':core').projectDir, 'assets/locales').text = output; } writeVersion = { @@ -118,9 +117,6 @@ project(":html") { compile "com.badlogicgames.gdx:gdx-controllers-gwt:$gdxVersion:sources" } - compileJava.options.compilerArgs = [ - "-processor", "io.anuke.annotations.RemoteMethodAnnotationProcessor" - ] } project(":ios") { @@ -185,7 +181,7 @@ project(":core") { } compileJava.options.compilerArgs = [ - "-processor", "io.anuke.annotations.RemoteMethodAnnotationProcessor" + "-processor", "io.anuke.annotations.RemoteMethodAnnotationProcessor,io.anuke.annotations.SerializeAnnotationProcessor" ] } diff --git a/core/src/io/anuke/mindustry/Vars.java b/core/src/io/anuke/mindustry/Vars.java index fccf8d9735..ba1605bb6f 100644 --- a/core/src/io/anuke/mindustry/Vars.java +++ b/core/src/io/anuke/mindustry/Vars.java @@ -4,7 +4,6 @@ import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.utils.Json; import io.anuke.mindustry.core.*; import io.anuke.mindustry.entities.Player; import io.anuke.mindustry.entities.TileEntity; @@ -16,6 +15,7 @@ import io.anuke.mindustry.entities.traits.SyncTrait; import io.anuke.mindustry.entities.units.BaseUnit; import io.anuke.mindustry.game.Team; import io.anuke.mindustry.game.Version; +import io.anuke.mindustry.gen.Serialization; import io.anuke.mindustry.net.Net; import io.anuke.mindustry.world.blocks.defense.ForceProjector.ShieldEntity; import io.anuke.ucore.entities.Entities; @@ -138,9 +138,10 @@ public class Vars{ public static final Translator[] tmptr = new Translator[]{new Translator(), new Translator(), new Translator(), new Translator()}; public static void init(){ + Serialization.init(); //load locales - String[] stra = new Json().fromJson(String[].class, Gdx.files.internal("locales.json")); + String[] stra = Gdx.files.internal("locales").readString().split("\n"); locales = new Locale[stra.length]; for(int i = 0; i < locales.length; i++){ String code = stra[i]; diff --git a/core/src/io/anuke/mindustry/core/Control.java b/core/src/io/anuke/mindustry/core/Control.java index a9f97f88e4..c71aa6eacf 100644 --- a/core/src/io/anuke/mindustry/core/Control.java +++ b/core/src/io/anuke/mindustry/core/Control.java @@ -51,6 +51,7 @@ public class Control extends Module{ private Throwable error; public Control(){ + saves = new Saves(); db = new ContentDatabase(); diff --git a/core/src/io/anuke/mindustry/game/ContentDatabase.java b/core/src/io/anuke/mindustry/game/ContentDatabase.java index d296fb875c..b35a160640 100644 --- a/core/src/io/anuke/mindustry/game/ContentDatabase.java +++ b/core/src/io/anuke/mindustry/game/ContentDatabase.java @@ -68,7 +68,7 @@ public class ContentDatabase{ } public void load(){ - ObjectMap> result = Settings.getJson("content-database", ObjectMap.class); + ObjectMap> result = Settings.getBinary("content-database", ObjectMap.class, () -> new ObjectMap<>()); for(Entry> entry : result.entries()){ ObjectSet set = new ObjectSet<>(); @@ -87,7 +87,7 @@ public class ContentDatabase{ write.put(entry.key, entry.value.iterator().toArray()); } - Settings.putJson("content-database", write); + Settings.putBinary("content-database", write); Settings.save(); dirty = false; } diff --git a/core/src/io/anuke/mindustry/game/Saves.java b/core/src/io/anuke/mindustry/game/Saves.java index ec2475c7ba..dfe6c11fee 100644 --- a/core/src/io/anuke/mindustry/game/Saves.java +++ b/core/src/io/anuke/mindustry/game/Saves.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.game; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.utils.Array; +import com.badlogic.gdx.utils.IntArray; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.TimeUtils; import io.anuke.mindustry.core.GameState.State; @@ -44,9 +45,10 @@ public class Saves{ public void load(){ saves.clear(); - int[] slots = Settings.getJson("save-slots", int[].class); + IntArray slots = Settings.getBinary("save-slots", IntArray.class, IntArray::new); - for(int index : slots){ + for(int i = 0; i < slots.size; i ++){ + int index = slots.get(i); if(SaveIO.isSaveValid(index)){ SaveSlot slot = new SaveSlot(index); saves.add(slot); @@ -138,11 +140,10 @@ public class Saves{ } private void saveSlots(){ - int[] result = new int[saves.size]; - for(int i = 0; i < result.length; i++){ - result[i] = saves.get(i).index; - } - Settings.putJson("save-slots", result); + IntArray result = new IntArray(saves.size); + for(int i = 0; i < saves.size; i++) result.add(saves.get(i).index); + + Settings.putBinary("save-slots", result); Settings.save(); } diff --git a/core/src/io/anuke/mindustry/maps/Maps.java b/core/src/io/anuke/mindustry/maps/Maps.java index 5b0ce56937..adff44c8a6 100644 --- a/core/src/io/anuke/mindustry/maps/Maps.java +++ b/core/src/io/anuke/mindustry/maps/Maps.java @@ -90,7 +90,7 @@ public class Maps implements Disposable{ Settings.putString("map-data-" + name, new String(Base64Coder.encode(stream.toByteArray()))); if(!customMapNames.contains(name, false)){ customMapNames.add(name); - Settings.putJson("custom-maps", customMapNames); + Settings.putBinary("custom-maps", customMapNames); } Settings.save(); } @@ -130,7 +130,7 @@ public class Maps implements Disposable{ } else { customMapNames.removeValue(map.name, false); Settings.putString("map-data-" + map.name, ""); - Settings.putJson("custom-maps", customMapNames); + Settings.putBinary("custom-maps", customMapNames); Settings.save(); } } @@ -163,7 +163,7 @@ public class Maps implements Disposable{ } }else{ - customMapNames = Settings.getJson("custom-maps", Array.class); + customMapNames = Settings.getBinary("custom-maps", Array.class, () -> new Array<>()); for(String name : customMapNames){ try{ diff --git a/core/src/io/anuke/mindustry/maps/Sector.java b/core/src/io/anuke/mindustry/maps/Sector.java index 9fd344d954..4299cb5497 100644 --- a/core/src/io/anuke/mindustry/maps/Sector.java +++ b/core/src/io/anuke/mindustry/maps/Sector.java @@ -2,6 +2,7 @@ package io.anuke.mindustry.maps; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.utils.Array; +import io.anuke.annotations.Annotations.Serialize; import io.anuke.mindustry.game.Difficulty; import io.anuke.mindustry.game.Saves.SaveSlot; import io.anuke.mindustry.game.SpawnGroup; @@ -12,6 +13,7 @@ import io.anuke.ucore.util.Bits; import static io.anuke.mindustry.Vars.control; +@Serialize public class Sector{ /**Position on the map, can be positive or negative.*/ public short x, y; diff --git a/core/src/io/anuke/mindustry/maps/Sectors.java b/core/src/io/anuke/mindustry/maps/Sectors.java index 4694c1cc94..965bbccc1a 100644 --- a/core/src/io/anuke/mindustry/maps/Sectors.java +++ b/core/src/io/anuke/mindustry/maps/Sectors.java @@ -29,10 +29,6 @@ public class Sectors{ private GridMap grid = new GridMap<>(); - public Sectors(){ - Settings.json().addClassTag("Sector", Sector.class); - } - public void playSector(Sector sector){ if(sector.hasSave() && SaveIO.breakingVersions.contains(sector.getSave().getBuild())){ sector.getSave().delete(); @@ -117,7 +113,7 @@ public class Sectors{ } grid.clear(); - Array out = Settings.getJson("sectors", Array.class); + Array out = Settings.getBinary("sectors", Array.class, () -> new Array<>()); for(Sector sector : out){ createTexture(sector); @@ -141,7 +137,7 @@ public class Sectors{ out.add(sector); } - Settings.putJson("sectors", out); + Settings.putBinary("sectors", out); Settings.save(); } diff --git a/core/src/io/anuke/mindustry/net/Administration.java b/core/src/io/anuke/mindustry/net/Administration.java index 40ede1b6dc..4c0a50469c 100644 --- a/core/src/io/anuke/mindustry/net/Administration.java +++ b/core/src/io/anuke/mindustry/net/Administration.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.net; import com.badlogic.gdx.utils.Array; import com.badlogic.gdx.utils.IntMap; import com.badlogic.gdx.utils.ObjectMap; +import io.anuke.annotations.Annotations.Serialize; import io.anuke.ucore.core.Settings; import static io.anuke.mindustry.Vars.headless; @@ -276,16 +277,17 @@ public class Administration{ } public void save(){ - Settings.putJson("player-info", playerInfo); - Settings.putJson("banned-ips", bannedIPs); + Settings.putBinary("player-info", playerInfo); + Settings.putBinary("banned-ips", bannedIPs); Settings.save(); } private void load(){ - playerInfo = Settings.getJson("player-info", ObjectMap.class); - bannedIPs = Settings.getJson("banned-ips", Array.class); + playerInfo = Settings.getBinary("player-info", ObjectMap.class, () -> new ObjectMap<>()); + bannedIPs = Settings.getBinary("banned-ips", Array.class, () -> new Array<>()); } + @Serialize public static class PlayerInfo{ public String id; public String lastName = "", lastIP = ""; @@ -303,7 +305,7 @@ public class Administration{ this.id = id; } - private PlayerInfo(){ + public PlayerInfo(){ } } diff --git a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java index 3b3192d63a..ad7de7fea2 100644 --- a/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java +++ b/core/src/io/anuke/mindustry/ui/dialogs/JoinDialog.java @@ -3,6 +3,7 @@ package io.anuke.mindustry.ui.dialogs; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.math.MathUtils; import com.badlogic.gdx.utils.Array; +import io.anuke.annotations.Annotations.Serialize; import io.anuke.mindustry.Vars; import io.anuke.mindustry.core.Platform; import io.anuke.mindustry.entities.Player; @@ -326,22 +327,24 @@ public class JoinDialog extends FloatingDialog{ } private void loadServers(){ - servers = Settings.getJson("server-list", Array.class); + servers = Settings.getBinary("server-list", Array.class, () -> new Array<>()); } private void saveServers(){ - Settings.putJson("server-list", servers); + Settings.putBinary("server-list", servers); Settings.save(); } - static class Server{ - String ip; - int port; + @Serialize + public static class Server{ + public String ip; + public int port; transient Host host; transient Table content; void setIP(String ip){ + //parse ip:port, if unsuccessful, use default values if(ip.lastIndexOf(':') != -1 && ip.lastIndexOf(':') != ip.length()-1){ try{ @@ -362,6 +365,6 @@ public class JoinDialog extends FloatingDialog{ return ip + (port != Vars.port ? ":" + port : ""); } - Server(){} + public Server(){} } }