Implemented improved ECS class generator based on entity descriptors
I can't test it yet because the API is full of errors because of the refactoring
This commit is contained in:
parent
9a195215f9
commit
9be1b5fdaf
7 changed files with 226 additions and 17 deletions
65
CodeGenerator/ECSAnalyzer.cs
Normal file
65
CodeGenerator/ECSAnalyzer.cs
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
129
CodeGenerator/ECSClassGenerator.cs
Normal file
129
CodeGenerator/ECSClassGenerator.cs
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
10
CodeGenerator/ECSClassInfo.cs
Normal file
10
CodeGenerator/ECSClassInfo.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CodeGenerator
|
||||||
|
{
|
||||||
|
public class ECSClassInfo
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public ECSPropertyInfo[] Properties { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
CodeGenerator/ECSPropertyInfo.cs
Normal file
11
CodeGenerator/ECSPropertyInfo.cs
Normal file
|
@ -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; }
|
||||||
|
}
|
||||||
|
}
|
7
CodeGenerator/ECSReflectedPropertyInfo.cs
Normal file
7
CodeGenerator/ECSReflectedPropertyInfo.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace CodeGenerator
|
||||||
|
{
|
||||||
|
public class ECSReflectedPropertyInfo : ECSPropertyInfo
|
||||||
|
{
|
||||||
|
public string OriginalClassName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,17 @@
|
||||||
using Svelto.ECS;
|
using Svelto.ECS;
|
||||||
|
using Techblox.Destruction;
|
||||||
using Techblox.TimeRunning.Clusters;
|
using Techblox.TimeRunning.Clusters;
|
||||||
|
|
||||||
namespace TechbloxModdingAPI
|
namespace TechbloxModdingAPI
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <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.
|
/// Only exists if a cluster destruction manager is present. Static blocks like grass and dirt aren't part of a cluster.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Cluster : EcsObjectBase
|
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))
|
public Cluster(uint id) : this(new EGID(id, ClustersExclusiveGroups.SIMULATION_CLUSTERS_GROUP))
|
||||||
|
|
|
@ -48,20 +48,6 @@ namespace TechbloxModdingAPI
|
||||||
Id = id;
|
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
|
#region ECS initializer stuff
|
||||||
|
|
||||||
protected internal EcsInitData InitData;
|
protected internal EcsInitData InitData;
|
||||||
|
|
Loading…
Reference in a new issue