work on gen
This commit is contained in:
parent
2a0225abdf
commit
7e66acab76
26
casestodesignfor
Normal file
26
casestodesignfor
Normal file
@ -0,0 +1,26 @@
|
||||
|
||||
|
||||
Spawn a tree
|
||||
Lets say there's a concept of macros within the networking framework
|
||||
You call the spawn foliage function specifying the type of tree
|
||||
the spawn foliage function manually calls the server side macro to create a tree
|
||||
This macro sends a packet to all clients in the chunk to create this tree entity
|
||||
Each client creates the tree with the client equivalent of the create tree function
|
||||
|
||||
Player joins a chunk that has a tree that is on fire
|
||||
The macro is called to spawn a tree, just for that player (how do we tell to call macro for this entity and not generic spawn entity function?)
|
||||
...?
|
||||
For each btree that isn't a part of the default created tree, keep a list on the entity of additional, attached trees
|
||||
when the tree is created, loop through this list of additional trees and send packets to client to synchronize its state
|
||||
^This could also include default trees that aren't in default state
|
||||
^^At that point just include all trees for ease of use's sake
|
||||
|
||||
Kill a tree
|
||||
Server death animation tree fires, also triggering client death animation tree to fire
|
||||
once the animation has finished, the server calls the delete entity function
|
||||
the delete entity function server side sends a special packet to client that triggers client delete entity function
|
||||
|
||||
A player joins a chunk with a player entity in it
|
||||
The server sends a packet to create a new human
|
||||
The server sends the human template packet for that entity ID
|
||||
The server iterates over every synchronized behavior tree and sends the status of each variable that is synchronized within that tree
|
||||
58
goals
Normal file
58
goals
Normal file
@ -0,0 +1,58 @@
|
||||
Annotation on fields
|
||||
Annotation has param to not generate callbacks eg if we want to handle that manually but still label a field as synchronized
|
||||
Annotation will have a parameter that specifies a class which the corresponding field is in. Ie server field will have param pointing to client file
|
||||
|
||||
Callback object to handle initial sync packet
|
||||
Specifically, a callback for sending the data and one for receiving the data, each for client and server
|
||||
All four callback skeletons are generated for each field annotated
|
||||
|
||||
A fifth function is generated to attach the callbacks to a given entity, this is used to enforce it being properly attached every time
|
||||
|
||||
A getter and setter are each generated
|
||||
The setter has logic that will distribute the new value every time it is called, alternatively a parameter on the Annotation can throttle how often the value is updated
|
||||
Use engine specific functions within setter to actually send packet
|
||||
Because the logic to send packet is different on server and client, will need to annotate behaviortree with either server or client annotation
|
||||
Should have validation in cli that verifies the field is never manually set outside the setter
|
||||
|
||||
|
||||
Service with map of entity to list of callbacks to synchronize
|
||||
The service is generated in a folder specified by a json config
|
||||
Service receives packets with its given packet category each frame
|
||||
These packets contain entity ID, field id, and new value
|
||||
Loops through all packets at start of frame and sets values on entities accordingly
|
||||
|
||||
|
||||
When a scene is being synchronized, loop through every entity in the scene cross referencing the service and firing callbacks to generate packets
|
||||
Callback job is to grab current value and send over net
|
||||
Attribute data could theoretically change if changing things like player hair are implemented, so that should be stored as dynamic data
|
||||
|
||||
|
||||
|
||||
For non entity fields (think weather status of a region, number of players logged in, etc), the flow works mostly the same
|
||||
Annotation still has a link to related client field
|
||||
Callbacks still generated
|
||||
When value is set, call chunk independent function to submit packet
|
||||
When packets are being looped through, there's a different class of packet that has field id and value, but no entity ID
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Cases to consider
|
||||
A chat log
|
||||
A character playing an instrument
|
||||
Creating a particle
|
||||
Playing a sound
|
||||
Creating an effect generally (ie fire)
|
||||
Creating a speech bubble over a character
|
||||
Playing an animation
|
||||
Writing a book on the client side
|
||||
Fetching book contents from server(lazy load?)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Generally want synchronized fields as well at the ability to call a function on server and client simultaneously (ie 'create fire here')
|
||||
@ -9,37 +9,51 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.jboss.forge.roaster.Roaster;
|
||||
import org.jboss.forge.roaster.model.ValuePair;
|
||||
import org.jboss.forge.roaster.model.source.AnnotationSource;
|
||||
import org.jboss.forge.roaster.model.source.EnumConstantSource;
|
||||
import org.jboss.forge.roaster.model.source.FieldSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaDocSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaEnumSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaSource;
|
||||
import org.jboss.forge.roaster.model.source.MethodSource;
|
||||
|
||||
import electrosphere.main.targets.NetcodeGenTarget;
|
||||
import electrosphere.main.codegen.CodeGen;
|
||||
import electrosphere.main.structure.ProjectStructure;
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author amaterasu
|
||||
* Code Generation Utility
|
||||
*/
|
||||
public class Main {
|
||||
|
||||
static String topLevelFolderPath = "C:\\Users\\satellite\\Documents\\Renderer\\src\\main\\java\\electrosphere";
|
||||
|
||||
static String[] targetFiles = new String[]{
|
||||
"C:\\Users\\satellite\\Documents\\Renderer\\src\\main\\java\\electrosphere\\entity\\state\\IdleTree.java",
|
||||
"C:\\Users\\satellite\\Documents\\Renderer\\src\\main\\java\\electrosphere\\entity\\state\\idle\\IdleTree.java",
|
||||
};
|
||||
|
||||
static int bTreeIterator = 0;
|
||||
public static int bTreeIterator = 0;
|
||||
|
||||
//maps the btree annotation "name" to an id
|
||||
static Map<String,Integer> bTreeIdMap = new HashMap<String,Integer>();
|
||||
public static Map<String,Integer> bTreeIdMap = new HashMap<String,Integer>();
|
||||
|
||||
public static void main(String args[]){
|
||||
List<TargetFile> targets = getFilesToModify(topLevelFolderPath);
|
||||
ProjectStructure structure = new ProjectStructure(targets);
|
||||
structure.parseRichStructure();
|
||||
structure.generate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses library to parse a java source file into rich data
|
||||
* @param content
|
||||
* @return
|
||||
*/
|
||||
static JavaClassSource parseJavaFile(String content){
|
||||
return Roaster.parse(JavaClassSource.class, content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a directory path into a list of target files that should be parsed into overall project structure
|
||||
* @param topLevelFolderPath The top level directory of the project
|
||||
* @return The list of target files
|
||||
*/
|
||||
static List<TargetFile> getFilesToModify(String topLevelFolderPath){
|
||||
List<TargetFile> rVal = new LinkedList<TargetFile>();
|
||||
File topLevelFolder = new File(topLevelFolderPath);
|
||||
List<File> fileQueue = new LinkedList<File>();
|
||||
List<File> fileQueueOpenSet = new LinkedList<File>();
|
||||
@ -56,11 +70,10 @@ public class Main {
|
||||
String content = "";
|
||||
try {
|
||||
content = Files.readString(currentFile.toPath());
|
||||
if(content.contains("@BehaviorTreeAnnotation")){
|
||||
if(content.contains("@SynchronizedBehaviorTree")){
|
||||
//parse
|
||||
JavaClassSource source = parseJavaFile(content);
|
||||
//generate code
|
||||
generateCode(source, currentFile, content);
|
||||
rVal.add(new TargetFile(currentFile.getAbsolutePath(), content, currentFile.getName(), source));
|
||||
}
|
||||
} catch (IOException e){
|
||||
e.printStackTrace();
|
||||
@ -79,295 +92,7 @@ public class Main {
|
||||
fileQueue.remove(currentClosedFile);
|
||||
}
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
static JavaClassSource parseJavaFile(String content){
|
||||
return Roaster.parse(JavaClassSource.class, content);
|
||||
}
|
||||
|
||||
static void generateCode(JavaClassSource source, File file, String content){
|
||||
String bTreeName = source.getAnnotation("BehaviorTreeAnnotation").getStringValue("name");
|
||||
bTreeIdMap.put(bTreeName,bTreeIterator);
|
||||
bTreeIterator++;
|
||||
|
||||
TargetFile targetFile = new TargetFile(file.getAbsolutePath(), content, bTreeName, source);
|
||||
StringBuilder outputContent = new StringBuilder(content);
|
||||
List<NetcodeGenTarget> targets = parseGenerationTargets(targetFile);
|
||||
for(NetcodeGenTarget target : targets){
|
||||
//check if has getter
|
||||
MethodSource<JavaClassSource> getter = null;
|
||||
MethodSource<JavaClassSource> setter = null;
|
||||
MethodSource<JavaClassSource> enumToIntMapper = null;
|
||||
MethodSource<JavaClassSource> intToEnumMapper = null;
|
||||
for(MethodSource<JavaClassSource> method : targetFile.getSource().getMethods()){
|
||||
if(method.getName().equals(getGetterName(target.getName()))){
|
||||
getter = method;
|
||||
}
|
||||
if(method.getName().equals(getSetterName(target.getName()))){
|
||||
setter = method;
|
||||
}
|
||||
if(method.getName().equals(getEnumToIntMapperName(target.getName()))){
|
||||
enumToIntMapper = method;
|
||||
}
|
||||
if(method.getName().equals(getIntToEnumMapperName(target.getName()))){
|
||||
intToEnumMapper = method;
|
||||
}
|
||||
if(getter != null && setter != null && enumToIntMapper != null && intToEnumMapper != null){
|
||||
break;
|
||||
}
|
||||
}
|
||||
//check if need to make enum to int mapper function
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
//find enum in current file
|
||||
JavaSource<JavaEnumSource> enumSource = null;
|
||||
for(JavaSource<?> nestedSource : targetFile.getSource().getNestedTypes()){
|
||||
if(nestedSource.getName().equals(target.getTypeName())){
|
||||
enumSource = (JavaSource<JavaEnumSource>)nestedSource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<String> enumConstNames = new LinkedList<String>();
|
||||
if(enumSource != null){
|
||||
for(EnumConstantSource enumConstant : enumSource.getOrigin().getEnumConstants()){
|
||||
enumConstNames.add(enumConstant.getName());
|
||||
}
|
||||
}
|
||||
//mapper enum->int
|
||||
if(enumToIntMapper != null){
|
||||
//regenerate
|
||||
int startChar = enumToIntMapper.getStartPosition();
|
||||
int endChar = enumToIntMapper.getEndPosition();
|
||||
outputContent = outputContent.replace(startChar, endChar, generateEnumToIntMapperCode(target,enumConstNames));
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
outputContent.insert(positionJustBeforeClassEnd, generateEnumToIntMapperCode(target,enumConstNames));
|
||||
}
|
||||
//mapper int->enum
|
||||
if(intToEnumMapper != null){
|
||||
//regenerate
|
||||
int startChar = intToEnumMapper.getStartPosition();
|
||||
int endChar = intToEnumMapper.getEndPosition();
|
||||
outputContent = outputContent.replace(startChar, endChar, generateIntToEnumMapperCode(target,enumConstNames));
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
outputContent.insert(positionJustBeforeClassEnd, generateIntToEnumMapperCode(target,enumConstNames));
|
||||
}
|
||||
}
|
||||
//getter
|
||||
if(getter != null){
|
||||
//regenerate
|
||||
int startChar = getter.getStartPosition();
|
||||
int endChar = getter.getEndPosition();
|
||||
outputContent = outputContent.replace(startChar, endChar, generateGetterCode(target.getName(),target));
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
outputContent.insert(positionJustBeforeClassEnd, generateGetterCode(target.getName(),target));
|
||||
}
|
||||
//setter
|
||||
if(setter != null){
|
||||
//regenerate
|
||||
int startChar = setter.getStartPosition();
|
||||
int endChar = setter.getEndPosition();
|
||||
outputContent = outputContent.replace(startChar, endChar, generateSetterCode(targetFile,target.getName(),target));
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
outputContent.insert(positionJustBeforeClassEnd, generateSetterCode(targetFile,target.getName(),target));
|
||||
}
|
||||
//message parsing on behavior tree in general
|
||||
//...TODO
|
||||
|
||||
}
|
||||
//message parser generation
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
outputContent.insert(positionJustBeforeClassEnd, generateParseBTreeMessages(targetFile, targets));
|
||||
|
||||
System.out.println(outputContent);
|
||||
}
|
||||
|
||||
static List<NetcodeGenTarget> parseGenerationTargets(TargetFile targetFile){
|
||||
int targetIdIterator = 0;
|
||||
List<NetcodeGenTarget> targets = new LinkedList<NetcodeGenTarget>();
|
||||
for(FieldSource<JavaClassSource> field : targetFile.getSource().getFields()){
|
||||
List<AnnotationSource<JavaClassSource>> annotations = field.getAnnotations();
|
||||
for(AnnotationSource<JavaClassSource> annotation : annotations){
|
||||
if(annotation.getName().equals("SyncedField")){
|
||||
targets.add(new NetcodeGenTarget(targetIdIterator, field.getName(), field.getType().getName(), field, annotation));
|
||||
targetIdIterator++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
static void sourceCodeModification(){
|
||||
String sampleFile = "";
|
||||
try {
|
||||
sampleFile = Files.readString(new File("C:/Users/satellite/Documents/Renderer/src/main/java/electrosphere/renderer/texture/Texture.java").toPath());
|
||||
} catch (IOException e) {
|
||||
// TODO Auto-generated catch block
|
||||
e.printStackTrace();
|
||||
}
|
||||
JavaClassSource javaClass = Roaster.parse(JavaClassSource.class, sampleFile);
|
||||
javaClass.addMethod()
|
||||
.setPublic()
|
||||
.setStatic(true)
|
||||
.setName("main")
|
||||
.setReturnTypeVoid()
|
||||
.setBody("System.out.println(\"Hello World\");")
|
||||
.addParameter("java.lang.String[]", "args");
|
||||
System.out.println(javaClass);
|
||||
}
|
||||
|
||||
static void addingJavadocToClass(){
|
||||
JavaClassSource javaClass = Roaster.parse(JavaClassSource.class, "public class SomeClass {}");
|
||||
JavaDocSource javaDoc = javaClass.getJavaDoc();
|
||||
|
||||
javaDoc.setFullText("Full class documentation");
|
||||
// or
|
||||
javaDoc.setText("Class documentation text");
|
||||
javaDoc.addTagValue("@author","George Gastaldi");
|
||||
|
||||
System.out.println(javaClass);
|
||||
}
|
||||
|
||||
static String getGetterName(String fieldName){
|
||||
return "get" + camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getSetterName(String fieldName){
|
||||
return "set" + camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getEnumToIntMapperName(String fieldName){
|
||||
return fieldName + "ToInt";
|
||||
}
|
||||
|
||||
static String getIntToEnumMapperName(String fieldName){
|
||||
return fieldName + "FromInt";
|
||||
}
|
||||
|
||||
static String camelCase(String input){
|
||||
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
|
||||
}
|
||||
|
||||
static String generateGetterCode(String variableName, NetcodeGenTarget target){
|
||||
return Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/Getter.java"))
|
||||
.replace("REPLACENAMECAMEL",camelCase(variableName))
|
||||
.replace("REPLACETYPE",target.getTypeName())
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
}
|
||||
|
||||
static String generateSetterCode(TargetFile targetFile, String variableName, NetcodeGenTarget target){
|
||||
String messageConstructor = "";
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/SetterInt.java"))
|
||||
.replace("REPLACEBTREEID",bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
} break;
|
||||
case "double": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/SetterDouble.java"))
|
||||
.replace("REPLACEBTREEID",bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
} break;
|
||||
case "float": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/SetterFloat.java"))
|
||||
.replace("REPLACEBTREEID",bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
} break;
|
||||
case "String": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/SetterString.java"))
|
||||
.replace("REPLACEBTREEID",bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
} break;
|
||||
}
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/SetterEnum.java"))
|
||||
.replace("REPLACEBTREEID",bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMECAMEL",camelCase(variableName))
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
}
|
||||
return Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/Setter.java"))
|
||||
.replace("REPLACENAMECAMEL",camelCase(variableName))
|
||||
.replace("REPLACETYPE",target.getTypeName())
|
||||
.replace("REPLACENAMENOTCAMEL",variableName)
|
||||
.replace("REPLACEMESSAGECONSTRUCTOR",messageConstructor);
|
||||
}
|
||||
|
||||
static String generateEnumToIntMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + enumConstName + ":\n" +
|
||||
"return " + i + ";\n";
|
||||
i++;
|
||||
}
|
||||
return Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/EnumToIntMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",camelCase(target.getName()))
|
||||
.replace("REPLACECASE",switchCases)
|
||||
.replace("REPLACETYPENAME",target.getTypeName());
|
||||
}
|
||||
|
||||
|
||||
static String generateIntToEnumMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + i + ":\n" +
|
||||
"return " + target.getTypeName() + "." + enumConstName + ";\n";
|
||||
i++;
|
||||
}
|
||||
return Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/IntToEnumMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",camelCase(target.getName()))
|
||||
.replace("REPLACECASE",switchCases);
|
||||
}
|
||||
|
||||
static String generateParseBTreeMessages(TargetFile targetFile, List<NetcodeGenTarget> targets){
|
||||
String setCases = "";
|
||||
for(NetcodeGenTarget target : targets){
|
||||
String base = "case REPLACE_PROPERTY_ID: {\n" +
|
||||
"setREPLACE_VARIABLE_NAME_CAMEL(REPLACE_GET_PROPERTY_VALUE);\n" +
|
||||
"} break;\n";
|
||||
String propertyValueFetcher = "";
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
propertyValueFetcher = "message.getpropertyValueInt()";
|
||||
} break;
|
||||
case "float": {
|
||||
propertyValueFetcher = "message.getpropertyValueFloat()";
|
||||
} break;
|
||||
case "double": {
|
||||
propertyValueFetcher = "message.getpropertyValueDouble()";
|
||||
} break;
|
||||
case "String": {
|
||||
propertyValueFetcher = "message.getpropertyValueString()";
|
||||
} break;
|
||||
//enum
|
||||
default: {
|
||||
propertyValueFetcher = "getEnumIntValueREPLACENAMECAMEL(message.getpropertyValueInt())".replace("REPLACENAMECAMEL",camelCase(target.getName()));
|
||||
} break;
|
||||
}
|
||||
String replaced = base
|
||||
.replace("REPLACE_PROPERTY_ID",target.getId() + "")
|
||||
.replace("REPLACE_GET_PROPERTY_VALUE",propertyValueFetcher)
|
||||
.replace("REPLACE_VARIABLE_NAME_CAMEL",camelCase(target.getName()));
|
||||
System.out.println(replaced);
|
||||
setCases = setCases + replaced;
|
||||
}
|
||||
return Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/ParseBTreeMessages.java"))
|
||||
.replace("REPLACE_WITH_CASES",setCases);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@ -1,53 +0,0 @@
|
||||
package electrosphere.main;
|
||||
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class Utilities {
|
||||
|
||||
static final int maxReadFails = 3;
|
||||
static final int READ_TIMEOUT_DURATION = 5;
|
||||
public static String readBakedResourceToString(InputStream resourceInputStream){
|
||||
String rVal = "";
|
||||
BufferedReader reader;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(resourceInputStream));
|
||||
int failCounter = 0;
|
||||
boolean reading = true;
|
||||
StringBuilder builder = new StringBuilder("");
|
||||
while(reading){
|
||||
if(reader.ready()){
|
||||
failCounter = 0;
|
||||
int nextValue = reader.read();
|
||||
if(nextValue == -1){
|
||||
reading = false;
|
||||
} else {
|
||||
builder.append((char)nextValue);
|
||||
}
|
||||
} else {
|
||||
failCounter++;
|
||||
if(failCounter > maxReadFails){
|
||||
reading = false;
|
||||
} else {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(READ_TIMEOUT_DURATION);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rVal = builder.toString();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
292
src/main/java/electrosphere/main/codegen/ClientGen.java
Normal file
292
src/main/java/electrosphere/main/codegen/ClientGen.java
Normal file
@ -0,0 +1,292 @@
|
||||
package electrosphere.main.codegen;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.forge.roaster.model.source.AnnotationSource;
|
||||
import org.jboss.forge.roaster.model.source.EnumConstantSource;
|
||||
import org.jboss.forge.roaster.model.source.FieldSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaEnumSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaSource;
|
||||
import org.jboss.forge.roaster.model.source.MethodSource;
|
||||
|
||||
import electrosphere.main.Main;
|
||||
import electrosphere.main.targets.NetcodeGenTarget;
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
import electrosphere.main.util.Utilities;
|
||||
|
||||
/**
|
||||
* Generates client-side synchronization code
|
||||
*/
|
||||
public class ClientGen {
|
||||
|
||||
|
||||
|
||||
public static void generateCode(JavaClassSource source, String bTreeName, File file, String content) throws Exception {
|
||||
TargetFile targetFile = new TargetFile(file.getAbsolutePath(), content, bTreeName, source);
|
||||
StringBuilder outputContent = new StringBuilder(content);
|
||||
List<NetcodeGenTarget> targets = parseGenerationTargets(targetFile);
|
||||
//
|
||||
//Find if there was already a parse network message function
|
||||
MethodSource<JavaClassSource> parseNetworkMessageFunction = null;
|
||||
for(MethodSource<JavaClassSource> method : targetFile.getSource().getMethods()){
|
||||
if(method.getName().equals("parseBTreeMessages")){
|
||||
parseNetworkMessageFunction = method;
|
||||
}
|
||||
}
|
||||
//
|
||||
//Code gen for each synchronized variable
|
||||
//
|
||||
for(NetcodeGenTarget target : targets){
|
||||
//
|
||||
//Look for existing methods
|
||||
MethodSource<JavaClassSource> setter = null;
|
||||
MethodSource<JavaClassSource> enumToIntMapper = null;
|
||||
MethodSource<JavaClassSource> intToEnumMapper = null;
|
||||
for(MethodSource<JavaClassSource> method : targetFile.getSource().getMethods()){
|
||||
if(method.getName().equals(getSetterName(target.getName()))){
|
||||
setter = method;
|
||||
}
|
||||
if(method.getName().equals(getEnumToIntMapperName(target.getName()))){
|
||||
enumToIntMapper = method;
|
||||
}
|
||||
if(method.getName().equals(getIntToEnumMapperName(target.getName()))){
|
||||
intToEnumMapper = method;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//check if need to make enum to int mapper function
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
if(target.getAnnotation().getStringValue("enumId") == null){
|
||||
throw new Exception("Failed to parse enum because enumId is not set on the synchronized field annotation");
|
||||
}
|
||||
int enumId = Integer.parseInt(target.getAnnotation().getStringValue("enumId"));
|
||||
|
||||
//
|
||||
//find enum in current file
|
||||
JavaSource<JavaEnumSource> enumSource = null;
|
||||
for(JavaSource<?> nestedSource : targetFile.getSource().getNestedTypes()){
|
||||
if(nestedSource.getName().equals(target.getTypeName())){
|
||||
enumSource = (JavaSource<JavaEnumSource>)nestedSource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<String> enumConstNames = new LinkedList<String>();
|
||||
if(enumSource != null){
|
||||
for(EnumConstantSource enumConstant : enumSource.getOrigin().getEnumConstants()){
|
||||
enumConstNames.add(enumConstant.getName());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//mapper enum->int
|
||||
if(enumToIntMapper != null){
|
||||
//regenerate
|
||||
int startChar = enumToIntMapper.getStartPosition();
|
||||
int endChar = enumToIntMapper.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateEnumToIntMapperCode(target,enumConstNames), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateEnumToIntMapperCode(target,enumConstNames), lineNumber);
|
||||
}
|
||||
|
||||
//
|
||||
//mapper int->enum
|
||||
if(intToEnumMapper != null){
|
||||
//regenerate
|
||||
int startChar = intToEnumMapper.getStartPosition();
|
||||
int endChar = intToEnumMapper.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateIntToEnumMapperCode(target,enumConstNames), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateIntToEnumMapperCode(target,enumConstNames), lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
//generate setter
|
||||
if(setter != null){
|
||||
//regenerate
|
||||
int startChar = setter.getStartPosition();
|
||||
int endChar = setter.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateSetterCode(targetFile,target.getName(),target), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateSetterCode(targetFile,target.getName(),target), lineNumber);
|
||||
}
|
||||
}
|
||||
//message parser generation
|
||||
if(parseNetworkMessageFunction == null){
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateParseBTreeMessages(targetFile, targets), lineNumber);
|
||||
} else {
|
||||
//regenerate
|
||||
int startChar = parseNetworkMessageFunction.getStartPosition();
|
||||
int endChar = parseNetworkMessageFunction.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateParseBTreeMessages(targetFile, targets), lineNumber);
|
||||
}
|
||||
|
||||
// System.out.println(outputContent);
|
||||
}
|
||||
|
||||
static List<NetcodeGenTarget> parseGenerationTargets(TargetFile targetFile){
|
||||
int targetIdIterator = 0;
|
||||
List<NetcodeGenTarget> targets = new LinkedList<NetcodeGenTarget>();
|
||||
for(FieldSource<JavaClassSource> field : targetFile.getSource().getFields()){
|
||||
List<AnnotationSource<JavaClassSource>> annotations = field.getAnnotations();
|
||||
for(AnnotationSource<JavaClassSource> annotation : annotations){
|
||||
if(annotation.getName().equals("SyncedField")){
|
||||
targets.add(new NetcodeGenTarget(targetIdIterator, field.getName(), field.getType().getName(), field, annotation));
|
||||
targetIdIterator++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
static String getSetterName(String fieldName){
|
||||
return "set" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getEnumToIntMapperName(String fieldName){
|
||||
return "getEnumIntValue" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getIntToEnumMapperName(String fieldName){
|
||||
return "getIntEnumValue" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String generateSetterCode(TargetFile targetFile, String variableName, NetcodeGenTarget target){
|
||||
String messageConstructor = "";
|
||||
//
|
||||
//regular types
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/SetterInt.java"));
|
||||
} break;
|
||||
case "double": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/SetterDouble.java"));
|
||||
} break;
|
||||
case "float": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/SetterFloat.java"));
|
||||
} break;
|
||||
case "String": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/SetterString.java"));
|
||||
} break;
|
||||
}
|
||||
messageConstructor = messageConstructor
|
||||
.replace("REPLACEBTREEID",Main.bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
//
|
||||
//enum type handling
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/SetterEnum.java"))
|
||||
.replace("REPLACEBTREEID",Main.bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(variableName))
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
}
|
||||
//
|
||||
//returns setter string
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/Setter.java"))
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(variableName))
|
||||
.replace("REPLACETYPE",target.getTypeName())
|
||||
.replace("REPLACENAMENOTCAMEL",variableName)
|
||||
.replace("REPLACEMESSAGECONSTRUCTOR",messageConstructor);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
static String generateEnumToIntMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + enumConstName + ":\n" +
|
||||
"return " + i + ";\n";
|
||||
i++;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/EnumToIntMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()))
|
||||
.replace("REPLACETYPENAME",target.getTypeName());
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, switchCases, "REPLACECASE");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
static String generateIntToEnumMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + i + ":\n" +
|
||||
"return " + target.getTypeName() + "." + enumConstName + ";\n";
|
||||
i++;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/IntToEnumMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()));
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, switchCases, "REPLACECASE");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
static String generateParseBTreeMessages(TargetFile targetFile, List<NetcodeGenTarget> targets){
|
||||
String setCases = "";
|
||||
for(NetcodeGenTarget target : targets){
|
||||
String base = "case REPLACE_PROPERTY_ID: {\n" +
|
||||
"setREPLACE_VARIABLE_NAME_CAMEL(REPLACE_GET_PROPERTY_VALUE);\n" +
|
||||
"} break;\n";
|
||||
String propertyValueFetcher = "";
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
propertyValueFetcher = "message.getpropertyValueInt()";
|
||||
} break;
|
||||
case "float": {
|
||||
propertyValueFetcher = "message.getpropertyValueFloat()";
|
||||
} break;
|
||||
case "double": {
|
||||
propertyValueFetcher = "message.getpropertyValueDouble()";
|
||||
} break;
|
||||
case "String": {
|
||||
propertyValueFetcher = "message.getpropertyValueString()";
|
||||
} break;
|
||||
//enum
|
||||
default: {
|
||||
propertyValueFetcher = "getIntEnumValueREPLACENAMECAMEL(message.getpropertyValueInt())".replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()));
|
||||
} break;
|
||||
}
|
||||
String replaced = base
|
||||
.replace("REPLACE_PROPERTY_ID",target.getId() + "")
|
||||
.replace("REPLACE_GET_PROPERTY_VALUE",propertyValueFetcher)
|
||||
.replace("REPLACE_VARIABLE_NAME_CAMEL",Utilities.camelCase(target.getName()));
|
||||
setCases = setCases + replaced;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/client/ParseBTreeMessages.java"));
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, setCases, "REPLACE_WITH_CASES");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
}
|
||||
31
src/main/java/electrosphere/main/codegen/CodeGen.java
Normal file
31
src/main/java/electrosphere/main/codegen/CodeGen.java
Normal file
@ -0,0 +1,31 @@
|
||||
package electrosphere.main.codegen;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.jboss.forge.roaster.model.source.AnnotationSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
|
||||
import electrosphere.main.Main;
|
||||
|
||||
/**
|
||||
* Generates synchronization code. Principally figures out if it's a server or a client btree and handles accordingly.
|
||||
*/
|
||||
public class CodeGen {
|
||||
|
||||
public static void generateCode(JavaClassSource source, File file, String content) throws Exception {
|
||||
AnnotationSource<JavaClassSource> mainAnnotation = source.getAnnotation("SynchronizedBehaviorTree");
|
||||
|
||||
String bTreeName = mainAnnotation.getStringValue("name");
|
||||
boolean isServer = Boolean.parseBoolean(mainAnnotation.getStringValue("isServer"));
|
||||
|
||||
Main.bTreeIdMap.put(bTreeName,Main.bTreeIterator);
|
||||
Main.bTreeIterator++;
|
||||
|
||||
if(isServer){
|
||||
ServerGen.generateCode(source, bTreeName, file, content);
|
||||
} else {
|
||||
ClientGen.generateCode(source, bTreeName, file, content);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
316
src/main/java/electrosphere/main/codegen/ServerGen.java
Normal file
316
src/main/java/electrosphere/main/codegen/ServerGen.java
Normal file
@ -0,0 +1,316 @@
|
||||
package electrosphere.main.codegen;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.forge.roaster.model.source.AnnotationSource;
|
||||
import org.jboss.forge.roaster.model.source.EnumConstantSource;
|
||||
import org.jboss.forge.roaster.model.source.FieldSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaEnumSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaSource;
|
||||
import org.jboss.forge.roaster.model.source.MethodSource;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import electrosphere.main.Main;
|
||||
import electrosphere.main.targets.NetcodeGenTarget;
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
import electrosphere.main.util.Utilities;
|
||||
|
||||
/**
|
||||
* Generates server-side synchronization code
|
||||
*/
|
||||
public class ServerGen {
|
||||
|
||||
|
||||
/**
|
||||
* Main method for replacing source file content with synchronization code
|
||||
* @param source The roaster source file object
|
||||
* @param bTreeName The name of this btree
|
||||
* @param file The file handle for this source file
|
||||
* @param content The content of the source file
|
||||
* @throws Exception Can throw an exception if an enum isn't properly annotated
|
||||
*/
|
||||
public static void generateCode(JavaClassSource source, String bTreeName, File file, String content) throws Exception {
|
||||
TargetFile targetFile = new TargetFile(file.getAbsolutePath(), content, bTreeName, source);
|
||||
StringBuilder outputContent = new StringBuilder(content);
|
||||
List<NetcodeGenTarget> targets = parseGenerationTargets(targetFile);
|
||||
//
|
||||
//Find if there was already a parse network message function
|
||||
MethodSource<JavaClassSource> parseNetworkMessageFunction = null;
|
||||
for(MethodSource<JavaClassSource> method : targetFile.getSource().getMethods()){
|
||||
if(method.getName().equals("parseBTreeMessages")){
|
||||
parseNetworkMessageFunction = method;
|
||||
}
|
||||
}
|
||||
//
|
||||
//Code gen for each synchronized variable
|
||||
//
|
||||
for(NetcodeGenTarget target : targets){
|
||||
//
|
||||
//Look for existing methods
|
||||
MethodSource<JavaClassSource> setter = null;
|
||||
MethodSource<JavaClassSource> enumToIntMapper = null;
|
||||
MethodSource<JavaClassSource> intToEnumMapper = null;
|
||||
for(MethodSource<JavaClassSource> method : targetFile.getSource().getMethods()){
|
||||
if(method.getName().equals(getSetterName(target.getName()))){
|
||||
setter = method;
|
||||
}
|
||||
if(method.getName().equals(getEnumToIntMapperName(target.getName()))){
|
||||
enumToIntMapper = method;
|
||||
}
|
||||
if(method.getName().equals(getIntToEnumMapperName(target.getName()))){
|
||||
intToEnumMapper = method;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//
|
||||
//check if need to make enum to int mapper function
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
if(target.getAnnotation().getStringValue("enumId") == null){
|
||||
throw new Exception("Failed to parse enum because enumId is not set on the synchronized field annotation");
|
||||
}
|
||||
int enumId = Integer.parseInt(target.getAnnotation().getStringValue("enumId"));
|
||||
|
||||
//
|
||||
//find enum in current file
|
||||
JavaSource<JavaEnumSource> enumSource = null;
|
||||
for(JavaSource<?> nestedSource : targetFile.getSource().getNestedTypes()){
|
||||
if(nestedSource.getName().equals(target.getTypeName())){
|
||||
enumSource = (JavaSource<JavaEnumSource>)nestedSource;
|
||||
break;
|
||||
}
|
||||
}
|
||||
List<String> enumConstNames = new LinkedList<String>();
|
||||
if(enumSource != null){
|
||||
for(EnumConstantSource enumConstant : enumSource.getOrigin().getEnumConstants()){
|
||||
enumConstNames.add(enumConstant.getName());
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//mapper enum->int
|
||||
if(enumToIntMapper != null){
|
||||
//regenerate
|
||||
int startChar = enumToIntMapper.getStartPosition();
|
||||
int endChar = enumToIntMapper.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateEnumToIntMapperCode(target,enumConstNames), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateEnumToIntMapperCode(target,enumConstNames), lineNumber);
|
||||
}
|
||||
|
||||
//
|
||||
//mapper int->enum
|
||||
if(intToEnumMapper != null){
|
||||
//regenerate
|
||||
int startChar = intToEnumMapper.getStartPosition();
|
||||
int endChar = intToEnumMapper.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateIntToEnumMapperCode(target,enumConstNames), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateIntToEnumMapperCode(target,enumConstNames), lineNumber);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//
|
||||
//generate setter
|
||||
if(setter != null){
|
||||
//regenerate
|
||||
int startChar = setter.getStartPosition();
|
||||
int endChar = setter.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateSetterCode(targetFile,target.getName(),target), lineNumber);
|
||||
} else {
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateSetterCode(targetFile,target.getName(),target), lineNumber);
|
||||
}
|
||||
}
|
||||
//create network message parser for incoming requests to change synchronized variables
|
||||
//...
|
||||
|
||||
//message parser generation
|
||||
if(parseNetworkMessageFunction == null){
|
||||
//generate
|
||||
int positionJustBeforeClassEnd = targetFile.getSource().getEndPosition() - 1;
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), positionJustBeforeClassEnd);
|
||||
outputContent = Utilities.insertIntoSource(outputContent, generateParseBTreeMessages(targetFile, targets), lineNumber);
|
||||
} else {
|
||||
//regenerate
|
||||
int startChar = parseNetworkMessageFunction.getStartPosition();
|
||||
int endChar = parseNetworkMessageFunction.getEndPosition();
|
||||
outputContent = outputContent.delete(startChar, endChar);
|
||||
int lineNumber = Utilities.getLineNumber(outputContent.toString(), startChar);
|
||||
Utilities.insertIntoSource(outputContent, generateParseBTreeMessages(targetFile, targets), lineNumber);
|
||||
}
|
||||
|
||||
//
|
||||
//Replace file on disk
|
||||
Files.write(outputContent.toString().getBytes(), file);
|
||||
}
|
||||
|
||||
static List<NetcodeGenTarget> parseGenerationTargets(TargetFile targetFile){
|
||||
int targetIdIterator = 0;
|
||||
List<NetcodeGenTarget> targets = new LinkedList<NetcodeGenTarget>();
|
||||
for(FieldSource<JavaClassSource> field : targetFile.getSource().getFields()){
|
||||
List<AnnotationSource<JavaClassSource>> annotations = field.getAnnotations();
|
||||
for(AnnotationSource<JavaClassSource> annotation : annotations){
|
||||
if(annotation.getName().equals("SyncedField")){
|
||||
targets.add(new NetcodeGenTarget(targetIdIterator, field.getName(), field.getType().getName(), field, annotation));
|
||||
targetIdIterator++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return targets;
|
||||
}
|
||||
|
||||
static String getSetterName(String fieldName){
|
||||
return "set" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getEnumToIntMapperName(String fieldName){
|
||||
return "getEnumIntValue" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
static String getIntToEnumMapperName(String fieldName){
|
||||
return "getIntEnumValue" + Utilities.camelCase(fieldName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates code for a setter method for a synchronized field
|
||||
* @param targetFile The target file
|
||||
* @param variableName The name of the variable being set
|
||||
* @param target The target
|
||||
* @return The source code string
|
||||
*/
|
||||
static String generateSetterCode(TargetFile targetFile, String variableName, NetcodeGenTarget target){
|
||||
String messageConstructor = "";
|
||||
//
|
||||
//regular types
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/SetterInt.java"));
|
||||
} break;
|
||||
case "double": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/SetterDouble.java"));
|
||||
} break;
|
||||
case "float": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/SetterFloat.java"));
|
||||
} break;
|
||||
case "String": {
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/SetterString.java"));
|
||||
} break;
|
||||
}
|
||||
messageConstructor = messageConstructor
|
||||
.replace("REPLACEBTREEID",Main.bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
|
||||
//
|
||||
//enum type handling
|
||||
if(target.getAnnotation().getStringValue("isEnum") != null){
|
||||
messageConstructor = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/SetterEnum.java"))
|
||||
.replace("REPLACEBTREEID",Main.bTreeIdMap.get(targetFile.getName()) + "")
|
||||
.replace("REPLACEPROPERTYID",target.getId() + "")
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(variableName))
|
||||
.replace("REPLACENAMENOTCAMEL",variableName);
|
||||
}
|
||||
|
||||
//
|
||||
//returns setter string
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/Setter.java"))
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(variableName))
|
||||
.replace("REPLACETYPE",target.getTypeName())
|
||||
.replace("REPLACENAMENOTCAMEL",variableName)
|
||||
.replace("REPLACEMESSAGECONSTRUCTOR",messageConstructor);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
static String generateEnumToIntMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + enumConstName + ":\n" +
|
||||
"return " + i + ";\n";
|
||||
i++;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/EnumToIntMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()))
|
||||
.replace("REPLACETYPENAME",target.getTypeName());
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, switchCases, "REPLACECASE");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
|
||||
static String generateIntToEnumMapperCode(NetcodeGenTarget target, List<String> enumConstNames){
|
||||
String switchCases = "";
|
||||
int i = 0;
|
||||
for(String enumConstName : enumConstNames){
|
||||
switchCases = switchCases + "case " + i + ":\n" +
|
||||
"return " + target.getTypeName() + "." + enumConstName + ";\n";
|
||||
i++;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/IntToEnumMapper.java"))
|
||||
.replace("REPLACEENUMTYPE",target.getTypeName())
|
||||
.replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()));
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, switchCases, "REPLACECASE");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
static String generateParseBTreeMessages(TargetFile targetFile, List<NetcodeGenTarget> targets){
|
||||
String setCases = "";
|
||||
for(NetcodeGenTarget target : targets){
|
||||
String base = "case REPLACE_PROPERTY_ID: {\n" +
|
||||
" setREPLACE_VARIABLE_NAME_CAMEL(REPLACE_GET_PROPERTY_VALUE);\n" +
|
||||
"} break;\n";
|
||||
String propertyValueFetcher = "";
|
||||
switch(target.getTypeName()){
|
||||
case "int": {
|
||||
propertyValueFetcher = "message.getpropertyValueInt()";
|
||||
} break;
|
||||
case "float": {
|
||||
propertyValueFetcher = "message.getpropertyValueFloat()";
|
||||
} break;
|
||||
case "double": {
|
||||
propertyValueFetcher = "message.getpropertyValueDouble()";
|
||||
} break;
|
||||
case "String": {
|
||||
propertyValueFetcher = "message.getpropertyValueString()";
|
||||
} break;
|
||||
//enum
|
||||
default: {
|
||||
propertyValueFetcher = "getIntEnumValueREPLACENAMECAMEL(message.getpropertyValueInt())".replace("REPLACENAMECAMEL",Utilities.camelCase(target.getName()));
|
||||
} break;
|
||||
}
|
||||
String replaced = base
|
||||
.replace("REPLACE_PROPERTY_ID",target.getId() + "")
|
||||
.replace("REPLACE_GET_PROPERTY_VALUE",propertyValueFetcher)
|
||||
.replace("REPLACE_VARIABLE_NAME_CAMEL",Utilities.camelCase(target.getName()));
|
||||
// System.out.println(replaced);
|
||||
setCases = setCases + replaced;
|
||||
}
|
||||
String rVal = Utilities.readBakedResourceToString(Main.class.getResourceAsStream("/server/ParseBTreeMessages.java"));
|
||||
rVal = Utilities.replacePhraseWithPhraseAtIndent(rVal, setCases, "REPLACE_WITH_CASES");
|
||||
return rVal;
|
||||
}
|
||||
|
||||
}
|
||||
74
src/main/java/electrosphere/main/structure/BehaviorTree.java
Normal file
74
src/main/java/electrosphere/main/structure/BehaviorTree.java
Normal file
@ -0,0 +1,74 @@
|
||||
package electrosphere.main.structure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
|
||||
/**
|
||||
* Represents a behavior tree in the source code
|
||||
*/
|
||||
public class BehaviorTree {
|
||||
|
||||
//id assigned to the tree by this tool
|
||||
int id;
|
||||
|
||||
//The name of this tree
|
||||
String name;
|
||||
|
||||
//The name of the tree corresponding to this one
|
||||
String correspondingTreeName;
|
||||
|
||||
//If true, this is a server behavior tree, if false, it is not
|
||||
boolean isServer;
|
||||
|
||||
//The synchronized fields within this tree
|
||||
List<SynchronizedField> synchronizedFields;
|
||||
|
||||
//The file this type appears in
|
||||
TargetFile targetFile;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param id
|
||||
* @param name
|
||||
* @param correspondingTreeName
|
||||
* @param isServer
|
||||
* @param synchronizedFields
|
||||
*/
|
||||
public BehaviorTree(
|
||||
int id,
|
||||
String name,
|
||||
String correspondingTreeName,
|
||||
boolean isServer,
|
||||
List<SynchronizedField> synchronizedFields,
|
||||
TargetFile targetFile
|
||||
){
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.correspondingTreeName = correspondingTreeName;
|
||||
this.isServer = isServer;
|
||||
this.synchronizedFields = synchronizedFields;
|
||||
this.targetFile = targetFile;
|
||||
}
|
||||
|
||||
public String getName(){
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getCorrespondingTreeName(){
|
||||
return correspondingTreeName;
|
||||
}
|
||||
|
||||
public boolean isServer(){
|
||||
return isServer;
|
||||
}
|
||||
|
||||
public List<SynchronizedField> getSynchronizedFields(){
|
||||
return synchronizedFields;
|
||||
}
|
||||
|
||||
public TargetFile getTargetFile(){
|
||||
return targetFile;
|
||||
}
|
||||
|
||||
}
|
||||
111
src/main/java/electrosphere/main/structure/ProjectStructure.java
Normal file
111
src/main/java/electrosphere/main/structure/ProjectStructure.java
Normal file
@ -0,0 +1,111 @@
|
||||
package electrosphere.main.structure;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.forge.roaster.model.source.AnnotationSource;
|
||||
import org.jboss.forge.roaster.model.source.EnumConstantSource;
|
||||
import org.jboss.forge.roaster.model.source.FieldSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaEnumSource;
|
||||
import org.jboss.forge.roaster.model.source.JavaSource;
|
||||
import org.jboss.forge.roaster.model.source.MethodSource;
|
||||
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
import electrosphere.main.util.ClassSourceUtils;
|
||||
|
||||
/**
|
||||
* Container for overall structure of the project
|
||||
*/
|
||||
public class ProjectStructure {
|
||||
|
||||
|
||||
//The list of files to parse
|
||||
List<TargetFile> targetFiles;
|
||||
|
||||
//The list of behavior trees to synchronize
|
||||
List<BehaviorTree> behaviorTrees = new LinkedList<BehaviorTree>();
|
||||
|
||||
//All fields that are to be synchronized
|
||||
List<SynchronizedField> synchronizedFields = new LinkedList<SynchronizedField>();
|
||||
|
||||
//All types (enums) that can be synchronized
|
||||
List<SynchronizedType> synchronizedTypes = new LinkedList<SynchronizedType>();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param targetFiles The target files to iterate on
|
||||
*/
|
||||
public ProjectStructure(List<TargetFile> targetFiles){
|
||||
this.targetFiles = targetFiles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Parses the target files in this project into rich data to generate off of
|
||||
*/
|
||||
public void parseRichStructure(){
|
||||
int behaviorTreeIdIterator = 0;
|
||||
int synchronizedFieldIterator = 0;
|
||||
int typeIterator = 0;
|
||||
for(TargetFile target : targetFiles){
|
||||
AnnotationSource<JavaClassSource> mainAnnotation = target.getSource().getAnnotation("SynchronizedBehaviorTree");
|
||||
|
||||
String bTreeName = mainAnnotation.getStringValue("name");
|
||||
String bTreeCorrespondingName = mainAnnotation.getStringValue("correspondingTree");
|
||||
boolean isServer = Boolean.parseBoolean(mainAnnotation.getStringValue("isServer"));
|
||||
|
||||
|
||||
//parse sync'd fields
|
||||
List<SynchronizedField> syncedFields = new LinkedList<SynchronizedField>();
|
||||
for(FieldSource<JavaClassSource> fieldSource : target.getSource().getFields()){
|
||||
AnnotationSource<JavaClassSource> syncAnnotation = fieldSource.getAnnotation("SyncedField");
|
||||
if(syncAnnotation != null){
|
||||
String fieldName = fieldSource.getName();
|
||||
String typeName = fieldSource.getType().getName();
|
||||
SynchronizedField field = new SynchronizedField(synchronizedFieldIterator,fieldName,typeName,target);
|
||||
this.synchronizedFields.add(field);
|
||||
syncedFields.add(field);
|
||||
synchronizedFieldIterator++;
|
||||
}
|
||||
}
|
||||
|
||||
//parse sync'd enum types
|
||||
for(JavaSource<?> fieldSource : target.getSource().getNestedTypes()){
|
||||
if(fieldSource instanceof JavaEnumSource){
|
||||
JavaEnumSource enumSource = (JavaEnumSource)fieldSource;
|
||||
AnnotationSource<JavaEnumSource> annotation = enumSource.getAnnotation("SynchronizableEnum");
|
||||
if(annotation != null){
|
||||
List<String> values = new LinkedList<String>();
|
||||
for(EnumConstantSource constants: enumSource.getEnumConstants()){
|
||||
values.add(constants.getName());
|
||||
}
|
||||
SynchronizedType type = new SynchronizedType(typeIterator, enumSource.getName(), values,target);
|
||||
synchronizedTypes.add(type);
|
||||
typeIterator++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BehaviorTree newBehaviorTree = new BehaviorTree(behaviorTreeIdIterator,bTreeName,bTreeCorrespondingName,isServer,syncedFields,target);
|
||||
behaviorTrees.add(newBehaviorTree);
|
||||
behaviorTreeIdIterator++;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Main code generation routine
|
||||
*/
|
||||
public void generate(){
|
||||
//generate in-class functions for btrees
|
||||
for(BehaviorTree tree : behaviorTrees){
|
||||
if(tree.isServer){
|
||||
for(SynchronizedField field : tree.synchronizedFields){
|
||||
ClassSourceUtils.addOrReplaceMethod(this, tree, field.getSetterName(), field.getServerSetterContent());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package electrosphere.main.structure;
|
||||
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
import electrosphere.main.util.Utilities;
|
||||
|
||||
/**
|
||||
* Represents a field inside a behavior tree that should be synchronized
|
||||
*/
|
||||
public class SynchronizedField {
|
||||
|
||||
//The id assigned to the field by this tool
|
||||
int id;
|
||||
|
||||
//The name of the field
|
||||
String fieldName;
|
||||
|
||||
//The name of the type (int, short, String, etc, or the name of the enum eg IdleTreeState)
|
||||
String typeName;
|
||||
|
||||
//The file this type appears in
|
||||
TargetFile targetFile;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param id
|
||||
* @param fieldName
|
||||
* @param typeName
|
||||
*/
|
||||
public SynchronizedField(int id, String fieldName, String typeName, TargetFile targetFile){
|
||||
this.id = id;
|
||||
this.fieldName = fieldName;
|
||||
this.typeName = typeName;
|
||||
this.targetFile = targetFile;
|
||||
}
|
||||
|
||||
public String getSetterName(){
|
||||
String rVal = "";
|
||||
rVal = "set" + Utilities.camelCase(fieldName);
|
||||
return rVal;
|
||||
}
|
||||
|
||||
public String getServerSetterContent(){
|
||||
String rVal = "";
|
||||
return rVal;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package electrosphere.main.structure;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
|
||||
/**
|
||||
* An enum type that should have code generated for serializing/deserializing it
|
||||
*/
|
||||
public class SynchronizedType {
|
||||
|
||||
//The id assigned to the type by this tool
|
||||
int id;
|
||||
|
||||
//The name of the type
|
||||
String name;
|
||||
|
||||
//Names of values that the type can have
|
||||
List<String> values;
|
||||
|
||||
//The file this type appears in
|
||||
TargetFile targetFile;
|
||||
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param id
|
||||
* @param name
|
||||
* @param values
|
||||
*/
|
||||
public SynchronizedType(
|
||||
int id,
|
||||
String name,
|
||||
List<String> values,
|
||||
TargetFile targetFile
|
||||
){
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.values = values;
|
||||
this.targetFile = targetFile;
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,28 +1,76 @@
|
||||
package electrosphere.main.targets;
|
||||
|
||||
import org.jboss.forge.roaster.Roaster;
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
|
||||
/**
|
||||
* A file that will have netcode generation performed on it
|
||||
*/
|
||||
public class TargetFile {
|
||||
|
||||
//the path of the file
|
||||
String path;
|
||||
String content;
|
||||
//the original content of the file
|
||||
String originalContent;
|
||||
//The content, with any modifications made by the netcode generator
|
||||
String modifiedContent;
|
||||
//the Roaster parsed source for the file
|
||||
JavaClassSource source;
|
||||
//the name of the file
|
||||
String name;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
* @param path
|
||||
* @param content
|
||||
* @param name
|
||||
* @param source
|
||||
*/
|
||||
public TargetFile(String path, String content, String name, JavaClassSource source){
|
||||
this.path = path;
|
||||
this.content = content;
|
||||
this.originalContent = content;
|
||||
this.modifiedContent = content;
|
||||
this.name = name;
|
||||
this.source = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the source of the file
|
||||
* @return The source
|
||||
*/
|
||||
public JavaClassSource getSource(){
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of a file
|
||||
* @return The name
|
||||
*/
|
||||
public String getName(){
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the original content of the file
|
||||
* @return The content
|
||||
*/
|
||||
public String getOriginalContent(){
|
||||
return originalContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the modified content of the file
|
||||
* @return The modified content
|
||||
*/
|
||||
public String getModifiedContent(){
|
||||
return modifiedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the modified content of the file to provided string
|
||||
* @param content The new modified content of the file
|
||||
*/
|
||||
public void setModifiedContent(String content){
|
||||
this.modifiedContent = content;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
36
src/main/java/electrosphere/main/util/ClassSourceUtils.java
Normal file
36
src/main/java/electrosphere/main/util/ClassSourceUtils.java
Normal file
@ -0,0 +1,36 @@
|
||||
package electrosphere.main.util;
|
||||
|
||||
import org.jboss.forge.roaster.model.source.JavaClassSource;
|
||||
import org.jboss.forge.roaster.model.source.MethodSource;
|
||||
|
||||
import electrosphere.main.structure.BehaviorTree;
|
||||
import electrosphere.main.structure.ProjectStructure;
|
||||
import electrosphere.main.targets.TargetFile;
|
||||
|
||||
/**
|
||||
* Utilities for modifying the source code of a class
|
||||
*/
|
||||
public class ClassSourceUtils {
|
||||
|
||||
|
||||
/**
|
||||
* Adds to replaces a method in a given behavior tree
|
||||
* @param structure The project structure
|
||||
* @param tree The tree
|
||||
* @param methodName The name of the method
|
||||
* @param methodContent The content of the method that you want to insert or replace with
|
||||
*/
|
||||
public static void addOrReplaceMethod(ProjectStructure structure, BehaviorTree tree, String methodName, String methodContent){
|
||||
boolean hasMethod;
|
||||
//search for setter
|
||||
MethodSource<JavaClassSource> methodSource = tree.getTargetFile().getSource().getMethod(methodName);
|
||||
if(methodSource == null){
|
||||
|
||||
} else {
|
||||
ClassSourceUtils.addOrReplaceMethod(this, tree, field.getSetterName(), field.getServerSetterContent());
|
||||
System.out.println(new StringBuilder(tree.targetFile.getModifiedContent()).insert(setterMethod.getStartPosition(), "asdf").toString());
|
||||
// System.out.println(setterMethod.getStartPosition());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
199
src/main/java/electrosphere/main/util/Utilities.java
Normal file
199
src/main/java/electrosphere/main/util/Utilities.java
Normal file
@ -0,0 +1,199 @@
|
||||
package electrosphere.main.util;
|
||||
|
||||
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Utility functions
|
||||
*/
|
||||
public class Utilities {
|
||||
|
||||
//max attempts to read a file
|
||||
static final int maxReadFails = 3;
|
||||
//the timeout between read attempts
|
||||
static final int READ_TIMEOUT_DURATION = 5;
|
||||
/**
|
||||
* Reads a file as a string
|
||||
* @param resourceInputStream The resource input stream
|
||||
* @return The string
|
||||
*/
|
||||
public static String readBakedResourceToString(InputStream resourceInputStream){
|
||||
String rVal = "";
|
||||
BufferedReader reader;
|
||||
try {
|
||||
reader = new BufferedReader(new InputStreamReader(resourceInputStream));
|
||||
int failCounter = 0;
|
||||
boolean reading = true;
|
||||
StringBuilder builder = new StringBuilder("");
|
||||
while(reading){
|
||||
if(reader.ready()){
|
||||
failCounter = 0;
|
||||
int nextValue = reader.read();
|
||||
if(nextValue == -1){
|
||||
reading = false;
|
||||
} else {
|
||||
builder.append((char)nextValue);
|
||||
}
|
||||
} else {
|
||||
failCounter++;
|
||||
if(failCounter > maxReadFails){
|
||||
reading = false;
|
||||
} else {
|
||||
try {
|
||||
TimeUnit.MILLISECONDS.sleep(READ_TIMEOUT_DURATION);
|
||||
} catch (InterruptedException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
rVal = builder.toString();
|
||||
} catch (IOException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the input string in camel case format
|
||||
* @param input The input
|
||||
* @return The camel case string
|
||||
*/
|
||||
public static String camelCase(String input){
|
||||
return Character.toUpperCase(input.charAt(0)) + input.substring(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a block of text into a provided source file with correct indentation at a given line number
|
||||
* @param source The source file text
|
||||
* @param insertion The inserted block of code
|
||||
* @param lineNumber The line number
|
||||
* @return The combined string
|
||||
*/
|
||||
public static StringBuilder insertIntoSource(StringBuilder source, String insertion, int lineNumber){
|
||||
//find the position in the source string of the line number
|
||||
int newLinesEncountered = 0;
|
||||
int characterPosition = 0;
|
||||
while(newLinesEncountered < lineNumber - 1){
|
||||
if(source.charAt(characterPosition)=='\n'){
|
||||
newLinesEncountered++;
|
||||
}
|
||||
characterPosition++;
|
||||
}
|
||||
//the position to insert text at
|
||||
int characterToInsertAt = characterPosition;
|
||||
//now move backwards to find the previous line with content on it
|
||||
characterPosition = characterPosition - 1;
|
||||
while(true){
|
||||
if(source.charAt(characterPosition)=='\n' && source.charAt(characterPosition + 1)==' '){
|
||||
break;
|
||||
}
|
||||
characterPosition--;
|
||||
}
|
||||
//count the number of spaces between this newline and the next real character
|
||||
int numSpaces = 0;
|
||||
while(source.charAt(characterPosition)=='\n' || source.charAt(characterPosition)==' '){
|
||||
if(source.charAt(characterPosition)==' '){
|
||||
numSpaces++;
|
||||
}
|
||||
characterPosition++;
|
||||
}
|
||||
String spaceString = "";
|
||||
for(int i = 0; i < numSpaces; i++){
|
||||
spaceString = spaceString + " ";
|
||||
}
|
||||
//now modify insertion string to have the required number of spaces at the beginning of each line
|
||||
String[] lines = insertion.split("\n");
|
||||
for(int i = 0; i < lines.length; i++){
|
||||
lines[i] = spaceString + lines[i];
|
||||
}
|
||||
String spacedInsertion = "\n" + Arrays.stream(lines).collect(Collectors.joining("\n")) + "\n" + spaceString + "\n" + spaceString;
|
||||
System.out.println("\"" + spacedInsertion + "\"");
|
||||
//insert into source file
|
||||
source.insert(characterToInsertAt, spacedInsertion);
|
||||
//lastly, iterate after the function and see if there are a bunch of newlines and spaces (lots of white space)
|
||||
//if these is, cull it down to two newlines
|
||||
characterPosition = characterToInsertAt + spacedInsertion.length();
|
||||
int startPosition = characterPosition;
|
||||
while(true){
|
||||
if(source.charAt(characterPosition) != ' ' && source.charAt(characterPosition) != '\n' && source.charAt(characterPosition) != '\r'){
|
||||
break;
|
||||
}
|
||||
characterPosition++;
|
||||
}
|
||||
source.delete(startPosition, characterPosition);
|
||||
// actually return generated source
|
||||
return source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the line number of a given position in a source string
|
||||
* @param source The source string
|
||||
* @param position The character position
|
||||
* @return The line number
|
||||
*/
|
||||
public static int getLineNumber(String source, int position){
|
||||
int numberOfNewLines = 0;
|
||||
for(int i = 0; i < position; i++){
|
||||
if(source.charAt(i)=='\n'){
|
||||
numberOfNewLines++;
|
||||
}
|
||||
}
|
||||
return numberOfNewLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces a phrase in mainString with the insertionString while keeping the indentation of the phrase that is replaced.
|
||||
* @param mainString The main string to operate on
|
||||
* @param insertionString The string to be inserted into mainString
|
||||
* @param replacedPhrase The phrase to be replaced in mainString
|
||||
* @return mainString with the operation applied
|
||||
*/
|
||||
public static String replacePhraseWithPhraseAtIndent(String mainString, String insertionString, String replacedPhrase){
|
||||
StringBuilder rVal = new StringBuilder(mainString);
|
||||
while(true){
|
||||
Matcher m = Pattern.compile(replacedPhrase).matcher(rVal);
|
||||
if(!m.find()){
|
||||
break;
|
||||
}
|
||||
//delete replaced phrase found
|
||||
int startLocation = m.start();
|
||||
rVal.delete(startLocation, startLocation + replacedPhrase.length());
|
||||
//find number of spaces at location
|
||||
int numSpaces = 0;
|
||||
int charPosition = startLocation - 1;
|
||||
while(rVal.charAt(charPosition) == ' '){
|
||||
charPosition--;
|
||||
numSpaces++;
|
||||
}
|
||||
//create append space string
|
||||
String spaceString = "";
|
||||
for(int i = 0; i < numSpaces; i++){
|
||||
spaceString = spaceString + " ";
|
||||
}
|
||||
//append spaces to beginning of insertion string for this run
|
||||
String[] lines = insertionString.split("\n");
|
||||
for(int i = 0; i < lines.length; i++){
|
||||
lines[i] = spaceString + lines[i];
|
||||
}
|
||||
String spacedInsertion = "\n" + Arrays.stream(lines).collect(Collectors.joining("\n")) + "\n";
|
||||
//insert
|
||||
rVal.insert(startLocation, spacedInsertion);
|
||||
}
|
||||
|
||||
return rVal.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
private int getEnumIntValueREPLACENAMECAMEL(REPLACEENUMTYPE value){
|
||||
int rVal = -1;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
@ -1,3 +0,0 @@
|
||||
public REPLACETYPE getREPLACENAMECAMEL(){
|
||||
return REPLACENAMENOTCAMEL;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
private REPLACEENUMTYPE getEnumIntValueREPLACENAMECAMEL(int value){
|
||||
REPLACEENUMTYPE rVal = null;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
private void parseBTreeMessages(List<EntityMessage> messages){
|
||||
for(EntityMessage message : messages){
|
||||
switch(message.getpropertyID()){
|
||||
REPLACE_WITH_CASES
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){
|
||||
this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL;
|
||||
if(Globals.RUN_SERVER){
|
||||
//only server
|
||||
Globals.dataCellManager.sendNetworkMessageToChunk(
|
||||
REPLACEMESSAGECONSTRUCTOR,
|
||||
Globals.serverWorldData.convertRealToChunkSpace(position.x),
|
||||
Globals.serverWorldData.convertRealToChunkSpace(position.z)
|
||||
);
|
||||
if(Globals.RUN_CLIENT){
|
||||
//server and client so alert client entity that this value has been updated
|
||||
}
|
||||
} else if(Globals.RUN_CLIENT){
|
||||
//only client
|
||||
this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL;
|
||||
}
|
||||
}
|
||||
15
src/main/resources/client/EnumToIntMapper.java
Normal file
15
src/main/resources/client/EnumToIntMapper.java
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Maps the enum REPLACEENUMTYPE to an integer for networking operations.
|
||||
* </p>
|
||||
* @param value The value of REPLACEENUMTYPE to convert to an integer.
|
||||
* @return The corresponding integer value
|
||||
*/
|
||||
private int getEnumIntValueREPLACENAMECAMEL(REPLACEENUMTYPE value){
|
||||
int rVal = -1;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
15
src/main/resources/client/IntToEnumMapper.java
Normal file
15
src/main/resources/client/IntToEnumMapper.java
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Maps an integer to a REPLACEENUMTYPE value for networking operations.
|
||||
* </p>
|
||||
* @param value The integer to convert.
|
||||
* @return The corresponding REPLACEENUMTYPE value
|
||||
*/
|
||||
private REPLACEENUMTYPE getIntEnumValueREPLACENAMECAMEL(int value){
|
||||
REPLACEENUMTYPE rVal = null;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
16
src/main/resources/client/ParseBTreeMessages.java
Normal file
16
src/main/resources/client/ParseBTreeMessages.java
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Parses the network message queue for messages setting synchronized variables.
|
||||
* Then performs the synchronization logic on any found.
|
||||
* This should be called at the top of the simulation method.
|
||||
* </p>
|
||||
* @param messages The network message queue
|
||||
*/
|
||||
private void parseBTreeMessages(List<EntityMessage> messages){
|
||||
for(EntityMessage message : messages){
|
||||
switch(message.getpropertyID()){
|
||||
REPLACE_WITH_CASES
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/main/resources/client/Setter.java
Normal file
10
src/main/resources/client/Setter.java
Normal file
@ -0,0 +1,10 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Sets REPLACENAMENOTCAMEL and handles the synchronization logic for it.
|
||||
* </p>
|
||||
* @param REPLACENAMENOTCAMEL The value to set REPLACENAMENOTCAMEL to.
|
||||
*/
|
||||
public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){
|
||||
this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL;
|
||||
}
|
||||
15
src/main/resources/server/EnumToIntMapper.java
Normal file
15
src/main/resources/server/EnumToIntMapper.java
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Maps the enum REPLACEENUMTYPE to an integer for networking operations.
|
||||
* </p>
|
||||
* @param value The value of REPLACEENUMTYPE to convert to an integer.
|
||||
* @return The corresponding integer value
|
||||
*/
|
||||
private int getEnumIntValueREPLACENAMECAMEL(REPLACEENUMTYPE value){
|
||||
int rVal = -1;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
15
src/main/resources/server/IntToEnumMapper.java
Normal file
15
src/main/resources/server/IntToEnumMapper.java
Normal file
@ -0,0 +1,15 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Maps an integer to a REPLACEENUMTYPE value for networking operations.
|
||||
* </p>
|
||||
* @param value The integer to convert.
|
||||
* @return The corresponding REPLACEENUMTYPE value
|
||||
*/
|
||||
private REPLACEENUMTYPE getIntEnumValueREPLACENAMECAMEL(int value){
|
||||
REPLACEENUMTYPE rVal = null;
|
||||
switch(value){
|
||||
REPLACECASE
|
||||
}
|
||||
return rVal;
|
||||
}
|
||||
16
src/main/resources/server/ParseBTreeMessages.java
Normal file
16
src/main/resources/server/ParseBTreeMessages.java
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Parses the network message queue for messages setting synchronized variables.
|
||||
* Then performs the synchronization logic on any found.
|
||||
* This should be called at the top of the simulation method.
|
||||
* </p>
|
||||
* @param messages The network message queue
|
||||
*/
|
||||
private void parseBTreeMessages(List<EntityMessage> messages){
|
||||
for(EntityMessage message : messages){
|
||||
switch(message.getpropertyID()){
|
||||
REPLACE_WITH_CASES
|
||||
}
|
||||
}
|
||||
}
|
||||
13
src/main/resources/server/Setter.java
Normal file
13
src/main/resources/server/Setter.java
Normal file
@ -0,0 +1,13 @@
|
||||
/**
|
||||
* <p> Automatically generated </p>
|
||||
* <p>
|
||||
* Sets REPLACENAMENOTCAMEL and handles the synchronization logic for it.
|
||||
* </p>
|
||||
* @param REPLACENAMENOTCAMEL The value to set REPLACENAMENOTCAMEL to.
|
||||
*/
|
||||
public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){
|
||||
this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL;
|
||||
Globals.entityDataCellMapper.getEntityDataCell(parent).broadcastNetworkMessage(
|
||||
REPLACEMESSAGECONSTRUCTOR
|
||||
);
|
||||
}
|
||||
1
src/main/resources/server/SetterDouble.java
Normal file
1
src/main/resources/server/SetterDouble.java
Normal file
@ -0,0 +1 @@
|
||||
EntityMessage.constructsetBTreePropertyDoubleMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)
|
||||
1
src/main/resources/server/SetterEnum.java
Normal file
1
src/main/resources/server/SetterEnum.java
Normal file
@ -0,0 +1 @@
|
||||
EntityMessage.constructsetBTreePropertyEnumMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, getEnumIntValueREPLACENAMECAMEL(REPLACENAMENOTCAMEL))
|
||||
1
src/main/resources/server/SetterFloat.java
Normal file
1
src/main/resources/server/SetterFloat.java
Normal file
@ -0,0 +1 @@
|
||||
EntityMessage.constructsetBTreePropertyFloatMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)
|
||||
1
src/main/resources/server/SetterInt.java
Normal file
1
src/main/resources/server/SetterInt.java
Normal file
@ -0,0 +1 @@
|
||||
EntityMessage.constructsetBTreePropertyIntMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)
|
||||
1
src/main/resources/server/SetterString.java
Normal file
1
src/main/resources/server/SetterString.java
Normal file
@ -0,0 +1 @@
|
||||
EntityMessage.constructsetBTreePropertyStringMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)
|
||||
6
validationToBuild.md
Normal file
6
validationToBuild.md
Normal file
@ -0,0 +1,6 @@
|
||||
Validation to build into tool
|
||||
|
||||
[ ]Check that behavior trees have corresponding receiver trees
|
||||
[ ]Check that all synchronized fields are private
|
||||
[ ]If a synchronized field is an enum, check that both the client and server versions use the same enum type so that there isn't misalignment
|
||||
[ ]Check that all synchronized fields have corresponding fields on opposite tree
|
||||
50
walkthroughOfServerToClient.md
Normal file
50
walkthroughOfServerToClient.md
Normal file
@ -0,0 +1,50 @@
|
||||
======server=======
|
||||
Server idle tree has single field to synchronize
|
||||
|
||||
Code gen creates a setter for the field
|
||||
|
||||
Within the setter, when the value is set, it creates a packet to update the value and submits it
|
||||
How does the code gen tool know how to convert the value? How does the code gen tool know how to submit it?
|
||||
|
||||
For enums, code gen tool creates a function in the behavior tree that converts values into shorts and attaches that value to the packet
|
||||
|
||||
The code gen tool is given a class name and method name for a method that will take a server packet and submit it over the network
|
||||
Specifically, the server function takes an entity and a behavior tree update packet
|
||||
|
||||
Once this packet is sent, it arrives on client
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
======client=======
|
||||
Client receives a behavior tree update packet
|
||||
|
||||
client sends this packet to a behavior tree synchronization manager
|
||||
|
||||
the manager finds the client with the correct id
|
||||
|
||||
it then does a lookup against a table generated by the code gen tool
|
||||
the table points behavior tree id to class
|
||||
it gets the entity data string for the entity pointing to the behavior tree object
|
||||
once it finds this object, it updates the value in the object to be the new value
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
whole files generated:
|
||||
* file containing the whole lookup function that takes behavior tree id number and a raw object, and returns the object cast as the correct class
|
||||
|
||||
|
||||
functions generated:
|
||||
* Within server behavior tree
|
||||
- Setter that submits the packet
|
||||
- Optionally, function to convert enum to short
|
||||
* Within client behavior tree
|
||||
- Optionally, function to convert short to enum
|
||||
Loading…
Reference in New Issue
Block a user