Added entity revisions
This commit is contained in:
@@ -68,6 +68,25 @@ public abstract class BaseProcessor extends AbstractProcessor{
|
||||
}
|
||||
}
|
||||
|
||||
//in bytes
|
||||
public static int typeSize(String kind){
|
||||
switch(kind){
|
||||
case "boolean":
|
||||
case "byte":
|
||||
return 1;
|
||||
case "short":
|
||||
return 2;
|
||||
case "float":
|
||||
case "char":
|
||||
case "int":
|
||||
return 4;
|
||||
case "long":
|
||||
return 8;
|
||||
default:
|
||||
throw new IllegalArgumentException("Invalid primitive type: " + kind + "");
|
||||
}
|
||||
}
|
||||
|
||||
public static String simpleName(String str){
|
||||
return str.contains(".") ? str.substring(str.lastIndexOf('.') + 1) : str;
|
||||
}
|
||||
|
||||
@@ -1,59 +1,185 @@
|
||||
package mindustry.annotations.entity;
|
||||
|
||||
import arc.files.*;
|
||||
import arc.struct.*;
|
||||
import arc.util.serialization.*;
|
||||
import com.squareup.javapoet.*;
|
||||
import com.squareup.javapoet.MethodSpec.*;
|
||||
import mindustry.annotations.Annotations.*;
|
||||
import mindustry.annotations.*;
|
||||
import mindustry.annotations.util.TypeIOResolver.*;
|
||||
|
||||
import javax.lang.model.element.*;
|
||||
|
||||
import static mindustry.annotations.BaseProcessor.instanceOf;
|
||||
|
||||
public class EntityIO{
|
||||
final MethodSpec.Builder builder;
|
||||
final boolean write;
|
||||
final ClassSerializer serializer;
|
||||
final static Json json = new Json();
|
||||
|
||||
EntityIO(Builder builder, boolean write, ClassSerializer serializer){
|
||||
this.builder = builder;
|
||||
final ClassSerializer serializer;
|
||||
final String name;
|
||||
final TypeSpec.Builder type;
|
||||
final Fi directory;
|
||||
final Array<Revision> revisions = new Array<>();
|
||||
|
||||
boolean write;
|
||||
MethodSpec.Builder method;
|
||||
ObjectSet<String> presentFields = new ObjectSet<>();
|
||||
|
||||
EntityIO(String name, TypeSpec.Builder type, ClassSerializer serializer, Fi directory){
|
||||
this.directory = directory;
|
||||
this.type = type;
|
||||
this.serializer = serializer;
|
||||
this.write = write;
|
||||
this.name = name;
|
||||
|
||||
directory.mkdirs();
|
||||
|
||||
//load old revisions
|
||||
for(Fi fi : directory.list()){
|
||||
revisions.add(json.fromJson(Revision.class, fi));
|
||||
}
|
||||
|
||||
//next revision to be used
|
||||
int nextRevision = revisions.isEmpty() ? 0 : revisions.max(r -> r.version).version + 1;
|
||||
|
||||
//resolve preferred field order based on fields that fit
|
||||
Array<FieldSpec> fields = Array.with(type.fieldSpecs).select(spec ->
|
||||
!spec.hasModifier(Modifier.TRANSIENT) &&
|
||||
!spec.hasModifier(Modifier.STATIC) &&
|
||||
!spec.hasModifier(Modifier.FINAL) &&
|
||||
(spec.type.isPrimitive() || serializer.has(spec.type.toString())));
|
||||
|
||||
//sort to keep order
|
||||
fields.sortComparing(f -> f.name);
|
||||
|
||||
//keep track of fields present in the entity
|
||||
presentFields.addAll(fields.map(f -> f.name));
|
||||
|
||||
//add new revision if it doesn't match or there are no revisions
|
||||
if(revisions.isEmpty() || !revisions.peek().equal(fields)){
|
||||
revisions.add(new Revision(nextRevision, fields.map(f -> new RevisionField(f.name, f.type.toString(), f.type.isPrimitive() ? BaseProcessor.typeSize(f.type.toString()) : -1))));
|
||||
//write revision
|
||||
directory.child(nextRevision + ".json").writeString(json.toJson(revisions.peek()));
|
||||
}
|
||||
}
|
||||
|
||||
void io(TypeName type, String field) throws Exception{
|
||||
void write(MethodSpec.Builder method, boolean write) throws Exception{
|
||||
this.method = method;
|
||||
this.write = write;
|
||||
|
||||
if(type.isPrimitive()){
|
||||
s(type == TypeName.BOOLEAN ? "bool" : type.toString().charAt(0) + "", field);
|
||||
//}else if(type.toString().equals("java.lang.String")){
|
||||
// s("str", field);
|
||||
}else if(instanceOf(type.toString(), "mindustry.ctype.Content")){
|
||||
//subclasses *have* to call this method
|
||||
method.addAnnotation(CallSuper.class);
|
||||
|
||||
if(write){
|
||||
//write short revision
|
||||
st("write.s($L)", revisions.peek().version);
|
||||
//write uses most recent revision
|
||||
for(RevisionField field : revisions.peek().fields){
|
||||
io(field.type, "this." + field.name);
|
||||
}
|
||||
}else{
|
||||
//read revision
|
||||
st("short REV = read.s()");
|
||||
|
||||
for(int i = 0; i < revisions.size; i++){
|
||||
//check for the right revision
|
||||
Revision rev = revisions.get(i);
|
||||
if(i == 0){
|
||||
cont("if(REV == $L)", rev.version);
|
||||
}else{
|
||||
ncont("else if(REV == $L)", rev.version);
|
||||
}
|
||||
|
||||
//add code for reading revision
|
||||
for(RevisionField field : rev.fields){
|
||||
//if the field doesn't exist, the result will be an empty string, it won't get assigned
|
||||
io(field.type, presentFields.contains(field.name) ? "this." + field.name + " = " : "");
|
||||
}
|
||||
}
|
||||
|
||||
//throw exception on illegal revisions
|
||||
ncont("else");
|
||||
st("throw new IllegalArgumentException(\"Unknown revision '\" + REV + \"' for entity type '" + name + "'\")");
|
||||
econt();
|
||||
}
|
||||
}
|
||||
|
||||
private void io(String type, String field) throws Exception{
|
||||
if(BaseProcessor.isPrimitive(type)){
|
||||
s(type.equals("boolean") ? "bool" : type.charAt(0) + "", field);
|
||||
}else if(instanceOf(type, "mindustry.ctype.Content")){
|
||||
if(write){
|
||||
s("s", field + ".id");
|
||||
}else{
|
||||
st(field + " = mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type.toString()).toLowerCase().replace("type", ""));
|
||||
st(field + "mindustry.Vars.content.getByID(mindustry.ctype.ContentType.$L, read.s())", BaseProcessor.simpleName(type).toLowerCase().replace("type", ""));
|
||||
}
|
||||
}else if(serializer.writers.containsKey(type.toString()) && write){
|
||||
st("$L(write, $L)", serializer.writers.get(type.toString()), field);
|
||||
}else if(serializer.readers.containsKey(type.toString()) && !write){
|
||||
st("$L = $L(read)", field, serializer.readers.get(type.toString()));
|
||||
}else if(serializer.writers.containsKey(type) && write){
|
||||
st("$L(write, $L)", serializer.writers.get(type), field);
|
||||
}else if(serializer.readers.containsKey(type) && !write){
|
||||
st("$L$L(read)", field, serializer.readers.get(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void cont(String text, Object... fmt){
|
||||
builder.beginControlFlow(text, fmt);
|
||||
method.beginControlFlow(text, fmt);
|
||||
}
|
||||
|
||||
private void cont(){
|
||||
builder.endControlFlow();
|
||||
private void econt(){
|
||||
method.endControlFlow();
|
||||
}
|
||||
|
||||
private void ncont(String text, Object... fmt){
|
||||
method.nextControlFlow(text, fmt);
|
||||
}
|
||||
|
||||
private void st(String text, Object... args){
|
||||
builder.addStatement(text, args);
|
||||
method.addStatement(text, args);
|
||||
}
|
||||
|
||||
private void s(String type, String field){
|
||||
if(write){
|
||||
builder.addStatement("write.$L($L)", type, field);
|
||||
method.addStatement("write.$L($L)", type, field);
|
||||
}else{
|
||||
builder.addStatement("$L = read.$L()", field, type);
|
||||
method.addStatement("$Lread.$L()", field, type);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Revision{
|
||||
int version;
|
||||
Array<RevisionField> fields;
|
||||
|
||||
Revision(int version, Array<RevisionField> fields){
|
||||
this.version = version;
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
Revision(){}
|
||||
|
||||
/** @return whether these two revisions are compatible */
|
||||
boolean equal(Array<FieldSpec> specs){
|
||||
if(fields.size != specs.size) return false;
|
||||
|
||||
for(int i = 0; i < fields.size; i++){
|
||||
RevisionField field = fields.get(i);
|
||||
FieldSpec spec = specs.get(i);
|
||||
//TODO when making fields, their primitive size may be overwritten by an annotation; check for that
|
||||
if(!(field.type.equals(spec.type.toString()) && (!spec.type.isPrimitive() || BaseProcessor.typeSize(spec.type.toString()) == field.size))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RevisionField{
|
||||
String name, type;
|
||||
int size; //in bytes
|
||||
|
||||
RevisionField(String name, String type, int size){
|
||||
this.name = name;
|
||||
this.size = size;
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
RevisionField(){}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -275,6 +275,8 @@ public class EntityProcess extends BaseProcessor{
|
||||
.addModifiers(Modifier.PUBLIC)
|
||||
.addStatement("return $S + $L", name + "#", "id").build());
|
||||
|
||||
EntityIO io = new EntityIO(type.name(), builder, serializer, rootDirectory.child("annotations/src/main/resources/revisions").child(name));
|
||||
|
||||
//add all methods from components
|
||||
for(ObjectMap.Entry<String, Array<Smethod>> entry : methods){
|
||||
if(entry.value.contains(m -> m.has(Replace.class))){
|
||||
@@ -332,15 +334,7 @@ public class EntityProcess extends BaseProcessor{
|
||||
//SPECIAL CASE: I/O code
|
||||
//note that serialization is generated even for non-serializing entities for manual usage
|
||||
if((first.name().equals("read") || first.name().equals("write")) && ann.genio()){
|
||||
Array<FieldSpec> fields = Array.with(builder.fieldSpecs).select(spec -> !spec.hasModifier(Modifier.TRANSIENT) && !spec.hasModifier(Modifier.STATIC) && !spec.hasModifier(Modifier.FINAL));
|
||||
fields.sortComparing(f -> f.name); //sort to keep order
|
||||
EntityIO writer = new EntityIO(mbuilder, first.name().equals("write"), serializer);
|
||||
//subclasses *have* to call this method
|
||||
mbuilder.addAnnotation(CallSuper.class);
|
||||
//write or read each non-transient field
|
||||
for(FieldSpec spec : fields){
|
||||
writer.io(spec.type, "this." + spec.name);
|
||||
}
|
||||
io.write(mbuilder, first.name().equals("write"));
|
||||
}
|
||||
|
||||
for(Smethod elem : entry.value){
|
||||
|
||||
@@ -45,5 +45,9 @@ public class TypeIOResolver{
|
||||
this.writers = writers;
|
||||
this.readers = readers;
|
||||
}
|
||||
|
||||
public boolean has(String type){
|
||||
return writers.containsKey(type) && readers.containsKey(type);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user