Added runtime generation of the attribute classes and marking of field metadata

This commit is contained in:
Samuele Lorefice
2025-01-22 20:24:25 +01:00
parent 439cea385f
commit 162f888600
2 changed files with 240 additions and 19 deletions

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Statistic">
<option name="excludeDotArtifactDirectory" value="true" />
<option name="excludedDirectories">
<list>
<option value="$PROJECT_DIR$/CodeGenerator/bin" />
<option value="$PROJECT_DIR$/CodeGenerator/obj" />
<option value="$PROJECT_DIR$/BlendFile/bin" />
<option value="$PROJECT_DIR$/BlendFile/obj" />
</list>
</option>
</component>
</project>

View File

@@ -17,11 +17,16 @@ using BlendFile = Kaitai.BlenderBlend;
// ReSharper disable BitwiseOperatorOnEnumWithoutFlags // ReSharper disable BitwiseOperatorOnEnumWithoutFlags
namespace CodeGenerator { namespace CodeGenerator {
using CParamDeclExp = CodeParameterDeclarationExpression;
using CArgRefExp = CodeArgumentReferenceExpression;
using CThisRefExp = CodeThisReferenceExpression;
using CFieldRefExp = CodeFieldReferenceExpression;
public class Program { public class Program {
public static BlendFile blendfile; public static BlendFile blendfile;
private static StringBuilder sb = new(); private static StringBuilder sb = new();
private const string OutPath = @"Blendfile\DNA"; private const string OutPath = @"GeneratedOutput\";
private const string Namespace = "BlendFile.DNA"; private const string Namespace = "BlendFile";
private static readonly string[] AdaptedTypes = new[] { "uchar" }; private static readonly string[] AdaptedTypes = new[] { "uchar" };
private static HashSet<string> customTypes; private static HashSet<string> customTypes;
@@ -37,10 +42,12 @@ namespace CodeGenerator {
Log("Generating C# code..."); Log("Generating C# code...");
Log("Pass 1: Generating types"); Log("Pass 1: Generating types");
CodeNamespace ns = GenerateTypes(); CodeNamespace rootNs;
CodeNamespace ns = GenerateTypes(out rootNs);
Log("Pass 2: Writing out code"); Log("Pass 2: Writing out code");
OutputCodeFiles(ns); OutputCodeFiles(ns);
OutputCodeFiles(rootNs, false);
Log("Finished generating C# code!"); Log("Finished generating C# code!");
File.AppendAllText("Log.txt", sb.ToString()); File.AppendAllText("Log.txt", sb.ToString());
@@ -55,8 +62,12 @@ namespace CodeGenerator {
$"DNA1: {blendfile.SdnaStructs.Count} structures\n"); $"DNA1: {blendfile.SdnaStructs.Count} structures\n");
} }
private static CodeNamespace GenerateTypes() { private static CodeNamespace GenerateTypes(out CodeNamespace additionalNs) {
CodeNamespace ns = new CodeNamespace(Namespace); CodeNamespace rootNs = new CodeNamespace(Namespace);
rootNs.Types.Add(GenerateDNAFieldAttributeType());
rootNs.Types.Add(GenerateDNAClassAttributeType());
CodeNamespace ns = new CodeNamespace(Namespace+".DNA");
ns.Imports.Add(new(rootNs.Name));
customTypes = new(); customTypes = new();
@@ -93,7 +104,8 @@ namespace CodeGenerator {
//Add the fields to the class //Add the fields to the class
Log($"Fields: {type.Fields.Count}"); Log($"Fields: {type.Fields.Count}");
foreach (var field in type.Fields) { for (var index = 0; index < type.Fields.Count; index++) {
var field = type.Fields[index];
CodeMemberField cmf; CodeMemberField cmf;
string name = field.Name; string name = field.Name;
if (name.Contains("()")) continue; if (name.Contains("()")) continue;
@@ -105,7 +117,7 @@ namespace CodeGenerator {
Log($"Generating field {field.Name}"); Log($"Generating field {field.Name}");
cmf = CreateMemberField(field); cmf = CreateMemberField(field);
} }
cmf.CustomAttributes.Add(GenerateDNAFieldAttribute(index, field, field.M_Parent.M_Parent));
ctd.Members.Add(cmf); ctd.Members.Add(cmf);
} }
@@ -114,9 +126,196 @@ namespace CodeGenerator {
Log("Finished generating struct"); Log("Finished generating struct");
} }
additionalNs = rootNs;
return ns; return ns;
} }
private static CodeTypeDeclaration GenerateDNAFieldAttributeType() {
var ctd = new CodeTypeDeclaration("DNAFieldAttribute") {
IsClass = true,
Attributes = MemberAttributes.Public
};
ctd.BaseTypes.Add(new CodeTypeReference(typeof(Attribute)));
ctd.CustomAttributes.Add(new("AttributeUsage",
new CodeAttributeArgument(new CodeSnippetExpression("AttributeTargets.Field"))));
var cmf = new CodeMemberField(typeof(int), "_size") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
var cmp = new CodeMemberProperty() {
Name = "Size",
Type = new CodeTypeReference(typeof(int)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_size"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_size"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(string), "_originalType") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "OriginalType",
Type = new CodeTypeReference(typeof(string)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalType"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalType"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(string), "_originalName") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "OriginalName",
Type = new CodeTypeReference(typeof(string)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(
new CFieldRefExp(new CThisRefExp(), "_originalName"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalName"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(int), "_originalIndex") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "OriginalIndex",
Type = new CodeTypeReference(typeof(int)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"))
}
};
ctd.Members.Add(cmp);
var cc = new CodeConstructor() { Attributes = MemberAttributes.Public };
cc.Parameters.AddRange(new CParamDeclExp[] {
new(typeof(int), "originalIndex"),
new(typeof(string), "originalType"),
new(typeof(string), "originalName"),
new(typeof(int), "size")
});
cc.Statements.AddRange(new CodeAssignStatement[] {
new(new CFieldRefExp(new CThisRefExp(), "OriginalIndex"), new CArgRefExp("originalIndex")),
new(new CFieldRefExp(new CThisRefExp(), "OriginalType"), new CArgRefExp("originalType")),
new(new CFieldRefExp(new CThisRefExp(), "OriginalName"), new CArgRefExp("originalName")),
new(new CFieldRefExp(new CThisRefExp(), "Size"), new CArgRefExp("size"))
});
ctd.Members.Add(cc);
return ctd;
}
private static CodeTypeDeclaration GenerateDNAClassAttributeType() {
var ctd = new CodeTypeDeclaration("DNAClassAttribute") {
IsClass = true,
Attributes = MemberAttributes.Public
};
ctd.CustomAttributes.Add(new("AttributeUsage",
new CodeAttributeArgument(new CodeSnippetExpression("AttributeTargets.Class"))));
var cmf = new CodeMemberField(typeof(int), "_originalIndex") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
var cmp = new CodeMemberProperty() {
Name = "OriginalIndex",
Type = new CodeTypeReference(typeof(int)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalIndex"))
}
};
ctd.Members.Add(cmp);
cmf = new CodeMemberField(typeof(string), "_originalName") {
Attributes = MemberAttributes.Private
};
ctd.Members.Add(cmf);
cmp = new CodeMemberProperty() {
Name = "OriginalName",
Type = new CodeTypeReference(typeof(string)),
Attributes = MemberAttributes.Public,
HasSet = true,
SetStatements = {
new CodeAssignStatement(new CFieldRefExp(new CThisRefExp(), "_originalName"), new CArgRefExp("value"))
},
HasGet = true,
GetStatements = {
new CodeMethodReturnStatement(new CFieldRefExp(new CThisRefExp(), "_originalName"))
}
};
ctd.Members.Add(cmp);
var cc = new CodeConstructor() { Attributes = MemberAttributes.Public };
cc.Parameters.AddRange(new CParamDeclExp[] {
new(typeof(int), "originalIndex"),
new(typeof(string), "originalName")
});
cc.Statements.AddRange(new CodeAssignStatement[] {
new(new CFieldRefExp(new CThisRefExp(), "OriginalIndex"), new CArgRefExp("originalIndex")),
new(new CFieldRefExp(new CThisRefExp(), "OriginalName"), new CArgRefExp("originalName"))
});
ctd.Members.Add(cc);
return ctd;
}
private static CodeAttributeDeclaration GenerateDNAFieldAttribute(int index, BlendFile.DnaField field,
BlendFile.Dna1Body body) {
CodeAttributeDeclaration cad = new("DNAFieldAttribute");
cad.Arguments.AddRange(new CodeAttributeArgumentCollection() {
new(new CodePrimitiveExpression(index)),
new(new CodePrimitiveExpression(field.Type)),
new(new CodePrimitiveExpression(field.Name)),
new(new CodePrimitiveExpression((int)body.Lengths[field.IdxType]))
});
return cad;
}
private static CodeMemberField CreateMemberField(BlenderBlend.DnaField field) { private static CodeMemberField CreateMemberField(BlenderBlend.DnaField field) {
Type t = Type.GetType(field.Type.ParseFType()); Type t = Type.GetType(field.Type.ParseFType());
CodeMemberField cmf; CodeMemberField cmf;
@@ -173,7 +372,7 @@ namespace CodeGenerator {
public static CodeExpression GenerateArrayInitExpression(CodeTypeReference type, IEnumerable<int> dimensions) { public static CodeExpression GenerateArrayInitExpression(CodeTypeReference type, IEnumerable<int> dimensions) {
var dimValues = dimensions as int[] ?? dimensions.ToArray(); var dimValues = dimensions as int[] ?? dimensions.ToArray();
string dims = string.Concat(dimValues.Take(dimValues.Count() - 1).Select(d => $"{d},")); string dims = string.Concat(dimValues.Take(dimValues.Count() - 1).Select(d => $"{d},"));
dims+= dimValues.Last(); dims += dimValues.Last();
return new CodeSnippetExpression($"new {type.BaseType}[{dims}]"); return new CodeSnippetExpression($"new {type.BaseType}[{dims}]");
} }
@@ -190,7 +389,7 @@ namespace CodeGenerator {
dims.Add(0); dims.Add(0);
} }
return new CodeAssignStatement( return new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), f.Name), new CFieldRefExp(new CThisRefExp(), f.Name),
GenerateArrayInitExpression(f.Type, dims) GenerateArrayInitExpression(f.Type, dims)
); );
}).ToArray<CodeStatement>()); }).ToArray<CodeStatement>());
@@ -210,7 +409,7 @@ namespace CodeGenerator {
.OfType<CodeMemberField>() .OfType<CodeMemberField>()
.Select(f => .Select(f =>
{ {
var cpde = new CodeParameterDeclarationExpression(f.Type, f.Name); var cpde = new CParamDeclExp(f.Type, f.Name);
cpde.Direction = FieldDirection.In; cpde.Direction = FieldDirection.In;
return cpde; return cpde;
}).ToArray()); }).ToArray());
@@ -219,8 +418,8 @@ namespace CodeGenerator {
cc.Statements.AddRange(ctd.Members cc.Statements.AddRange(ctd.Members
.OfType<CodeMemberField>() .OfType<CodeMemberField>()
.Select(f => new CodeAssignStatement( .Select(f => new CodeAssignStatement(
new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), f.Name), new CFieldRefExp(new CThisRefExp(), f.Name),
new CodeArgumentReferenceExpression(f.Name)) new CArgRefExp(f.Name))
).ToArray<CodeStatement>()); ).ToArray<CodeStatement>());
return cc; return cc;
@@ -248,18 +447,22 @@ namespace CodeGenerator {
ccu.Namespaces.Add(globalNs); ccu.Namespaces.Add(globalNs);
} }
private static void OutputCodeFiles(CodeNamespace ns) { private static void OutputCodeFiles(CodeNamespace ns, bool outputExtraTypes = true) {
if (!Path.Exists(OutPath)) Directory.CreateDirectory(OutPath); string rootPath = Path.GetDirectoryName(GetOutputPath(ns, ""));
if (!Path.Exists(rootPath)) Directory.CreateDirectory(rootPath!);
SetupCCU(out var codeGeneratorOptions, out var provider, out var ccu); SetupCCU(out var codeGeneratorOptions, out var provider, out var ccu);
CodeNamespace tempNs = new CodeNamespace(Namespace); CodeNamespace tempNs = new CodeNamespace(ns.Name);
tempNs.Imports.AddRange(ns.Imports.Cast<CodeNamespaceImport>().ToArray());
ccu.Namespaces.Add(tempNs); ccu.Namespaces.Add(tempNs);
foreach (var type in ns.Types.OfType<CodeTypeDeclaration>()) { foreach (var type in ns.Types.OfType<CodeTypeDeclaration>()) {
tempNs.Types.Add(type); tempNs.Types.Add(type);
Log($"Writing out {(type.IsStruct ? "struct" : "class")} {type.Name}"); Log($"Writing out {(type.IsStruct ? "struct" : "class")} {type.Name}");
using var sw = new StreamWriter($"{OutPath}\\{type.Name}.cs"); using var sw = new StreamWriter(GetOutputPath(ns, type.Name));
provider.GenerateCodeFromCompileUnit(ccu, sw, codeGeneratorOptions); provider.GenerateCodeFromCompileUnit(ccu, sw, codeGeneratorOptions);
tempNs.Types.Remove(type); tempNs.Types.Remove(type);
} }
if (!outputExtraTypes) return;
customTypes.ExceptWith(ns.Types.OfType<CodeTypeDeclaration>().Select(t => t.Name)); customTypes.ExceptWith(ns.Types.OfType<CodeTypeDeclaration>().Select(t => t.Name));
foreach (var type in customTypes) { foreach (var type in customTypes) {
Log($"Creating empty struct for missing {type}"); Log($"Creating empty struct for missing {type}");
@@ -269,8 +472,12 @@ namespace CodeGenerator {
}; };
tempNs.Types.Add(ctd); tempNs.Types.Add(ctd);
} }
using var finalsw = new StreamWriter($"{OutPath}\\_ExtraTypes.cs"); using var finalsw = new StreamWriter(GetOutputPath(ns, "_ExtraTypes"));
provider.GenerateCodeFromCompileUnit(ccu, finalsw, codeGeneratorOptions); provider.GenerateCodeFromCompileUnit(ccu, finalsw, codeGeneratorOptions);
} }
private static string GetOutputPath(CodeNamespace ns, string typeName) {
return $"{OutPath}\\{string.Concat(ns.Name.Split('.').Skip(1))}\\{typeName}.cs";
}
} }
} }