work on gen

This commit is contained in:
unknown 2023-12-21 22:15:35 -05:00
parent 2a0225abdf
commit 7e66acab76
39 changed files with 1491 additions and 403 deletions

26
casestodesignfor Normal file
View 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
View 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')

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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;
}
}

View 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);
}
}
}

View 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;
}
}

View 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;
}
}

View 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());
}
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View 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());
}
}
}

View 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();
}
}

View File

@ -1,7 +0,0 @@
private int getEnumIntValueREPLACENAMECAMEL(REPLACEENUMTYPE value){
int rVal = -1;
switch(value){
REPLACECASE
}
return rVal;
}

View File

@ -1,3 +0,0 @@
public REPLACETYPE getREPLACENAMECAMEL(){
return REPLACENAMENOTCAMEL;
}

View File

@ -1,7 +0,0 @@
private REPLACEENUMTYPE getEnumIntValueREPLACENAMECAMEL(int value){
REPLACEENUMTYPE rVal = null;
switch(value){
REPLACECASE
}
return rVal;
}

View File

@ -1,7 +0,0 @@
private void parseBTreeMessages(List<EntityMessage> messages){
for(EntityMessage message : messages){
switch(message.getpropertyID()){
REPLACE_WITH_CASES
}
}
}

View File

@ -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;
}
}

View 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;
}

View 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;
}

View 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
}
}
}

View 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;
}

View 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;
}

View 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;
}

View 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
}
}
}

View 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
);
}

View File

@ -0,0 +1 @@
EntityMessage.constructsetBTreePropertyDoubleMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)

View File

@ -0,0 +1 @@
EntityMessage.constructsetBTreePropertyEnumMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, getEnumIntValueREPLACENAMECAMEL(REPLACENAMENOTCAMEL))

View File

@ -0,0 +1 @@
EntityMessage.constructsetBTreePropertyFloatMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)

View File

@ -0,0 +1 @@
EntityMessage.constructsetBTreePropertyIntMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)

View File

@ -0,0 +1 @@
EntityMessage.constructsetBTreePropertyStringMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL)

6
validationToBuild.md Normal file
View 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

View 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