624 lines
28 KiB
C#
624 lines
28 KiB
C#
using System;
|
|
using System.CodeDom;
|
|
using System.CodeDom.Compiler;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading;
|
|
using Kaitai;
|
|
using Microsoft.CSharp;
|
|
|
|
// ReSharper disable BitwiseOperatorOnEnumWithoutFlags
|
|
namespace CodeGenerator {
|
|
using CParamDeclExp = CodeParameterDeclarationExpression;
|
|
using CArgRefExp = CodeArgumentReferenceExpression;
|
|
using CThisRefExp = CodeThisReferenceExpression;
|
|
using CFieldRefExp = CodeFieldReferenceExpression;
|
|
|
|
public static class Program {
|
|
private static BlendFile _blendfile;
|
|
private static readonly StringBuilder Sb = new();
|
|
private const string OutPath = @"GeneratedOutput";
|
|
private const string Namespace = "BlendFile";
|
|
private static HashSet<string> _customTypes;
|
|
|
|
private static readonly string[] ListMarkerStr = {"list", "array"};
|
|
private static readonly string[] ListLenghtStr = {"num", "len", "size"};
|
|
|
|
private static ConcurrentQueue<Tuple<string, LogType>> _logQueue = new();
|
|
|
|
enum LogType {
|
|
Info,
|
|
Warning,
|
|
Error
|
|
}
|
|
|
|
private static void LogNow(string message, LogType type = LogType.Info) {
|
|
string msg = $"{DateTime.Now:yy-MM-dd HH:mm:ss}|{type.ToString()}|{message}";
|
|
lock (Sb){ Sb.AppendLine(msg); }
|
|
Console.WriteLine(msg);
|
|
}
|
|
|
|
private static void Log(string message, LogType type = LogType.Info) {
|
|
_logQueue.Enqueue(new (message, type));
|
|
}
|
|
|
|
public static void Main(string[] args) {
|
|
LogNow("Setting up logger...");
|
|
Stopwatch sw = new();
|
|
sw.Start();
|
|
long start = 0;
|
|
|
|
bool loggerExit = false;
|
|
Thread logger = new(() => {
|
|
start = sw.ElapsedTicks;
|
|
LogNow($"Logger started! In {sw.ElapsedMilliseconds}ms");
|
|
// ReSharper disable once AccessToModifiedClosure LoopVariableIsNeverChangedInsideLoop
|
|
while (!loggerExit || !_logQueue.IsEmpty)
|
|
if (_logQueue.TryDequeue(out var msg))
|
|
LogNow(msg.Item1, msg.Item2);
|
|
|
|
LogNow("Logger exiting...");
|
|
});
|
|
logger.Start();
|
|
|
|
var initTime = sw.ElapsedTicks;
|
|
|
|
Log("Reading blend file");
|
|
sw.Restart();
|
|
ReadBlendFile();
|
|
var readTime = sw.ElapsedTicks;
|
|
|
|
Log("Generating C# code...");
|
|
|
|
Log("Pass 1: Generating types");
|
|
sw.Restart();
|
|
CodeNamespace ns = GenerateTypes(out var rootNs);
|
|
var genTime = sw.ElapsedTicks;
|
|
|
|
sw.Restart();
|
|
Log("Pass 2: Writing out code");
|
|
OutputCodeFiles(ns);
|
|
OutputCodeFiles(rootNs, false);
|
|
var writeTime = sw.ElapsedTicks;
|
|
sw.Stop();
|
|
|
|
Log("Finished generating C# code!");
|
|
Log($"""
|
|
Timings:
|
|
Initialization: {(decimal)initTime/ TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
Logger Startup: {(decimal)start / TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
Reading: {(decimal)readTime/ TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
Generating: {(decimal)genTime/ TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
Writing: {(decimal)writeTime/ TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
----------------------------
|
|
Total: {(decimal)(initTime+readTime+genTime+writeTime) / TimeSpan.TicksPerMillisecond,10:N4} ms
|
|
""");
|
|
loggerExit = true;
|
|
while(logger.IsAlive) { }
|
|
Thread.Sleep(1000);
|
|
lock (Sb) {
|
|
File.AppendAllText("Log.txt", Sb.ToString());
|
|
}
|
|
}
|
|
|
|
private static void ReadBlendFile() {
|
|
Log("Reading empty.blend file");
|
|
_blendfile = BlendFile.FromFile("empty.blend");
|
|
|
|
Log($"Header: Blender v{_blendfile.Hdr.Version} {_blendfile.Hdr.Endian}\n" +
|
|
$"DataBlocks: {_blendfile.Blocks.Count}\n" +
|
|
$"DNA1: {_blendfile.SdnaStructs.Count} structures\n");
|
|
}
|
|
|
|
private static CodeNamespace GenerateTypes(out CodeNamespace additionalNs) {
|
|
//Initialize the namespaces
|
|
CodeNamespace rootNs = new CodeNamespace(Namespace);
|
|
CodeNamespace ns = new CodeNamespace(Namespace+".DNA");
|
|
//Fill the attribute types then add them to the namespaces
|
|
rootNs.Types.AddRange(GenerateTypeDeclarations());
|
|
ns.Imports.Add(new(rootNs.Name));
|
|
|
|
_customTypes = new();
|
|
|
|
foreach (var type in _blendfile.SdnaStructs) {
|
|
//Add the type to the custom types list
|
|
_customTypes.Add(type.Type);
|
|
|
|
//Create a new type declaration
|
|
var ctd = new CodeTypeDeclaration(type.Type);
|
|
ctd.CustomAttributes.Add(new ("DNAClassAttribute",
|
|
new CodeAttributeArgument(new CodePrimitiveExpression(type.IdxType)),
|
|
new CodeAttributeArgument(new CodePrimitiveExpression(type.Type))
|
|
));
|
|
|
|
Log($"Generating type from struct {type.Type}");
|
|
|
|
if(IsClass(type)) {
|
|
Log($"Marking {type.Type} as class");
|
|
ctd.IsClass = true;
|
|
}
|
|
else {
|
|
Log($"Marking {type.Type} as struct");
|
|
ctd.IsStruct = !ctd.IsClass;
|
|
}
|
|
//Add the class to the namespace
|
|
ns.Types.Add(ctd);
|
|
|
|
//TODO: when encountering a list, run trough the fields to find a count/lenght or similar data.
|
|
List<BlendFile.DnaField> normalFields; //Fields that are not lists nor lenghts of lists
|
|
List<(BlendFile.DnaField, BlendFile.DnaField)> listFields; //Fields that are lists, and their corresponding length fields
|
|
|
|
//filter the fields we want to include in the class minus the lists
|
|
FilterFields(type.Fields, out normalFields, out listFields);
|
|
|
|
var totalSize = 0;
|
|
|
|
//Add the fields to the class
|
|
Log($"Fields: {type.Fields.Count}");
|
|
for (var index = 0; index < type.Fields.Count; index++) {
|
|
var field = type.Fields[index];
|
|
if (!normalFields.Contains(field)) {
|
|
Log($"Field {field.Name} is part of a list, skipping");
|
|
continue;
|
|
}
|
|
|
|
CodeMemberField cmf;
|
|
string name = field.Name;
|
|
if (name.Contains("()")) continue;
|
|
if (name.Contains("[")) {
|
|
Log($"Generating array field {field.Name}");
|
|
cmf = CreateArrayMemberField(field);
|
|
}
|
|
else {
|
|
Log($"Generating field {field.Name}");
|
|
cmf = CreateMemberField(field);
|
|
}
|
|
|
|
var attributes = GenerateDnaFieldAttribute(index, field, field.M_Parent.M_Parent, totalSize, out int size);
|
|
totalSize += size;
|
|
cmf.CustomAttributes.Add(attributes);
|
|
ctd.Members.Add(cmf);
|
|
}
|
|
|
|
//Add the lists to the class
|
|
for (var index = 0; index < type.Fields.Count; index++) {
|
|
var field = type.Fields[index];
|
|
//Skip fields that are not part of a list
|
|
if (!listFields.Select(f => f.Item1).Contains(field)) {
|
|
Log($"Field {field.Name} is not part of a list, skipping");
|
|
continue;
|
|
}
|
|
//Retrieve the list pointer and the list length fields
|
|
var (listPointer, listLength) = listFields.FirstOrDefault(x => x.Item1 == field);
|
|
//Retrieve the index of the list length field
|
|
int sizeIndex = type.Fields.IndexOf(listLength);
|
|
|
|
Log($"Generating list field {listPointer.Name}");
|
|
var cmf = CreateListMemberField(listPointer, listLength);
|
|
|
|
var attribute = GenerateDnaListAttribute(index, listPointer, listPointer.M_Parent.M_Parent, sizeIndex,
|
|
listLength, totalSize, out int size);
|
|
|
|
totalSize += size;
|
|
cmf.CustomAttributes.Add(attribute);
|
|
ctd.Members.Add(cmf);
|
|
}
|
|
|
|
ctd.CustomAttributes[0].Arguments.Add(new (new CodePrimitiveExpression(totalSize)));
|
|
|
|
Log("Generating Parameterless constructor");
|
|
if(ctd.Members.Count > 0)
|
|
ctd.Members.Add(GenerateParameterlessConstructor(type, ctd));
|
|
|
|
Log("Generating Default Constructor");
|
|
ctd.Members.Add(GenerateConstructor(type, ctd));
|
|
|
|
Log("Finished generating struct");
|
|
}
|
|
additionalNs = rootNs;
|
|
return ns;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Determines if the type has to be serialized as a class or a struct
|
|
/// </summary>
|
|
/// <param name="type"><see cref="BlendFile.DnaStruct"/> istance to analyze</param>
|
|
/// <returns> <see langword="true"/> if there is any pointer or self reference, otherwise <see langword="false"/></returns>
|
|
private static bool IsClass(BlendFile.DnaStruct type) {
|
|
foreach (var field in type.Fields) {
|
|
if (field.Name.Contains("*")) {
|
|
Log($"Pointer detected. {field.Type} {field.Name}");
|
|
return true;
|
|
}
|
|
if (field.Type.Contains(type.Type)) {
|
|
Log($"Self reference detected. {field.Type} {field.Name}");
|
|
return true;
|
|
}
|
|
}
|
|
Log($"No pointer or self reference detected in {type.Type}");
|
|
return false;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Filters the fields into normal fields and list fields pairs
|
|
/// </summary>
|
|
/// <param name="fields"><see cref="IEnumerable{T}"/> of <see cref="BlendFile.DnaField"/>s from all parameters</param>
|
|
/// <param name="normalFields"><see cref="List{T}"/> of <see cref="BlendFile.DnaField"/> containing all fields not part of a List</param>
|
|
/// <param name="listFields"><see cref="List{T}"/> of (<see cref="BlendFile.DnaField"/>, <see cref="BlendFile.DnaField"/>) collection where Item1 is the ListPointer and Item2 is the list lenght</param>
|
|
private static void FilterFields(IEnumerable<BlendFile.DnaField> fields,
|
|
out List<BlendFile.DnaField> normalFields,
|
|
out List<(BlendFile.DnaField, BlendFile.DnaField)> listFields) {
|
|
normalFields = new(); //Fields that are not lists nor lengths of lists
|
|
listFields = new(); //Fields that are lists, and their corresponding length fields
|
|
|
|
//Cast to array the fields to avoid multiple enumerations
|
|
var dnaFields = fields as BlendFile.DnaField[] ?? fields.ToArray();
|
|
foreach (var field in dnaFields) {
|
|
if (ListMarkerStr.Any(s => field.Name.Contains(s)) &&
|
|
!ListLenghtStr.Any(s2 => field.Name.Contains(s2))) {
|
|
|
|
Log($"Found list field {field.Name}");
|
|
Log($"Searching for list length field");
|
|
|
|
var listLengthField = dnaFields.FirstOrDefault(f => f.Name.Contains(field.Name.ParseFName()) &&
|
|
ListLenghtStr.Any(s2 => f.Name.Contains(s2)));
|
|
|
|
if (listLengthField == null)
|
|
Log($"No list length field found for {field.Name}");
|
|
else {
|
|
Log($"Found list length field {listLengthField.Name}");
|
|
listFields.Add((field, listLengthField));
|
|
|
|
//Remove the list length field from the normal fields (if present)
|
|
if(normalFields.Remove(listLengthField))
|
|
Log($"Removed list length field {listLengthField.Name}");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
//Skip fields that are recognized as listLengths
|
|
if (listFields.Select(f => f.Item2).Contains(field)) {
|
|
Log($"Skipping known list length field {field.Name}");
|
|
continue;
|
|
}
|
|
|
|
Log($"Adding normal field {field.Name}");
|
|
normalFields.Add(field);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Generates the following attribute types <c>DNAAttribute, DNAFieldAttribute, DNAClassAttribute, DNAListAttribute</c>
|
|
/// </summary>
|
|
/// <returns><see cref="CodeTypeDeclaration"/> array containing the generated attribute types</returns>
|
|
/// <remarks>This internally uses a single instance of <see cref="AttributeBuilder"/> and sequentially generates the various attributes.</remarks>
|
|
private static CodeTypeDeclaration[] GenerateTypeDeclarations() {
|
|
var attributeBuilder = new AttributeBuilder();
|
|
|
|
var typeDeclarations = new[] {
|
|
attributeBuilder.New().SetName("DNAAttribute")
|
|
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.All")))
|
|
.DeriveFromClass()
|
|
.AddAutoProperty(typeof(int), "OriginalIndex")
|
|
.AddAutoProperty(typeof(string), "OriginalName")
|
|
.AddPropertiesConstructor()
|
|
.Build(),
|
|
attributeBuilder.New().SetName("DNAFieldAttribute")
|
|
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Field")))
|
|
.DeriveFromClass($"{Namespace}.DNAAttribute")
|
|
.AddAutoProperty(typeof(int), "Size")
|
|
.AddAutoProperty(typeof(string), "OriginalType")
|
|
.AddAutoProperty(typeof(int), "OriginalIndex")
|
|
.AddAutoProperty(typeof(string), "OriginalName")
|
|
.AddAutoProperty(typeof(string), "UnderlyingType")
|
|
.AddAutoProperty(typeof(bool), "IsPointer")
|
|
.AddAutoProperty(typeof(int), "MemoryOffset")
|
|
.AddPropertiesConstructor()
|
|
.AddBaseConstructorParams(["OriginalIndex", "OriginalName"])
|
|
.Build(),
|
|
attributeBuilder.New().SetName("DNAClassAttribute")
|
|
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Class | AttributeTargets.Struct")))
|
|
.DeriveFromClass($"{Namespace}.DNAAttribute")
|
|
.AddAutoProperty(typeof(int), "OriginalIndex")
|
|
.AddAutoProperty(typeof(string), "OriginalName")
|
|
.AddAutoProperty(typeof(int), "Size")
|
|
.AddPropertiesConstructor()
|
|
.AddBaseConstructorParams(["OriginalIndex", "OriginalName"])
|
|
.Build(),
|
|
attributeBuilder.New().SetName("DNAListAttribute")
|
|
.SetAttributeUsage(new (new CodeSnippetExpression("AttributeTargets.Property | AttributeTargets.Field")))
|
|
.DeriveFromClass($"{Namespace}.DNAAttribute")
|
|
.AddAutoProperty(typeof(int), "Size")
|
|
.AddAutoProperty(typeof(string), "OriginalType")
|
|
.AddAutoProperty(typeof(string), "OriginalName")
|
|
.AddAutoProperty(typeof(int), "OriginalIndex")
|
|
.AddAutoProperty(typeof(string), "UnderlyingType")
|
|
.AddAutoProperty(typeof(string), "CountFieldName")
|
|
.AddAutoProperty(typeof(int), "CountFieldIndex")
|
|
.AddAutoProperty(typeof(int), "MemoryOffset")
|
|
.AddPropertiesConstructor()
|
|
.AddBaseConstructorParams(["OriginalIndex", "OriginalName"])
|
|
.Build()
|
|
};
|
|
return typeDeclarations;
|
|
}
|
|
|
|
//TODO: use AttributeBuilder inside here
|
|
private static CodeAttributeDeclaration GenerateDnaFieldAttribute(int index, BlendFile.DnaField field,
|
|
BlendFile.Dna1Body body, int offset, out int size) {
|
|
string t;
|
|
size = body.Lengths[field.IdxType];
|
|
|
|
bool isPointer = false;
|
|
|
|
if (field.Name.StartsWith('*'))
|
|
{
|
|
size = 8;
|
|
isPointer = true;
|
|
}
|
|
|
|
if (field.Name.Contains('[')) {
|
|
CodeMemberField amf = CreateArrayMemberField(field);
|
|
var sb = new StringBuilder();
|
|
sb.Append(amf.Type.BaseType);
|
|
sb.Append("[");
|
|
for(int i=1; i<amf.Type.ArrayRank; i++) {
|
|
sb.Append(",");
|
|
}
|
|
sb.Append("]");
|
|
t = sb.ToString();
|
|
|
|
var dimensions = GetArrayDimensions(field.Name);
|
|
|
|
foreach(var dim in dimensions) {
|
|
size *= dim;
|
|
}
|
|
} else
|
|
{
|
|
t = field.Type;
|
|
}
|
|
|
|
CodeAttributeDeclaration cad = new("DNAFieldAttribute");
|
|
cad.Arguments.AddRange(new CodeAttributeArgumentCollection() {
|
|
new(new CodePrimitiveExpression(size)),
|
|
new(new CodePrimitiveExpression(field.Type)),
|
|
new(new CodePrimitiveExpression(index)),
|
|
new(new CodePrimitiveExpression(field.Name)),
|
|
new(new CodePrimitiveExpression(t)),
|
|
new(new CodePrimitiveExpression(isPointer)),
|
|
new(new CodePrimitiveExpression(offset))
|
|
});
|
|
return cad;
|
|
}
|
|
|
|
private static CodeAttributeDeclaration GenerateDnaListAttribute(int listIndex, BlendFile.DnaField listField,
|
|
BlendFile.Dna1Body body, int lenghtIndex, BlendFile.DnaField lenghtField, int offset, out int size) {
|
|
size = 0;
|
|
var cad = new CodeAttributeDeclaration("DNAListAttribute");
|
|
cad.Arguments.AddRange(new CodeAttributeArgumentCollection() {
|
|
new(new CodeSnippetExpression("8")),//pointer size
|
|
new(new CodePrimitiveExpression(listField.Type)),
|
|
new(new CodePrimitiveExpression(listField.Name)),
|
|
new(new CodePrimitiveExpression(listIndex)),
|
|
new(new CodePrimitiveExpression(listField.Type)),//TODO: double check this
|
|
new(new CodePrimitiveExpression(lenghtField.Name)),
|
|
new(new CodePrimitiveExpression(lenghtIndex)),
|
|
new(new CodePrimitiveExpression(offset))
|
|
});
|
|
return cad;
|
|
}
|
|
|
|
private static CodeMemberField CreateMemberField(BlendFile.DnaField field) {
|
|
Type t = Type.GetType(field.Type.ParseFType());
|
|
CodeMemberField cmf;
|
|
//Check if the type is a built-in type or a custom type
|
|
if (t != null) cmf = new(t, field.Name.ParseFName()); //Built-in type
|
|
else {
|
|
cmf = new(new CodeTypeReference(field.Type), field.Name.ParseFName()); //Custom type
|
|
_customTypes.Add(field.Type);
|
|
}
|
|
cmf.Attributes = MemberAttributes.Public;
|
|
return cmf;
|
|
}
|
|
|
|
private static CodeMemberField CreateArrayMemberField(BlendFile.DnaField field) {
|
|
Type t = Type.GetType(field.Type.ParseFType());
|
|
CodeMemberField cmf;
|
|
|
|
// Parse all array dimensions
|
|
var name = field.Name.ParseFName();
|
|
var dimensions = GetArrayDimensions(name);
|
|
|
|
// Get clean field name (without array brackets)
|
|
name = field.Name.Substring(0, field.Name.IndexOf('[')).ParseFName();
|
|
|
|
//Check if the type is a built-in type or a custom type
|
|
if (t != null) cmf = new(t, name); //Built-in type
|
|
else {
|
|
cmf = new(field.Type, name); //Custom type
|
|
_customTypes.Add(field.Type);
|
|
}
|
|
|
|
//Set the field attributes
|
|
cmf.Attributes = MemberAttributes.Public;
|
|
//Define the array type
|
|
cmf.Type.ArrayElementType = new(field.Type.ParseFType() ?? field.Type);
|
|
cmf.Type.ArrayRank = dimensions.Count;
|
|
|
|
//Define the array initialization expression
|
|
cmf.InitExpression = GenerateArrayInitExpression(cmf.Type, dimensions);
|
|
return cmf;
|
|
}
|
|
|
|
private static CodeMemberField CreateListMemberField(BlendFile.DnaField field, BlendFile.DnaField lenght) {
|
|
Type t = Type.GetType(field.Type.ParseFType());
|
|
CodeMemberField cmf;
|
|
CodeTypeReference ctr = new(typeof(List<>));
|
|
|
|
//Check if the type is a built-in type or a custom type
|
|
if (t != null) { //Built-in type
|
|
ctr.TypeArguments.Add(t);
|
|
cmf = new(ctr, field.Name.ParseFName());
|
|
} else { //Custom type
|
|
ctr.TypeArguments.Add(new CodeTypeReference(field.Type));
|
|
cmf = new(ctr, field.Name.ParseFName());
|
|
}
|
|
cmf.Attributes = MemberAttributes.Public;
|
|
return cmf;
|
|
}
|
|
|
|
private static List<int> GetArrayDimensions(string name)
|
|
{
|
|
var dimensions = new List<int>();
|
|
int startIndex = 0;
|
|
// Get all array dimensions
|
|
while ((startIndex = name.IndexOf('[', startIndex)) != -1) {
|
|
int endIndex = name.IndexOf(']', startIndex);
|
|
string sizeStr = name.Substring(startIndex + 1, endIndex - startIndex - 1);
|
|
if (int.TryParse(sizeStr, out int size)) {
|
|
dimensions.Add(size);
|
|
}
|
|
startIndex = endIndex + 1;
|
|
}
|
|
|
|
return dimensions;
|
|
}
|
|
|
|
private static CodeExpression GenerateArrayInitExpression(CodeTypeReference type, IEnumerable<int> dimensions) {
|
|
var dimValues = dimensions as int[] ?? dimensions.ToArray();
|
|
string dims = string.Concat(dimValues.Take(dimValues.Count() - 1).Select(d => $"{d},"));
|
|
dims += dimValues.Last();
|
|
return new CodeSnippetExpression($"new {type.BaseType}[{dims}]");
|
|
}
|
|
|
|
private static CodeTypeConstructor GenerateStaticConstructor(CodeTypeDeclaration ctd) {
|
|
CodeTypeConstructor ctc = new CodeTypeConstructor {
|
|
Attributes = MemberAttributes.Static
|
|
};
|
|
ctc.Statements.AddRange(ctd.Members
|
|
.OfType<CodeMemberField>()
|
|
.Where(f => f.Type.ArrayRank > 0)
|
|
.Select(f =>
|
|
{
|
|
var dims = new List<int>();
|
|
for (int i = 0; i < f.Type.ArrayRank; i++) {
|
|
dims.Add(0);
|
|
}
|
|
return new CodeAssignStatement(
|
|
new CFieldRefExp(new CThisRefExp(), f.Name),
|
|
GenerateArrayInitExpression(f.Type, dims)
|
|
);
|
|
}).ToArray<CodeStatement>());
|
|
return ctc;
|
|
}
|
|
|
|
private static CodeConstructor GenerateConstructor(BlendFile.DnaStruct type, CodeTypeDeclaration ctd) {
|
|
//Create a normal constructor
|
|
CodeConstructor cc = new CodeConstructor {
|
|
Name = type.Type,
|
|
Attributes = MemberAttributes.Public,
|
|
ReturnType = new(type.Type)
|
|
};
|
|
|
|
//Add the parameters to the constructor
|
|
cc.Parameters.AddRange(ctd.Members
|
|
.OfType<CodeMemberField>()
|
|
.Select(f =>
|
|
{
|
|
var cpde = new CParamDeclExp(f.Type, f.Name) {
|
|
Direction = FieldDirection.In
|
|
};
|
|
return cpde;
|
|
}).ToArray());
|
|
|
|
//Assign the parameters to the respective fields
|
|
cc.Statements.AddRange(ctd.Members
|
|
.OfType<CodeMemberField>()
|
|
.Select(f => new CodeAssignStatement(
|
|
new CFieldRefExp(new CThisRefExp(), f.Name),
|
|
new CArgRefExp(f.Name))
|
|
).ToArray<CodeStatement>());
|
|
|
|
return cc;
|
|
}
|
|
|
|
private static CodeConstructor GenerateParameterlessConstructor(BlendFile.DnaStruct type, CodeTypeDeclaration ctd) {
|
|
//Create a normal constructor
|
|
CodeConstructor cc = new CodeConstructor {
|
|
Name = type.Type,
|
|
Attributes = MemberAttributes.Public,
|
|
ReturnType = new(type.Type)
|
|
};
|
|
|
|
//Assign the parameters to the respective fields
|
|
cc.Statements.AddRange(ctd.Members
|
|
.OfType<CodeMemberField>()
|
|
.Select(f => new CodeAssignStatement(
|
|
new CFieldRefExp(new CThisRefExp(), f.Name),
|
|
new CodeSnippetExpression("default"))
|
|
).ToArray<CodeStatement>());
|
|
|
|
return cc;
|
|
}
|
|
|
|
private static void SetupCcu(out CodeGeneratorOptions genOpts, out CSharpCodeProvider provider,
|
|
out CodeCompileUnit ccu) {
|
|
genOpts = new() {
|
|
BlankLinesBetweenMembers = false,
|
|
BracingStyle = "Block",
|
|
ElseOnClosing = true,
|
|
IndentString = " ",
|
|
VerbatimOrder = true
|
|
};
|
|
provider = new();
|
|
//var date = DateTime.Now.ToString(CultureInfo.InvariantCulture);
|
|
|
|
CodeNamespace globalNs = new CodeNamespace();
|
|
//CodeComment comment = new CodeComment("Automatically generated by BlenderSharp at " + date, false);
|
|
//globalNs.Comments.Add(new(comment));
|
|
|
|
globalNs.Imports.Add(new("System"));
|
|
|
|
ccu = new();
|
|
ccu.Namespaces.Add(globalNs);
|
|
}
|
|
|
|
private static void OutputCodeFiles(CodeNamespace ns, bool outputExtraTypes = true) {
|
|
string rootPath = Path.GetDirectoryName(GetOutputPath(ns, ""));
|
|
if (!Path.Exists(rootPath)) Directory.CreateDirectory(rootPath!);
|
|
SetupCcu(out var codeGeneratorOptions, out var provider, out var ccu);
|
|
CodeNamespace tempNs = new CodeNamespace(ns.Name);
|
|
tempNs.Imports.AddRange(ns.Imports.Cast<CodeNamespaceImport>().ToArray());
|
|
ccu.Namespaces.Add(tempNs);
|
|
foreach (var type in ns.Types.OfType<CodeTypeDeclaration>()) {
|
|
tempNs.Types.Add(type);
|
|
Log($"Writing out {(type.IsStruct ? "struct" : "class")} {type.Name}");
|
|
using var sw = new StreamWriter(GetOutputPath(ns, type.Name));
|
|
provider.GenerateCodeFromCompileUnit(ccu, sw, codeGeneratorOptions);
|
|
tempNs.Types.Remove(type);
|
|
}
|
|
|
|
if (!outputExtraTypes) return;
|
|
_customTypes.ExceptWith(ns.Types.OfType<CodeTypeDeclaration>().Select(t => t.Name));
|
|
foreach (var type in _customTypes) {
|
|
Log($"Creating empty struct for missing {type}");
|
|
var ctd = new CodeTypeDeclaration(type) {
|
|
IsStruct = true,
|
|
Attributes = MemberAttributes.Public
|
|
};
|
|
tempNs.Types.Add(ctd);
|
|
}
|
|
using var finalsw = new StreamWriter(GetOutputPath(ns, "_ExtraTypes"));
|
|
provider.GenerateCodeFromCompileUnit(ccu, finalsw, codeGeneratorOptions);
|
|
}
|
|
|
|
private static string GetOutputPath(CodeNamespace ns, string typeName) {
|
|
return Path.Combine(OutPath, ns.Name, typeName + ".cs");
|
|
}
|
|
}
|
|
} |