Initial server->client full flow implementation

This commit is contained in:
unknown 2024-02-24 21:38:05 -05:00
parent e8dc3265d3
commit 9f1e5e9ae6
34 changed files with 775 additions and 112 deletions

1
.gitignore vendored
View File

@ -6,3 +6,4 @@
/.classpath
/.project
/.vscode
/dependency-reduced-pom.xml

27
pom.xml
View File

@ -34,25 +34,22 @@
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.0.0</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>electrosphere.main.Main</mainClass>
</manifest>
</archive>
</configuration>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase> <!-- bind to the packaging phase -->
<phase>package</phase>
<goals>
<goal>single</goal>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>electrosphere.main.Main</mainClass>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>

View File

@ -19,19 +19,25 @@ import electrosphere.main.targets.TargetFile;
*/
public class Main {
static String topLevelFolderPathWindows = "C:\\Users\\satellite\\Documents\\Renderer\\src\\main\\java\\electrosphere";
static String topLevelFolderPathMac = "/Users/satellite/p/Renderer/src/main/java/electrosphere";
public static String topLevelFolderPathWindows = "C:\\Users\\satellite\\Documents\\Renderer";
static String topLevelFolderPathMac = "/Users/satellite/p/Renderer";
public static int bTreeIterator = 0;
//hard-coded white list files to always parse
static final String[] ALWAYS_PARSED_CLASSES = new String[]{
"ClientSynchronizationManager"
};
//maps the btree annotation "name" to an id
public static Map<String,Integer> bTreeIdMap = new HashMap<String,Integer>();
public static void main(String args[]){
List<TargetFile> targets = getFilesToModify(topLevelFolderPathMac);
ProjectStructure structure = new ProjectStructure(targets);
List<TargetFile> targets = getFilesToModify(topLevelFolderPathWindows + "/src/main/java/electrosphere");
ProjectStructure structure = new ProjectStructure(targets, new File(topLevelFolderPathWindows));
structure.parseRichStructure();
structure.generate();
structure.writeFiles();
}
/**
@ -39,7 +45,7 @@ public class Main {
* @param content
* @return
*/
static JavaClassSource parseJavaFile(String content){
public static JavaClassSource parseJavaFile(String content){
return Roaster.parse(JavaClassSource.class, content);
}
@ -66,7 +72,10 @@ public class Main {
String content = "";
try {
content = Files.readString(currentFile.toPath());
if(content.contains("@SynchronizedBehaviorTree")){
if(
content.contains("@SynchronizedBehaviorTree") ||
isHardCodedAlwaysParse(currentFile.getName())
){
//parse
JavaClassSource source = parseJavaFile(content);
rVal.add(new TargetFile(currentFile.getAbsolutePath(), content, currentFile.getName(), source));
@ -91,4 +100,18 @@ public class Main {
return rVal;
}
/**
* Checks if a given file is a hard coded case to parse
* @param fullFileName The full file name
* @return true if should parse, false otherwise
*/
private static boolean isHardCodedAlwaysParse(String fullFileName){
for(String hardCodedCase : ALWAYS_PARSED_CLASSES){
if(fullFileName.contains(hardCodedCase)){
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,74 @@
package electrosphere.main.structure;
import java.io.File;
import java.io.IOException;
import com.google.common.io.Files;
import electrosphere.main.util.ClassSourceUtils;
/**
* A class that is a list of every behavior tree and its associated id
*/
public class BTreeIdEnum {
//The class name of this class
public static final String CLASSNAME = "BehaviorTreeIdEnums";
//The location of the file
public static final String LOCATION = "src/main/java/electrosphere/net/synchronization/BehaviorTreeIdEnums.java";
/**
* Gets the qualified path for the b tree id enum
* @param structure The project structure
* @return The qualified path
*/
public static String getQualifiedPath(ProjectStructure structure){
File outFile = new File(structure.getRootFolder().getAbsolutePath()).toPath().resolve(LOCATION).toFile();
return ClassSourceUtils.getPackageSourcePath(structure, outFile) + "." + CLASSNAME;
}
/**
* Gets the import declaration for this class
* @param structure The project structure
* @return The import declaration
*/
public static String getImport(ProjectStructure structure){
return "import " + getQualifiedPath(structure) + ";";
}
/**
* Generates a b tree id enum file
* @param structure The project structure
*/
public static void generate(ProjectStructure structure){
String source = "";
File outFile = new File(structure.getRootFolder().getAbsolutePath()).toPath().resolve(LOCATION).toFile();
source = source + ClassSourceUtils.getPackageDeclaration(structure, ClassSourceUtils.getPackageSourcePath(structure, outFile));
source = source + "\n";
source = source + ClassSourceUtils.generateClassHeader("BehaviorTreeIdEnums", "List of enums for each automatically synchronized behavior tree.", true);
source = source + "\n";
for(BehaviorTree tree : structure.behaviorTrees){
source = source + " public static final int " + getTreeIdEnum(tree) + " = " + tree.id + ";\n";
}
source = source + "\n";
source = source + ClassSourceUtils.generateClassEnd();
try {
Files.write(source.getBytes(), outFile);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Gets the enum name for this behavior tree
* @param tree the tree
* @return the name
*/
public static String getTreeIdEnum(BehaviorTree tree){
return "BTREE_" + tree.name.toUpperCase() + "_ID";
}
}

View File

@ -3,6 +3,7 @@ package electrosphere.main.structure;
import java.util.List;
import electrosphere.main.targets.TargetFile;
import electrosphere.main.util.TemplateInjectionUtils;
import electrosphere.main.util.Utilities;
/**
@ -16,6 +17,9 @@ public class BehaviorTree {
//The name of this tree
String name;
//the name of the class containing this tree
String className;
//The name of the tree corresponding to this one
String correspondingTreeName;
@ -32,6 +36,7 @@ public class BehaviorTree {
* Constructor
* @param id
* @param name
* @param className
* @param correspondingTreeName
* @param isServer
* @param synchronizedFields
@ -39,6 +44,7 @@ public class BehaviorTree {
public BehaviorTree(
int id,
String name,
String className,
String correspondingTreeName,
boolean isServer,
List<SynchronizedField> synchronizedFields,
@ -46,6 +52,7 @@ public class BehaviorTree {
){
this.id = id;
this.name = name;
this.className = className;
this.correspondingTreeName = correspondingTreeName;
this.isServer = isServer;
this.synchronizedFields = synchronizedFields;
@ -56,6 +63,10 @@ public class BehaviorTree {
return name;
}
public String getClassName(){
return className;
}
public String getCorrespondingTreeName(){
return correspondingTreeName;
}
@ -77,23 +88,41 @@ public class BehaviorTree {
*/
public String getAttachMethodName(){
String rVal = "";
rVal = "attach" + Utilities.camelCase(name);
rVal = "attachTree";
return rVal;
}
public String getAttachMethodContent(){
String rVal = "";
public String getAttachMethodContent(boolean isServer){
String templateSource = "/server/AttachBTree.java";
if(!isServer){
templateSource = "/client/AttachBTree.java";
}
String rVal = TemplateInjectionUtils.getFragmentWithReplacement(templateSource, this.name.toUpperCase(), "TREE_" + this.name.toUpperCase(), className);
return rVal;
}
public String getDetachMethodName(){
String rVal = "";
rVal = "detach" + Utilities.camelCase(name);
rVal = "detachTree";
return rVal;
}
public String getDetachMethodContent(){
String rVal = "";
public String getDetachMethodContent(boolean isServer){
String templateSource = "/server/DetachBTree.java";
if(!isServer){
templateSource = "/client/DetachBTree.java";
}
String rVal = TemplateInjectionUtils.getFragmentWithReplacement(templateSource, this.name.toUpperCase());
return rVal;
}
public String getEntityFetchMethodName(){
String rVal = "get" + className;
return rVal;
}
public String getEntityFetchMethodContent(){
String rVal = TemplateInjectionUtils.getFragmentWithReplacement("/btree/EntityFetchMethod.java", this.className, "TREE_" + this.name.toUpperCase());
return rVal;
}

View File

@ -0,0 +1,61 @@
package electrosphere.main.structure;
import electrosphere.main.targets.TargetFile;
import electrosphere.main.util.ClassSourceUtils;
import electrosphere.main.util.TemplateInjectionUtils;
public class ClientSynchronizationManager {
//The class name of this class
public static final String CLASSNAME = "ClientSynchronizationManager";
//The location of the file
public static final String LOCATION = "src/main/java/electrosphere/net/synchronization/ClientSynchronizationManager.java";
/**
* Updates the ClientSynchronizationManager with latest data in specific functions
* @param structure
* @param clientSynchronizationManager
*/
public static void update(ProjectStructure structure, TargetFile clientSynchronizationManager){
String updateCases = "";
for(BehaviorTree tree : structure.behaviorTrees){
//counterintuitively, want to only update client for server behavior tree ids
if(tree.isServer()){
BehaviorTree clientEquivalent = structure.getTree(tree.correspondingTreeName);
updateCases = updateCases + " case BehaviorTreeIdEnums." + BTreeIdEnum.getTreeIdEnum(tree) + ": {\n";
updateCases = updateCases + " switch(message.getfieldId()){\n";
for(SynchronizedField field : tree.synchronizedFields){
String treeName = clientEquivalent.getClassName();
switch(field.typeName){
case "int":
case "long":
case "float":
case "double":
case "String":
{
throw new UnsupportedOperationException("Trying to parse type that is not supported yet");
}
default: {
SynchronizedType type = structure.getType(field.typeName);
String typeClass = type.getTargetFile().getSource().getName();
updateCases = updateCases + " case " + field.id + ":{\n";
updateCases = updateCases + " " + treeName + " tree = " + treeName + ".get" + treeName + "(entity);\n";
updateCases = updateCases + " tree." + field.getSetterName() + "(" + typeClass + "." + type.getFromShortConversionMethodName() + "((short)message.getbTreeValue()));\n";
updateCases = updateCases + " } break;\n";
} break;
}
}
updateCases = updateCases + " }\n";
updateCases = updateCases + " } break;\n";
//guarantee import
ClassSourceUtils.importClass(structure, clientSynchronizationManager, tree.getTargetFile().getQualifiedPath());
}
}
String fullReplacementText = TemplateInjectionUtils.getFragmentWithReplacement("/client/UpdateEntityState.java", updateCases);
ClassSourceUtils.addOrReplaceMethod(structure, clientSynchronizationManager, "updateEntityState", fullReplacementText);
}
}

View File

@ -1,5 +1,6 @@
package electrosphere.main.structure;
import java.io.File;
import java.util.LinkedList;
import java.util.List;
@ -18,6 +19,9 @@ import electrosphere.main.util.ClassSourceUtils;
*/
public class ProjectStructure {
//The root folder of the project
File rootFolder;
//The list of files to parse
List<TargetFile> targetFiles;
@ -31,12 +35,17 @@ public class ProjectStructure {
//All types (enums) that can be synchronized
List<SynchronizedType> synchronizedTypes = new LinkedList<SynchronizedType>();
//the client synchronization manager
TargetFile clientSynchronizationManager;
/**
* Constructor
* @param targetFiles The target files to iterate on
* @param rootFolder The root folder of the project
*/
public ProjectStructure(List<TargetFile> targetFiles){
public ProjectStructure(List<TargetFile> targetFiles, File rootFolder){
this.targetFiles = targetFiles;
this.rootFolder = rootFolder;
}
@ -50,6 +59,8 @@ public class ProjectStructure {
for(TargetFile target : targetFiles){
AnnotationSource<JavaClassSource> mainAnnotation = target.getSource().getAnnotation("SynchronizedBehaviorTree");
//parse btrees
if(mainAnnotation != null){
String bTreeName = mainAnnotation.getStringValue("name");
String bTreeCorrespondingName = mainAnnotation.getStringValue("correspondingTree");
boolean isServer = Boolean.parseBoolean(mainAnnotation.getStringValue("isServer"));
@ -86,9 +97,31 @@ public class ProjectStructure {
}
}
BehaviorTree newBehaviorTree = new BehaviorTree(behaviorTreeIdIterator,bTreeName,bTreeCorrespondingName,isServer,syncedFields,target);
BehaviorTree newBehaviorTree = new BehaviorTree(
behaviorTreeIdIterator,
bTreeName,
target.getSource().getName(),
bTreeCorrespondingName,
isServer,
syncedFields,
target
);
behaviorTrees.add(newBehaviorTree);
behaviorTreeIdIterator++;
//push behavior tree to each field
for(SynchronizedField field : syncedFields){
field.setParent(newBehaviorTree);
}
}
//
//specific cases
//client synchronization manager
if(target.getName().contains("ClientSynchronizationManager")){
this.clientSynchronizationManager = target;
}
}
}
@ -99,15 +132,84 @@ public class ProjectStructure {
public void generate(){
//generate in-class functions for btrees
for(BehaviorTree tree : behaviorTrees){
//server side getter + setter
if(tree.isServer){
for(SynchronizedField field : tree.synchronizedFields){
ClassSourceUtils.addOrReplaceMethod(this, tree, field.getGetterName(), field.getServerGetterContent());
ClassSourceUtils.addOrReplaceMethod(this, tree, field.getSetterName(), field.getServerSetterContent());
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), field.getGetterName(), field.getServerGetterContent());
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), field.getSetterName(), field.getServerSetterContent(this));
ClassSourceUtils.importClass(this, tree.getTargetFile(), "electrosphere.server.datacell.utils.DataCellSearchUtils");
ClassSourceUtils.importClass(this, tree.getTargetFile(), "electrosphere.net.parser.net.message.SynchronizationMessage");
}
ClassSourceUtils.addOrReplaceMethod(this, tree, tree.getAttachMethodName(), tree.getAttachMethodContent());
ClassSourceUtils.addOrReplaceMethod(this, tree, tree.getDetachMethodName(), tree.getDetachMethodContent());
//attach + detatch methods
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), tree.getAttachMethodName(), tree.getAttachMethodContent(true));
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), tree.getDetachMethodName(), tree.getDetachMethodContent(true));
} else {
//attach + detatch methods
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), tree.getAttachMethodName(), tree.getAttachMethodContent(false));
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), tree.getDetachMethodName(), tree.getDetachMethodContent(false));
}
//guarantee entity fetch method
ClassSourceUtils.addOrReplaceMethod(this, tree.getTargetFile(), tree.getEntityFetchMethodName(), tree.getEntityFetchMethodContent());
//guarantee imports of required files (btree enum, etc)
ClassSourceUtils.importClass(this, tree.getTargetFile(), BTreeIdEnum.getQualifiedPath(this));
}
//generate enums for all synchronized types
for(SynchronizedType type : synchronizedTypes){
ClassSourceUtils.addOrReplaceMethod(this, type.getTargetFile(), type.getToShortConversionMethodName(), type.getToShortConversionMethodContent());
ClassSourceUtils.addOrReplaceMethod(this, type.getTargetFile(), type.getFromShortConversionMethodName(), type.getFromShortConversionMethodContent());
}
BTreeIdEnum.generate(this);
//client sync manager
ClientSynchronizationManager.update(this, clientSynchronizationManager);
}
/**
* Writes all files that had content change to disk
*/
public void writeFiles(){
for(TargetFile file : this.targetFiles){
if(file.isModified()){
file.write();
}
}
}
/**
* Gets the root folder as a file object
* @return The root folder
*/
public File getRootFolder(){
return this.rootFolder;
}
/**
* Gets a synchronized type from a name
* @param typeName The name of the type
* @return The synchronized type if it exists, null otherwise
*/
public SynchronizedType getType(String typeName){
for(SynchronizedType type : this.synchronizedTypes){
if(type.name.equals(typeName)){
return type;
}
}
return null;
}
/**
* Gets a behavior tree from its name
* @param typeName The name of the tree
* @return The tree if it exists, null otherwise
*/
public BehaviorTree getTree(String typeName){
for(BehaviorTree tree : this.behaviorTrees){
if(tree.name.equals(typeName)){
return tree;
}
}
return null;
}
}

View File

@ -1,6 +1,7 @@
package electrosphere.main.structure;
import electrosphere.main.targets.TargetFile;
import electrosphere.main.util.TemplateInjectionUtils;
import electrosphere.main.util.Utilities;
/**
@ -20,11 +21,16 @@ public class SynchronizedField {
//The file this type appears in
TargetFile targetFile;
//The parent behavior tree
BehaviorTree parent;
/**
* Constructor
* @param id
* @param parent
* @param fieldName
* @param typeName
* @param targetFile
*/
public SynchronizedField(int id, String fieldName, String typeName, TargetFile targetFile){
this.id = id;
@ -40,7 +46,7 @@ public class SynchronizedField {
}
public String getServerGetterContent(){
String rVal = "";
String rVal = TemplateInjectionUtils.getFragmentWithReplacement("/server/Getter.java", fieldName, Utilities.camelCase(fieldName), typeName);
return rVal;
}
@ -50,8 +56,19 @@ public class SynchronizedField {
return rVal;
}
public String getServerSetterContent(){
String rVal = "";
/**
* Gets parent tree of this field
* @param tree
*/
public void setParent(BehaviorTree tree){
this.parent = tree;
}
public String getServerSetterContent(ProjectStructure structure){
SynchronizedType type = structure.getType(typeName);
String packetContentFiller = " int value = " + type.getContainingClassName() + "." + type.getToShortConversionMethodName() + "(state);\n";
packetContentFiller = packetContentFiller + " DataCellSearchUtils.getEntityDataCell(parent).broadcastNetworkMessage(SynchronizationMessage.constructUpdateClientStateMessage(parent.getId(), " + parent.id + ", " + id + ", value));";
String rVal = TemplateInjectionUtils.getFragmentWithReplacement("/server/Setter.java", fieldName, Utilities.camelCase(fieldName), typeName, packetContentFiller);
return rVal;
}

View File

@ -3,6 +3,8 @@ package electrosphere.main.structure;
import java.util.List;
import electrosphere.main.targets.TargetFile;
import electrosphere.main.util.TemplateInjectionUtils;
import electrosphere.main.util.Utilities;
/**
* An enum type that should have code generated for serializing/deserializing it
@ -40,4 +42,77 @@ public class SynchronizedType {
this.targetFile = targetFile;
}
/**
* Gets the target file
* @return The target file
*/
public TargetFile getTargetFile(){
return this.targetFile;
}
/**
* Gets the name of the function that converts this synchronized type TO a short
* @return The name
*/
public String getToShortConversionMethodName(){
return "get" + Utilities.camelCase(name) + "EnumAsShort";
}
/**
* Gets the content of the function that converts this synchronized type TO a short
* @return The content
*/
public String getToShortConversionMethodContent(){
//get enum switch array
String enumSwitchArray = "";
int i = 0;
for(String value : values){
enumSwitchArray = enumSwitchArray + " case " + value + ":\n";
enumSwitchArray = enumSwitchArray + " return " + i + ";\n";
i++;
}
//chop off last newline
enumSwitchArray = enumSwitchArray.substring(0, enumSwitchArray.length() - 1);
String rVal = TemplateInjectionUtils.getFragmentWithReplacement("/enum/EnumToShort.java", this.name, this.name, enumSwitchArray);
return rVal;
}
/**
* Gets the name of the function that converts this synchronized type FROM a short
* @return The name
*/
public String getFromShortConversionMethodName(){
return "get" + Utilities.camelCase(name) + "ShortAsEnum";
}
/**
* Gets the content of the function that converts this synchronized type TO a short
* @return The content
*/
public String getFromShortConversionMethodContent(){
//get enum switch array
String enumSwitchArray = "";
int i = 0;
for(String value : values){
enumSwitchArray = enumSwitchArray + " case " + i + ":\n";
enumSwitchArray = enumSwitchArray + " return " + this.name + "." + value + ";\n";
i++;
}
//add default case
enumSwitchArray = enumSwitchArray + " default:\n";
enumSwitchArray = enumSwitchArray + " return " + this.name + "." + values.get(0) + ";\n";
//chop off last newline
enumSwitchArray = enumSwitchArray.substring(0, enumSwitchArray.length() - 1);
String rVal = TemplateInjectionUtils.getFragmentWithReplacement("/enum/ShortToEnum.java", this.name, this.name, enumSwitchArray);
return rVal;
}
/**
* Gets the name of the class containing the declaration of this type
* @return
*/
public String getContainingClassName(){
return getTargetFile().getName().substring(0, getTargetFile().getName().length() - 5);
}
}

View File

@ -1,7 +1,14 @@
package electrosphere.main.targets;
import java.io.File;
import java.io.IOException;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import com.google.common.io.Files;
import electrosphere.main.Main;
/**
* A file that will have netcode generation performed on it
*/
@ -12,12 +19,15 @@ public class TargetFile {
//the original content of the file
String originalContent;
//The content, with any modifications made by the netcode generator
String modifiedContent;
StringBuilder modifiedContent;
//the Roaster parsed source for the file
JavaClassSource source;
//the name of the file
String name;
//holds whether the file was edited
boolean modified = false;
/**
* Constructor
* @param path
@ -28,7 +38,7 @@ public class TargetFile {
public TargetFile(String path, String content, String name, JavaClassSource source){
this.path = path;
this.originalContent = content;
this.modifiedContent = content;
this.modifiedContent = new StringBuilder(content);
this.name = name;
this.source = source;
}
@ -61,7 +71,7 @@ public class TargetFile {
* Gets the modified content of the file
* @return The modified content
*/
public String getModifiedContent(){
public StringBuilder getModifiedContent(){
return modifiedContent;
}
@ -70,7 +80,51 @@ public class TargetFile {
* @param content The new modified content of the file
*/
public void setModifiedContent(String content){
this.modifiedContent = content;
this.setModified(true);
this.modifiedContent = new StringBuilder(content);
}
/**
* Re-parses the modified content
*/
public void reparse(){
this.source = Main.parseJavaFile(this.modifiedContent.toString());
}
/**
* Sets the modified state of the file
* @param modified true if modified, false otherwise
*/
private void setModified(boolean modified){
this.modified = modified;
}
/**
* Gets the modified state of the file
* @returntrue if modified, false otherwise
*/
public boolean isModified(){
return this.modified;
}
/**
* Writes the modified content of the file to disk
*/
public void write(){
try {
Files.write(this.modifiedContent.toString().getBytes(), new File(this.path));
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets the qualified path for this file
* @return The qualified path
*/
public String getQualifiedPath(){
String rVal = this.source.getQualifiedName();
return rVal;
}
}

View File

@ -1,48 +1,103 @@
package electrosphere.main.util;
import java.io.File;
import java.util.List;
import org.jboss.forge.roaster.model.source.Import;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.jboss.forge.roaster.model.source.MethodSource;
import electrosphere.main.structure.BTreeIdEnum;
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 {
//The default indent for a method, field, etc
static final int DEFAULT_INDENT = 4;
/**
* Adds to replaces a method in a given behavior tree
* @param structure The project structure
* @param tree The tree
* @param targetFile The targetFile
* @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 {
throw new UnsupportedOperationException();
// 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());
public static void addOrReplaceMethod(ProjectStructure structure, TargetFile targetFile, String methodName, String methodContent){
//search for method
List<MethodSource<JavaClassSource>> methods = targetFile.getSource().getMethods();
MethodSource<JavaClassSource> methodSource = null;
for(MethodSource<JavaClassSource> methodRaw : methods){
if(methodRaw.getName().equals(methodName)){
if(methodSource != null){
throw new UnknownError("Two methods in a source file have the same name. This is unsupported by the addOrReplaceMethod function at this time!");
}
methodSource = methodRaw;
}
}
StringBuilder modifiedContent = targetFile.getModifiedContent();
if(methodSource == null){
int endOfClass = findEndOfClass(modifiedContent.toString());
String indentedFragment = TemplateInjectionUtils.indentFragment(methodContent, DEFAULT_INDENT);
//must newline
String finalInsertion = indentedFragment + "\n";
modifiedContent.insert(endOfClass, finalInsertion);
} else {
//methodStart is literally the first character of the function's name
int methodStart = methodSource.getStartPosition();
int indent = getCurrentIndent(modifiedContent.toString(),methodStart - 1);
int methodEnd = methodSource.getEndPosition();
String indentedFragment = TemplateInjectionUtils.indentFragment(methodContent, indent).trim();
modifiedContent.replace(methodStart, methodEnd, indentedFragment);
}
targetFile.setModifiedContent(modifiedContent.toString());
targetFile.reparse();
}
/**
* Finds the end of a class file
* @param sourceRaw The raw source
* @return The index of the final curly brace
*/
private static int findEndOfClass(String sourceRaw){
int curr = sourceRaw.length() - 1;
while(sourceRaw.charAt(curr) != '}'){
curr--;
}
return curr;
}
/**
* Gets the current indent of the source file at a given location
* @param sourceRaw The source file
* @param position The position
* @return The indent
*/
private static int getCurrentIndent(String sourceRaw, int position){
int indent = 0;
int curr = position;
while(sourceRaw.charAt(curr) != '\n'){
curr--;
indent++;
}
return indent;
}
/**
* Generates the first part of a class declaration file
* @param name The name of the class
* @param description The description of the class
* @param isPublic True if public, false otherwise
* @return The string containing the class header
*/
public static String generateClassHeader(String name, boolean isPublic){
String rVal = "\t";
public static String generateClassHeader(String name, String description, boolean isPublic){
String rVal = "/**\n * " + description + "\n */\n";
rVal = rVal + "";
rVal = rVal + (isPublic ? "public " : "");
rVal = rVal + "class " + name + " {\n";
return rVal;
@ -54,9 +109,31 @@ public class ClassSourceUtils {
* @param fileObj The raw java file object
* @return The package declaration
*/
public static String getPackageSourcePath(File fileObj){
public static String getPackageSourcePath(ProjectStructure structure, File fileObj){
String rVal = "";
throw new UnsupportedOperationException();
rVal = structure.getRootFolder().toURI().relativize(fileObj.toURI()).getPath();
//delete past last forwardslash
int lastSlashIndex = rVal.length() - 1;
while(rVal.charAt(lastSlashIndex) != '/'){
lastSlashIndex--;
}
rVal = rVal.substring(0, lastSlashIndex);
//delete intro part
rVal = rVal.replace("src/main/java/", "");
rVal = rVal.replace("/",".");
return rVal;
}
/**
* Gets the package declaration for a given package path
* @param structure The project structure
* @param packagePath The package path
* @return The declaration line
*/
public static String getPackageDeclaration(ProjectStructure structure, String packagePath){
String rVal = "";
rVal = "package " + packagePath + ";\n";
return rVal;
}
/**
@ -67,4 +144,26 @@ public class ClassSourceUtils {
return "}\n";
}
/**
* Guarantees that a given path is imported
* @param structure The project structure
* @param tree The tree
* @param qualifiedPath The qualified name to import
*/
public static void importClass(ProjectStructure structure, TargetFile targetFile, String qualifiedPath){
StringBuilder modifiedContent = targetFile.getModifiedContent();
List<Import> imports = targetFile.getSource().getImports();
boolean present = false;
for(Import currentImport : imports){
String importName = currentImport.getQualifiedName();
if(importName.equals(qualifiedPath)){
present = true;
}
}
if(!present){
Utilities.insertIntoSource(modifiedContent, "import " + qualifiedPath + ";", 3);
targetFile.setModifiedContent(modifiedContent.toString());
}
}
}

View File

@ -2,6 +2,9 @@ package electrosphere.main.util;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import com.sun.tools.javac.Main;
/**
* Tools for taking files that represent source file fragments and injecting strings to replace portions of the fragment
@ -9,21 +12,37 @@ import java.util.List;
public class TemplateInjectionUtils {
/**
* Loads a fragment from a source file and replaces all strings with provided ones
* Loads a fragment from a source file and replaces all strings with provided ones. Used specifically for filling out template files baked into this tool.
* @param sourceFile The source file relative path
* @param replaceStrings The list of strings to replace
* @return The properly updated string
*/
public static String getFragmentWithReplacement(String sourceFile, String ... replaceStrings){
String rVal = "";
String rVal = Utilities.readBakedResourceToString(TemplateInjectionUtils.class.getResourceAsStream(sourceFile));
List<String> replaceStringList = Arrays.asList(replaceStrings);
int iterator = 0;
for(String replaceString : replaceStringList){
rVal = rVal.replace(iterator + "REPLACE_ME", replaceString);
rVal = rVal.replace("REPLACE_" + iterator + "_ME", replaceString);
iterator++;
}
return rVal;
}
/**
* Indents a fragment based on an indentation amount
* @param fragment The fragment
* @param indentAmount The indentation required (in spaces)
* @return The indented fragment
*/
public static String indentFragment(String fragment, int indentAmount){
return Arrays.asList(fragment.split("\n")).stream().map((String line) -> {
String indent = "";
for(int i = 0; i < indentAmount; i++){
indent = indent + " ";
}
return indent + line;
}).collect(Collectors.joining("\n"));
}
}

View File

@ -96,7 +96,7 @@ public class Utilities {
//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)==' '){
if(source.charAt(characterPosition)=='\n' && source.charAt(characterPosition + 1)!='\n'){
break;
}
characterPosition--;

View File

@ -0,0 +1,10 @@
/**
* <p>
* Gets the REPLACE_0_ME of the entity
* </p>
* @param entity the entity
* @return The REPLACE_0_ME
*/
public static REPLACE_0_ME getREPLACE_0_ME(Entity entity){
return (REPLACE_0_ME)entity.getData(EntityDataStrings.REPLACE_1_ME);
}

View File

@ -0,0 +1,20 @@
/**
* <p> (initially) Automatically generated </p>
* <p> More parameters can be safely added to this method</p>
* <p>
* Attaches this tree to the entity.
* </p>
* @param entity The entity to attach to
* @param tree The behavior tree to attach
*/
public static REPLACE_2_ME attachTree(Entity parent){
REPLACE_2_ME rVal = new REPLACE_2_ME(parent);
//put manual code here (setting params, etc)
//!!WARNING!! from here below should not be touched
//This was generated automatically to properly alert various systems that the btree exists and should be tracked
parent.putData(EntityDataStrings.REPLACE_1_ME, rVal);
Globals.entityValueTrackingService.attachTreeToEntity(parent, BehaviorTreeIdEnums.BTREE_REPLACE_0_ME_ID);
return rVal;
}

View File

@ -0,0 +1,11 @@
/**
* <p> Automatically generated </p>
* <p>
* Detatches this tree from the entity.
* </p>
* @param entity The entity to detach to
* @param tree The behavior tree to detach
*/
public static void detachTree(Entity entity, BehaviorTree tree){
Globals.entityValueTrackingService.detatchTreeFromEntity(entity, BehaviorTreeIdEnums.BTREE_REPLACE_0_ME_ID);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,14 @@
/**
* <p> Automatically generated </p>
* <p>
* Updates the state of a given behavior tree on a given entity
* </p>
* @param entity The entity
* @param bTreeId The id of the behavior tree
* @param message The raw synchronization message holding the update data
*/
private void updateEntityState(Entity entity, int bTreeId, SynchronizationMessage message){
switch(bTreeId){
REPLACE_0_ME
}
}

View File

@ -0,0 +1,15 @@
/**
* <p> Automatically generated </p>
* <p>
* Converts this enum type to an equivalent short value
* </p>
* @param enumVal The enum value
* @return The short value
*/
public static short getREPLACE_0_MEEnumAsShort(REPLACE_1_ME enumVal){
switch(enumVal){
REPLACE_2_ME
default:
return 0;
}
}

View File

@ -0,0 +1,13 @@
/**
* <p> Automatically generated </p>
* <p>
* Converts a short to the equivalent enum value
* </p>
* @param shortVal The short value
* @return The enum value
*/
public static REPLACE_1_ME getREPLACE_0_MEShortAsEnum(short shortVal){
switch(shortVal){
REPLACE_2_ME
}
}

View File

@ -0,0 +1,21 @@
/**
* <p> (initially) Automatically generated </p>
* <p> More parameters can be safely added to this method</p>
* <p>
* Attaches this tree to the entity.
* </p>
* @param entity The entity to attach to
* @param tree The behavior tree to attach
*/
public static REPLACE_2_ME attachTree(Entity parent){
REPLACE_2_ME rVal = new REPLACE_2_ME(parent);
//put manual code here (setting params, etc)
//!!WARNING!! from here below should not be touched
//This was generated automatically to properly alert various systems that the btree exists and should be tracked
ServerBehaviorTreeUtils.attachBTreeToEntity(parent, rVal);
parent.putData(EntityDataStrings.REPLACE_1_ME, rVal);
Globals.entityValueTrackingService.attachTreeToEntity(parent, BehaviorTreeIdEnums.BTREE_REPLACE_0_ME_ID);
return rVal;
}

View File

@ -0,0 +1,11 @@
/**
* <p> Automatically generated </p>
* <p>
* Detatches this tree from the entity.
* </p>
* @param entity The entity to detach to
* @param tree The behavior tree to detach
*/
public static void detachTree(Entity entity, BehaviorTree tree){
Globals.entityValueTrackingService.detatchTreeFromEntity(entity, BehaviorTreeIdEnums.BTREE_REPLACE_0_ME_ID);
}

View File

@ -0,0 +1,9 @@
/**
* <p> Automatically generated </p>
* <p>
* Gets REPLACE_0_ME.
* </p>
*/
public REPLACE_2_ME getREPLACE_1_ME(){
return REPLACE_0_ME;
}

View File

@ -1,13 +1,11 @@
/**
* <p> Automatically generated </p>
* <p>
* Sets REPLACENAMENOTCAMEL and handles the synchronization logic for it.
* Sets REPLACE_0_ME and handles the synchronization logic for it.
* </p>
* @param REPLACENAMENOTCAMEL The value to set REPLACENAMENOTCAMEL to.
* @param REPLACE_0_ME The value to set REPLACE_0_ME to.
*/
public void setREPLACENAMECAMEL(REPLACETYPE REPLACENAMENOTCAMEL){
this.REPLACENAMENOTCAMEL = REPLACENAMENOTCAMEL;
Globals.entityDataCellMapper.getEntityDataCell(parent).broadcastNetworkMessage(
REPLACEMESSAGECONSTRUCTOR
);
public void setREPLACE_1_ME(REPLACE_2_ME REPLACE_0_ME){
this.REPLACE_0_ME = REPLACE_0_ME;
REPLACE_3_ME
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -24,7 +24,7 @@ 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
the manager finds the entity 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