diff --git a/casestodesignfor b/casestodesignfor new file mode 100644 index 0000000..730dfcb --- /dev/null +++ b/casestodesignfor @@ -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 \ No newline at end of file diff --git a/goals b/goals new file mode 100644 index 0000000..d644a1c --- /dev/null +++ b/goals @@ -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') \ No newline at end of file diff --git a/src/main/java/electrosphere/main/Main.java b/src/main/java/electrosphere/main/Main.java index c81d4e5..5b89536 100644 --- a/src/main/java/electrosphere/main/Main.java +++ b/src/main/java/electrosphere/main/Main.java @@ -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 bTreeIdMap = new HashMap(); + public static Map bTreeIdMap = new HashMap(); public static void main(String args[]){ + List 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 getFilesToModify(String topLevelFolderPath){ + List rVal = new LinkedList(); File topLevelFolder = new File(topLevelFolderPath); List fileQueue = new LinkedList(); List fileQueueOpenSet = new LinkedList(); @@ -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 targets = parseGenerationTargets(targetFile); - for(NetcodeGenTarget target : targets){ - //check if has getter - MethodSource getter = null; - MethodSource setter = null; - MethodSource enumToIntMapper = null; - MethodSource intToEnumMapper = null; - for(MethodSource 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 enumSource = null; - for(JavaSource nestedSource : targetFile.getSource().getNestedTypes()){ - if(nestedSource.getName().equals(target.getTypeName())){ - enumSource = (JavaSource)nestedSource; - break; - } - } - List enumConstNames = new LinkedList(); - 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 parseGenerationTargets(TargetFile targetFile){ - int targetIdIterator = 0; - List targets = new LinkedList(); - for(FieldSource field : targetFile.getSource().getFields()){ - List> annotations = field.getAnnotations(); - for(AnnotationSource 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 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 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 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); - } - - } diff --git a/src/main/java/electrosphere/main/Utilities.java b/src/main/java/electrosphere/main/Utilities.java deleted file mode 100644 index 747b5b3..0000000 --- a/src/main/java/electrosphere/main/Utilities.java +++ /dev/null @@ -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; - } - - -} diff --git a/src/main/java/electrosphere/main/codegen/ClientGen.java b/src/main/java/electrosphere/main/codegen/ClientGen.java new file mode 100644 index 0000000..0f73328 --- /dev/null +++ b/src/main/java/electrosphere/main/codegen/ClientGen.java @@ -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 targets = parseGenerationTargets(targetFile); + // + //Find if there was already a parse network message function + MethodSource parseNetworkMessageFunction = null; + for(MethodSource 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 setter = null; + MethodSource enumToIntMapper = null; + MethodSource intToEnumMapper = null; + for(MethodSource 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 enumSource = null; + for(JavaSource nestedSource : targetFile.getSource().getNestedTypes()){ + if(nestedSource.getName().equals(target.getTypeName())){ + enumSource = (JavaSource)nestedSource; + break; + } + } + List enumConstNames = new LinkedList(); + 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 parseGenerationTargets(TargetFile targetFile){ + int targetIdIterator = 0; + List targets = new LinkedList(); + for(FieldSource field : targetFile.getSource().getFields()){ + List> annotations = field.getAnnotations(); + for(AnnotationSource 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 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 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 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; + } + +} diff --git a/src/main/java/electrosphere/main/codegen/CodeGen.java b/src/main/java/electrosphere/main/codegen/CodeGen.java new file mode 100644 index 0000000..f3dee47 --- /dev/null +++ b/src/main/java/electrosphere/main/codegen/CodeGen.java @@ -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 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); + } + } + +} diff --git a/src/main/java/electrosphere/main/codegen/ServerGen.java b/src/main/java/electrosphere/main/codegen/ServerGen.java new file mode 100644 index 0000000..6b7dddb --- /dev/null +++ b/src/main/java/electrosphere/main/codegen/ServerGen.java @@ -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 targets = parseGenerationTargets(targetFile); + // + //Find if there was already a parse network message function + MethodSource parseNetworkMessageFunction = null; + for(MethodSource 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 setter = null; + MethodSource enumToIntMapper = null; + MethodSource intToEnumMapper = null; + for(MethodSource 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 enumSource = null; + for(JavaSource nestedSource : targetFile.getSource().getNestedTypes()){ + if(nestedSource.getName().equals(target.getTypeName())){ + enumSource = (JavaSource)nestedSource; + break; + } + } + List enumConstNames = new LinkedList(); + 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 parseGenerationTargets(TargetFile targetFile){ + int targetIdIterator = 0; + List targets = new LinkedList(); + for(FieldSource field : targetFile.getSource().getFields()){ + List> annotations = field.getAnnotations(); + for(AnnotationSource 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 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 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 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; + } + +} diff --git a/src/main/java/electrosphere/main/structure/BehaviorTree.java b/src/main/java/electrosphere/main/structure/BehaviorTree.java new file mode 100644 index 0000000..62a0163 --- /dev/null +++ b/src/main/java/electrosphere/main/structure/BehaviorTree.java @@ -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 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 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 getSynchronizedFields(){ + return synchronizedFields; + } + + public TargetFile getTargetFile(){ + return targetFile; + } + +} diff --git a/src/main/java/electrosphere/main/structure/ProjectStructure.java b/src/main/java/electrosphere/main/structure/ProjectStructure.java new file mode 100644 index 0000000..842913f --- /dev/null +++ b/src/main/java/electrosphere/main/structure/ProjectStructure.java @@ -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 targetFiles; + + //The list of behavior trees to synchronize + List behaviorTrees = new LinkedList(); + + //All fields that are to be synchronized + List synchronizedFields = new LinkedList(); + + //All types (enums) that can be synchronized + List synchronizedTypes = new LinkedList(); + + /** + * Constructor + * @param targetFiles The target files to iterate on + */ + public ProjectStructure(List 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 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 syncedFields = new LinkedList(); + for(FieldSource fieldSource : target.getSource().getFields()){ + AnnotationSource 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 annotation = enumSource.getAnnotation("SynchronizableEnum"); + if(annotation != null){ + List values = new LinkedList(); + 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()); + } + } + } + } + +} diff --git a/src/main/java/electrosphere/main/structure/SynchronizedField.java b/src/main/java/electrosphere/main/structure/SynchronizedField.java new file mode 100644 index 0000000..b218209 --- /dev/null +++ b/src/main/java/electrosphere/main/structure/SynchronizedField.java @@ -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; + } + +} diff --git a/src/main/java/electrosphere/main/structure/SynchronizedType.java b/src/main/java/electrosphere/main/structure/SynchronizedType.java new file mode 100644 index 0000000..06ed6da --- /dev/null +++ b/src/main/java/electrosphere/main/structure/SynchronizedType.java @@ -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 values; + + //The file this type appears in + TargetFile targetFile; + + + /** + * Constructor + * @param id + * @param name + * @param values + */ + public SynchronizedType( + int id, + String name, + List values, + TargetFile targetFile + ){ + this.id = id; + this.name = name; + this.values = values; + this.targetFile = targetFile; + } + +} diff --git a/src/main/java/electrosphere/main/targets/TargetFile.java b/src/main/java/electrosphere/main/targets/TargetFile.java index 36b41cf..dac0952 100644 --- a/src/main/java/electrosphere/main/targets/TargetFile.java +++ b/src/main/java/electrosphere/main/targets/TargetFile.java @@ -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; + } + } diff --git a/src/main/java/electrosphere/main/util/ClassSourceUtils.java b/src/main/java/electrosphere/main/util/ClassSourceUtils.java new file mode 100644 index 0000000..61896be --- /dev/null +++ b/src/main/java/electrosphere/main/util/ClassSourceUtils.java @@ -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 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()); + } + } + +} diff --git a/src/main/java/electrosphere/main/util/Utilities.java b/src/main/java/electrosphere/main/util/Utilities.java new file mode 100644 index 0000000..06eb7c8 --- /dev/null +++ b/src/main/java/electrosphere/main/util/Utilities.java @@ -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(); + } + + +} diff --git a/src/main/resources/EnumToIntMapper.java b/src/main/resources/EnumToIntMapper.java deleted file mode 100644 index aa26852..0000000 --- a/src/main/resources/EnumToIntMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -private int getEnumIntValueREPLACENAMECAMEL(REPLACEENUMTYPE value){ - int rVal = -1; - switch(value){ - REPLACECASE - } - return rVal; -} \ No newline at end of file diff --git a/src/main/resources/Getter.java b/src/main/resources/Getter.java deleted file mode 100644 index eb1a822..0000000 --- a/src/main/resources/Getter.java +++ /dev/null @@ -1,3 +0,0 @@ -public REPLACETYPE getREPLACENAMECAMEL(){ - return REPLACENAMENOTCAMEL; -} \ No newline at end of file diff --git a/src/main/resources/IntToEnumMapper.java b/src/main/resources/IntToEnumMapper.java deleted file mode 100644 index 4ec1d67..0000000 --- a/src/main/resources/IntToEnumMapper.java +++ /dev/null @@ -1,7 +0,0 @@ -private REPLACEENUMTYPE getEnumIntValueREPLACENAMECAMEL(int value){ - REPLACEENUMTYPE rVal = null; - switch(value){ - REPLACECASE - } - return rVal; -} \ No newline at end of file diff --git a/src/main/resources/ParseBTreeMessages.java b/src/main/resources/ParseBTreeMessages.java deleted file mode 100644 index f81e2c9..0000000 --- a/src/main/resources/ParseBTreeMessages.java +++ /dev/null @@ -1,7 +0,0 @@ -private void parseBTreeMessages(List messages){ - for(EntityMessage message : messages){ - switch(message.getpropertyID()){ - REPLACE_WITH_CASES - } - } -} \ No newline at end of file diff --git a/src/main/resources/Setter.java b/src/main/resources/Setter.java deleted file mode 100644 index 957f3c9..0000000 --- a/src/main/resources/Setter.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/main/resources/client/EnumToIntMapper.java b/src/main/resources/client/EnumToIntMapper.java new file mode 100644 index 0000000..bea8143 --- /dev/null +++ b/src/main/resources/client/EnumToIntMapper.java @@ -0,0 +1,15 @@ +/** + *

Automatically generated

+ *

+ * Maps the enum REPLACEENUMTYPE to an integer for networking operations. + *

+ * @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; +} \ No newline at end of file diff --git a/src/main/resources/client/IntToEnumMapper.java b/src/main/resources/client/IntToEnumMapper.java new file mode 100644 index 0000000..9b2dc9b --- /dev/null +++ b/src/main/resources/client/IntToEnumMapper.java @@ -0,0 +1,15 @@ +/** + *

Automatically generated

+ *

+ * Maps an integer to a REPLACEENUMTYPE value for networking operations. + *

+ * @param value The integer to convert. + * @return The corresponding REPLACEENUMTYPE value + */ +private REPLACEENUMTYPE getIntEnumValueREPLACENAMECAMEL(int value){ + REPLACEENUMTYPE rVal = null; + switch(value){ + REPLACECASE + } + return rVal; +} \ No newline at end of file diff --git a/src/main/resources/client/ParseBTreeMessages.java b/src/main/resources/client/ParseBTreeMessages.java new file mode 100644 index 0000000..ff52416 --- /dev/null +++ b/src/main/resources/client/ParseBTreeMessages.java @@ -0,0 +1,16 @@ +/** + *

Automatically generated

+ *

+ * 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. + *

+ * @param messages The network message queue + */ +private void parseBTreeMessages(List messages){ + for(EntityMessage message : messages){ + switch(message.getpropertyID()){ + REPLACE_WITH_CASES + } + } +} \ No newline at end of file diff --git a/src/main/resources/client/Setter.java b/src/main/resources/client/Setter.java new file mode 100644 index 0000000..a41bbd8 --- /dev/null +++ b/src/main/resources/client/Setter.java @@ -0,0 +1,10 @@ +/** + *

Automatically generated

+ *

+ * Sets REPLACENAMENOTCAMEL and handles the synchronization logic for it. + *

+ * @param REPLACENAMENOTCAMEL The value to set REPLACENAMENOTCAMEL to. + */ +public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){ + this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL; +} \ No newline at end of file diff --git a/src/main/resources/SetterDouble.java b/src/main/resources/client/SetterDouble.java similarity index 100% rename from src/main/resources/SetterDouble.java rename to src/main/resources/client/SetterDouble.java diff --git a/src/main/resources/SetterEnum.java b/src/main/resources/client/SetterEnum.java similarity index 100% rename from src/main/resources/SetterEnum.java rename to src/main/resources/client/SetterEnum.java diff --git a/src/main/resources/SetterFloat.java b/src/main/resources/client/SetterFloat.java similarity index 100% rename from src/main/resources/SetterFloat.java rename to src/main/resources/client/SetterFloat.java diff --git a/src/main/resources/SetterInt.java b/src/main/resources/client/SetterInt.java similarity index 100% rename from src/main/resources/SetterInt.java rename to src/main/resources/client/SetterInt.java diff --git a/src/main/resources/SetterString.java b/src/main/resources/client/SetterString.java similarity index 100% rename from src/main/resources/SetterString.java rename to src/main/resources/client/SetterString.java diff --git a/src/main/resources/server/EnumToIntMapper.java b/src/main/resources/server/EnumToIntMapper.java new file mode 100644 index 0000000..bea8143 --- /dev/null +++ b/src/main/resources/server/EnumToIntMapper.java @@ -0,0 +1,15 @@ +/** + *

Automatically generated

+ *

+ * Maps the enum REPLACEENUMTYPE to an integer for networking operations. + *

+ * @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; +} \ No newline at end of file diff --git a/src/main/resources/server/IntToEnumMapper.java b/src/main/resources/server/IntToEnumMapper.java new file mode 100644 index 0000000..9b2dc9b --- /dev/null +++ b/src/main/resources/server/IntToEnumMapper.java @@ -0,0 +1,15 @@ +/** + *

Automatically generated

+ *

+ * Maps an integer to a REPLACEENUMTYPE value for networking operations. + *

+ * @param value The integer to convert. + * @return The corresponding REPLACEENUMTYPE value + */ +private REPLACEENUMTYPE getIntEnumValueREPLACENAMECAMEL(int value){ + REPLACEENUMTYPE rVal = null; + switch(value){ + REPLACECASE + } + return rVal; +} \ No newline at end of file diff --git a/src/main/resources/server/ParseBTreeMessages.java b/src/main/resources/server/ParseBTreeMessages.java new file mode 100644 index 0000000..ff52416 --- /dev/null +++ b/src/main/resources/server/ParseBTreeMessages.java @@ -0,0 +1,16 @@ +/** + *

Automatically generated

+ *

+ * 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. + *

+ * @param messages The network message queue + */ +private void parseBTreeMessages(List messages){ + for(EntityMessage message : messages){ + switch(message.getpropertyID()){ + REPLACE_WITH_CASES + } + } +} \ No newline at end of file diff --git a/src/main/resources/server/Setter.java b/src/main/resources/server/Setter.java new file mode 100644 index 0000000..ef2cf4f --- /dev/null +++ b/src/main/resources/server/Setter.java @@ -0,0 +1,13 @@ +/** + *

Automatically generated

+ *

+ * Sets REPLACENAMENOTCAMEL and handles the synchronization logic for it. + *

+ * @param REPLACENAMENOTCAMEL The value to set REPLACENAMENOTCAMEL to. + */ +public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){ + this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL; + Globals.entityDataCellMapper.getEntityDataCell(parent).broadcastNetworkMessage( + REPLACEMESSAGECONSTRUCTOR + ); +} \ No newline at end of file diff --git a/src/main/resources/server/SetterDouble.java b/src/main/resources/server/SetterDouble.java new file mode 100644 index 0000000..8cddfcd --- /dev/null +++ b/src/main/resources/server/SetterDouble.java @@ -0,0 +1 @@ +EntityMessage.constructsetBTreePropertyDoubleMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL) \ No newline at end of file diff --git a/src/main/resources/server/SetterEnum.java b/src/main/resources/server/SetterEnum.java new file mode 100644 index 0000000..bb7dd8c --- /dev/null +++ b/src/main/resources/server/SetterEnum.java @@ -0,0 +1 @@ +EntityMessage.constructsetBTreePropertyEnumMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, getEnumIntValueREPLACENAMECAMEL(REPLACENAMENOTCAMEL)) \ No newline at end of file diff --git a/src/main/resources/server/SetterFloat.java b/src/main/resources/server/SetterFloat.java new file mode 100644 index 0000000..1bd80b0 --- /dev/null +++ b/src/main/resources/server/SetterFloat.java @@ -0,0 +1 @@ +EntityMessage.constructsetBTreePropertyFloatMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL) \ No newline at end of file diff --git a/src/main/resources/server/SetterInt.java b/src/main/resources/server/SetterInt.java new file mode 100644 index 0000000..94da8b2 --- /dev/null +++ b/src/main/resources/server/SetterInt.java @@ -0,0 +1 @@ +EntityMessage.constructsetBTreePropertyIntMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL) \ No newline at end of file diff --git a/src/main/resources/server/SetterString.java b/src/main/resources/server/SetterString.java new file mode 100644 index 0000000..bc8ee76 --- /dev/null +++ b/src/main/resources/server/SetterString.java @@ -0,0 +1 @@ +EntityMessage.constructsetBTreePropertyStringMessage(parent.getId(), Main.getCurrentFrame(), REPLACEBTREEID, REPLACEPROPERTYID, REPLACENAMENOTCAMEL) \ No newline at end of file diff --git a/validationToBuild.md b/validationToBuild.md new file mode 100644 index 0000000..0ae7556 --- /dev/null +++ b/validationToBuild.md @@ -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 \ No newline at end of file diff --git a/walkthroughOfServerToClient.md b/walkthroughOfServerToClient.md new file mode 100644 index 0000000..d1c984c --- /dev/null +++ b/walkthroughOfServerToClient.md @@ -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 \ No newline at end of file