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:
Norbi Peti 2023-10-04 02:36:25 +02:00
parent 9a195215f9
commit 9be1b5fdaf
No known key found for this signature in database
GPG key ID: DBA4C4549A927E56
7 changed files with 226 additions and 17 deletions

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

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

View file

@ -0,0 +1,10 @@
using System;
namespace CodeGenerator
{
public class ECSClassInfo
{
public string Name { get; set; }
public ECSPropertyInfo[] Properties { get; set; }
}
}

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

View file

@ -0,0 +1,7 @@
namespace CodeGenerator
{
public class ECSReflectedPropertyInfo : ECSPropertyInfo
{
public string OriginalClassName { get; set; }
}
}

View file

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

View file

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