I can't test it yet because the API is full of errors because of the refactoringfeature/refactor.v3
@@ -0,0 +1,65 @@ | |||
using System; | |||
using System.CodeDom; | |||
using System.Collections.Generic; | |||
using System.Linq; | |||
using System.Linq.Expressions; | |||
using System.Reflection; | |||
using Gamecraft.Tweaks; | |||
using Svelto.ECS; | |||
namespace CodeGenerator | |||
{ | |||
public class ECSAnalyzer | |||
{ | |||
public static ECSClassInfo AnalyzeEntityDescriptor(Type entityDescriptorType) | |||
{ | |||
// TODO: Add support for creating/deleting entities (getting an up to date server/client engines root) | |||
var templateType = typeof(EntityDescriptorTemplate<>).MakeGenericType(entityDescriptorType); | |||
var getTemplateClass = Expression.Constant(templateType); | |||
var getDescriptorExpr = Expression.PropertyOrField(getTemplateClass, "descriptor"); | |||
var getTemplateDescriptorExpr = | |||
Expression.Lambda<Func<IEntityDescriptor>>(getDescriptorExpr); | |||
var getTemplateDescriptor = getTemplateDescriptorExpr.Compile(); | |||
var builders = getTemplateDescriptor().componentsToBuild; | |||
return new ECSClassInfo | |||
{ | |||
Name = entityDescriptorType.Name.Replace("EntityComponent", "").Replace("EntityStruct", ""), | |||
Properties = builders.Select(builder => builder.GetEntityComponentType()).SelectMany(AnalyzeFields).ToArray() | |||
}; | |||
} | |||
private static ECSPropertyInfo[] AnalyzeFields(Type componentType) | |||
{ | |||
bool useReflection = componentType.IsNotPublic; | |||
var result = new List<ECSPropertyInfo>(); | |||
foreach (var field in componentType.GetFields()) | |||
{ | |||
var attr = field.GetCustomAttribute<TweakableStatAttribute>(); | |||
string propName = field.Name; | |||
if (attr != null) | |||
propName = attr.propertyName; | |||
propName = char.ToUpper(propName[0]) + propName[1..]; | |||
if (useReflection) | |||
{ | |||
result.Add(new ECSReflectedPropertyInfo | |||
{ | |||
Name = propName, | |||
Type = field.FieldType, | |||
OriginalClassName = componentType.FullName, | |||
ComponentType = componentType | |||
}); | |||
} | |||
result.Add(new ECSPropertyInfo | |||
{ | |||
Name = propName, | |||
Type = field.FieldType, | |||
ComponentType = componentType | |||
}); | |||
} | |||
return result.ToArray(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,129 @@ | |||
using System; | |||
using System.CodeDom; | |||
using System.CodeDom.Compiler; | |||
using System.IO; | |||
using System.Linq; | |||
namespace CodeGenerator | |||
{ | |||
public static class ECSClassGenerator | |||
{ | |||
public static void Generate(Type entityDescriptorType) | |||
{ | |||
var info = ECSAnalyzer.AnalyzeEntityDescriptor(entityDescriptorType); | |||
var codeUnit = new CodeCompileUnit(); | |||
var ns = new CodeNamespace("TechbloxModdingAPI.Blocks"); | |||
ns.Imports.Add(new CodeNamespaceImport("RobocraftX.Common")); | |||
ns.Imports.Add(new CodeNamespaceImport("Svelto.ECS")); | |||
var cl = new CodeTypeDeclaration(info.Name); | |||
cl.BaseTypes.Add(new CodeTypeReference("SignalingBlock")); | |||
cl.Members.Add(new CodeConstructor | |||
{ | |||
Parameters = {new CodeParameterDeclarationExpression("EGID", "egid")}, | |||
Comments = | |||
{ | |||
_start, | |||
new CodeCommentStatement($"Constructs a(n) {info.Name} object representing an existing block.", true), | |||
_end | |||
}, | |||
BaseConstructorArgs = {new CodeVariableReferenceExpression("egid")}, | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final | |||
}); | |||
foreach (var propertyInfo in info.Properties) | |||
{ | |||
if (propertyInfo is ECSReflectedPropertyInfo reflectedPropertyInfo) | |||
GenerateReflectedProperty(reflectedPropertyInfo, cl); | |||
else | |||
GenerateProperty(propertyInfo, cl); | |||
} | |||
ns.Types.Add(cl); | |||
codeUnit.Namespaces.Add(ns); | |||
var provider = CodeDomProvider.CreateProvider("CSharp"); | |||
var path = $"../../../../TechbloxModdingAPI/Blocks/{info.Name}.cs"; | |||
using (var sw = new StreamWriter(path)) | |||
{ | |||
provider.GenerateCodeFromCompileUnit(codeUnit, sw, new CodeGeneratorOptions {BracingStyle = "C"}); | |||
} | |||
File.WriteAllLines(path, | |||
File.ReadAllLines(path).SkipWhile(line => line.StartsWith("//") || line.Length == 0)); | |||
} | |||
private static void GenerateProperty(ECSPropertyInfo info, CodeTypeDeclaration cl) | |||
{ | |||
var getStruct = new CodeMethodInvokeExpression( | |||
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), | |||
"GetBlockInfo", new CodeTypeReference(info.ComponentType)), | |||
new CodeThisReferenceExpression()); | |||
CodeExpression structFieldReference = new CodeFieldReferenceExpression(getStruct, info.Name); | |||
cl.Members.Add(new CodeMemberProperty | |||
{ | |||
Name = info.Name, | |||
HasGet = true, | |||
HasSet = true, | |||
GetStatements = | |||
{ | |||
new CodeMethodReturnStatement(structFieldReference) | |||
}, | |||
SetStatements = | |||
{ | |||
new CodeAssignStatement(structFieldReference, new CodePropertySetValueReferenceExpression()) | |||
}, | |||
Type = new CodeTypeReference(info.Type), | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final, | |||
Comments = | |||
{ | |||
_start, | |||
new CodeCommentStatement($"Gets or sets the {info.ComponentType.Name}'s {info.Name} property." + | |||
" May not be saved.", // TODO: Doesn't know if tweakable stat | |||
true), | |||
_end | |||
} | |||
}); | |||
} | |||
private static void GenerateReflectedProperty(ECSReflectedPropertyInfo info, CodeTypeDeclaration cl) | |||
{ | |||
var reflectedType = new CodeSnippetExpression($"HarmonyLib.AccessTools.TypeByName(\"{info.OriginalClassName}\")"); | |||
CodeExpression reflectedGet = new CodeCastExpression(info.Type, new CodeMethodInvokeExpression( | |||
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), | |||
"GetBlockInfo"), | |||
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(info.Name))); | |||
CodeExpression reflectedSet = new CodeMethodInvokeExpression( | |||
new CodeMethodReferenceExpression(new CodeSnippetExpression("BlockEngine"), | |||
"SetBlockInfo"), | |||
new CodeThisReferenceExpression(), reflectedType, new CodePrimitiveExpression(info.Name), | |||
new CodePropertySetValueReferenceExpression()); | |||
cl.Members.Add(new CodeMemberProperty | |||
{ | |||
Name = info.Name, | |||
HasGet = true, | |||
HasSet = true, | |||
GetStatements = | |||
{ | |||
new CodeMethodReturnStatement(reflectedGet) | |||
}, | |||
SetStatements = | |||
{ | |||
(CodeStatement)new CodeExpressionStatement(reflectedSet) | |||
}, | |||
Type = new CodeTypeReference(info.Type), | |||
Attributes = MemberAttributes.Public | MemberAttributes.Final, | |||
Comments = | |||
{ | |||
_start, | |||
new CodeCommentStatement($"Gets or sets the {info.ComponentType.Name}'s {info.Name} property." + | |||
" May not be saved.", // TODO: Doesn't know if tweakable stat | |||
true), | |||
_end | |||
} | |||
}); | |||
} | |||
private static readonly CodeCommentStatement _start = new("<summary>", true); | |||
private static readonly CodeCommentStatement _end = new("</summary>", true); | |||
} | |||
} |
@@ -0,0 +1,10 @@ | |||
using System; | |||
namespace CodeGenerator | |||
{ | |||
public class ECSClassInfo | |||
{ | |||
public string Name { get; set; } | |||
public ECSPropertyInfo[] Properties { get; set; } | |||
} | |||
} |
@@ -0,0 +1,11 @@ | |||
using System; | |||
namespace CodeGenerator | |||
{ | |||
public class ECSPropertyInfo | |||
{ | |||
public string Name { get; set; } | |||
public Type Type { get; set; } | |||
public Type ComponentType { get; set; } | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
namespace CodeGenerator | |||
{ | |||
public class ECSReflectedPropertyInfo : ECSPropertyInfo | |||
{ | |||
public string OriginalClassName { get; set; } | |||
} | |||
} |
@@ -1,16 +1,17 @@ | |||
using Svelto.ECS; | |||
using Techblox.Destruction; | |||
using Techblox.TimeRunning.Clusters; | |||
namespace TechbloxModdingAPI | |||
{ | |||
/// <summary> | |||
/// Represnts a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints. | |||
/// Represents a cluster of blocks in time running mode, meaning blocks that are connected either directly or via joints. | |||
/// Only exists if a cluster destruction manager is present. Static blocks like grass and dirt aren't part of a cluster. | |||
/// </summary> | |||
public class Cluster : EcsObjectBase | |||
{ | |||
public Cluster(EGID id) : base(id) | |||
{ | |||
public Cluster(EGID id) : base(id, typeof(ResetDestructionUtility)) | |||
{ // TODO: Damage has been connection-based for a while | |||
} | |||
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP)) | |||
@@ -48,20 +48,6 @@ namespace TechbloxModdingAPI | |||
Id = id; | |||
} | |||
private void AnalyzeEntityDescriptor(Type entityDescriptorType) | |||
{ | |||
// TODO: Cache | |||
// TODO: This should be in BlockClassGenerator | |||
// TODO: Add support for creating/deleting entities (getting an up to date server/client engines root) | |||
var templateType = typeof(EntityDescriptorTemplate<>).MakeGenericType(entityDescriptorType); | |||
var getTemplateClass = Expression.Constant(templateType); | |||
var getDescriptorExpr = Expression.PropertyOrField(getTemplateClass, "descriptor"); | |||
var getTemplateDescriptorExpr = | |||
Expression.Lambda<Func<IEntityDescriptor>>(getDescriptorExpr); | |||
var getTemplateDescriptor = getTemplateDescriptorExpr.Compile(); | |||
var builders = getTemplateDescriptor().componentsToBuild; | |||
} | |||
#region ECS initializer stuff | |||
protected internal EcsInitData InitData; | |||