Files

541 lines
22 KiB
C#
Raw Permalink Normal View History

using EpicGames.UHT.Types;
using System;
using System.Collections.Generic;
using System.Text;
using UnrealSharpScriptGenerator.PropertyTranslators;
using UnrealSharpScriptGenerator.Tooltip;
using UnrealSharpScriptGenerator.Utilities;
namespace UnrealSharpScriptGenerator.Exporters;
public static class StructExporter
{
public static void ExportStruct(UhtScriptStruct structObj, bool isManualExport)
{
GeneratorStringBuilder stringBuilder = new();
List<UhtProperty> exportedProperties = new();
Dictionary<UhtProperty, GetterSetterPair> getSetBackedProperties = new();
List<UhtStruct> inheritanceHierarchy = new();
UhtStruct? currentStruct = structObj;
while (currentStruct is not null)
{
inheritanceHierarchy.Add(currentStruct);
currentStruct = currentStruct.SuperStruct;
}
inheritanceHierarchy.Reverse();
foreach (UhtStruct inheritance in inheritanceHierarchy)
{
ScriptGeneratorUtilities.GetExportedProperties(inheritance, exportedProperties, getSetBackedProperties);
}
// Check there are not properties with the same name, remove otherwise
List<string> propertyNames = new();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetParameterName();
if (propertyNames.Contains(scriptName))
{
exportedProperties.RemoveAt(i);
i--;
}
else
{
propertyNames.Add(scriptName);
}
}
bool nullableEnabled = structObj.HasMetadata(UhtTypeUtilities.NullableEnable);
bool isRecordStruct = structObj.HasMetadata("RecordStruct");
bool isReadOnly = structObj.HasMetadata("ReadOnly");
bool useProperties = structObj.HasMetadata("UseProperties");
bool isBlittable = structObj.IsStructBlittable();
bool isCopyable = structObj.IsStructNativelyCopyable();
bool isDestructible = structObj.IsStructNativelyDestructible();
bool isEquatable = structObj.IsStructEquatable(exportedProperties);
string typeNameSpace = structObj.GetNamespace();
stringBuilder.GenerateTypeSkeleton(typeNameSpace, isBlittable, nullableEnabled);
stringBuilder.AppendTooltip(structObj);
AttributeBuilder attributeBuilder = new AttributeBuilder(structObj);
if (isBlittable)
{
attributeBuilder.AddIsBlittableAttribute();
attributeBuilder.AddStructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential);
}
attributeBuilder.AddGeneratedTypeAttribute(structObj);
attributeBuilder.Finish();
stringBuilder.AppendLine(attributeBuilder.ToString());
string structName = structObj.GetStructName();
List<string>? csInterfaces = null;
if (isBlittable || !isManualExport)
{
csInterfaces = new List<string> { $"MarshalledStruct<{structName}>" };
if (isDestructible)
{
csInterfaces.Add("IDisposable");
}
}
if (isEquatable)
{
// If null create the list and add the interface
(csInterfaces ??= new()).Add($"IEquatable<{structName}>");
}
stringBuilder.DeclareType(structObj, isRecordStruct ? "record struct" : "struct", structName, csInterfaces: csInterfaces,
modifiers: isReadOnly ? " readonly" : null);
if (isCopyable)
{
stringBuilder.AppendLine(isDestructible
? "private NativeStructHandle NativeHandle;"
: "private byte[] Allocation;");
}
// For manual exports we just want to generate attributes
if (!isManualExport)
{
List<string> reservedNames = GetReservedNames(exportedProperties);
ExportStructProperties(structObj, stringBuilder, exportedProperties, isBlittable, reservedNames, isReadOnly, useProperties);
}
if (isBlittable)
{
StaticConstructorUtilities.ExportStaticConstructor(stringBuilder, structObj,
new List<UhtProperty>(),
new List<UhtFunction>(),
new Dictionary<string, GetterSetterPair>(),
new Dictionary<UhtProperty, GetterSetterPair>(),
new List<UhtFunction>(),
true);
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} FromNative(IntPtr buffer) => BlittableMarshaller<{structName}>.FromNative(buffer, 0);");
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public void ToNative(IntPtr buffer) => BlittableMarshaller<{structName}>.ToNative(buffer, 0, this);");
}
else if (!isManualExport)
{
stringBuilder.AppendLine();
StaticConstructorUtilities.ExportStaticConstructor(stringBuilder, structObj, exportedProperties,
new List<UhtFunction>(),
new Dictionary<string, GetterSetterPair>(),
new Dictionary<UhtProperty, GetterSetterPair>(),
new List<UhtFunction>());
stringBuilder.AppendLine();
ExportMirrorStructMarshalling(stringBuilder, structObj, exportedProperties);
if (isDestructible)
{
stringBuilder.AppendLine();
stringBuilder.AppendLine("public void Dispose()");
stringBuilder.OpenBrace();
stringBuilder.AppendLine("NativeHandle?.Dispose();");
stringBuilder.CloseBrace();
}
}
if (isEquatable)
{
ExportStructEquality(structObj, structName, stringBuilder, exportedProperties);
}
if (structObj.CanSupportArithmetic(exportedProperties))
{
ExportStructArithmetic(structObj, structName, stringBuilder, exportedProperties);
}
stringBuilder.CloseBrace();
if (!isBlittable && !isManualExport)
{
ExportStructMarshaller(stringBuilder, structObj);
}
FileExporter.SaveGlueToDisk(structObj, stringBuilder);
}
public static void ExportStructEquality(UhtStruct structObj, string structName, GeneratorStringBuilder stringBuilder, List<UhtProperty> exportedProperties)
{
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public override bool Equals(object? obj)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return obj is {structName} other && Equals(other);");
stringBuilder.CloseBrace();
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public bool Equals({structName} other)");
stringBuilder.OpenBrace();
if (exportedProperties.Count == 0)
{
stringBuilder.AppendLine("return true;");
}
else
{
StringBuilder equalitySb = new StringBuilder();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
equalitySb.Append($"this.{scriptName} == other.{scriptName}");
if (i < exportedProperties.Count - 1)
{
equalitySb.Append(" && ");
}
}
stringBuilder.AppendLine($"return {equalitySb};");
}
stringBuilder.CloseBrace();
stringBuilder.AppendLine();
stringBuilder.AppendLine("public override int GetHashCode()");
stringBuilder.OpenBrace();
if (exportedProperties.Count == 0)
{
stringBuilder.AppendLine("return 0;");
}
// More accurate hashcode equality
else if (exportedProperties.Count <= 8)
{
StringBuilder hashSb = new StringBuilder();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
hashSb.Append($"{scriptName}");
if (i < exportedProperties.Count - 1)
{
hashSb.Append(", ");
}
}
stringBuilder.AppendLine($"return HashCode.Combine({hashSb});");
}
// Fallback to xor for more than 8 properties as HashCode.Combine only supports up to 8 parameters
else
{
StringBuilder hashSb = new StringBuilder();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
hashSb.Append($"{scriptName}.GetHashCode()");
if (i < exportedProperties.Count - 1)
{
hashSb.Append(" ^ ");
}
}
stringBuilder.AppendLine($"return {hashSb};");
}
stringBuilder.CloseBrace();
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static bool operator ==({structName} left, {structName} right)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine("return left.Equals(right);");
stringBuilder.CloseBrace();
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static bool operator !=({structName} left, {structName} right)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine("return !(left == right);");
stringBuilder.CloseBrace();
}
public static void ExportStructArithmetic(UhtStruct structObj, string structName, GeneratorStringBuilder stringBuilder, List<UhtProperty> exportedProperties)
{
// Addition operator
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} operator +({structName} lhs, {structName} rhs)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return new {structName}");
stringBuilder.OpenBrace();
stringBuilder.AppendLine();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyArithmetic(stringBuilder, property, ArithmeticKind.Add);
if (i < exportedProperties.Count - 1)
{
stringBuilder.Append(", ");
stringBuilder.AppendLine();
}
}
stringBuilder.UnIndent();
stringBuilder.AppendLine("};");
stringBuilder.CloseBrace();
// Subtraction operator
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} operator -({structName} lhs, {structName} rhs)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return new {structName}");
stringBuilder.OpenBrace();
stringBuilder.AppendLine();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyArithmetic(stringBuilder, property, ArithmeticKind.Subtract);
if (i < exportedProperties.Count - 1)
{
stringBuilder.Append(", ");
stringBuilder.AppendLine();
}
}
stringBuilder.UnIndent();
stringBuilder.AppendLine("};");
stringBuilder.CloseBrace();
// Multiplication operator
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} operator *({structName} lhs, {structName} rhs)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return new {structName}");
stringBuilder.OpenBrace();
stringBuilder.AppendLine();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyArithmetic(stringBuilder, property, ArithmeticKind.Multiply);
if (i < exportedProperties.Count - 1)
{
stringBuilder.Append(", ");
stringBuilder.AppendLine();
}
}
stringBuilder.UnIndent();
stringBuilder.AppendLine("};");
stringBuilder.CloseBrace();
// Division operator
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} operator /({structName} lhs, {structName} rhs)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return new {structName}");
stringBuilder.OpenBrace();
stringBuilder.AppendLine();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyArithmetic(stringBuilder, property, ArithmeticKind.Divide);
if (i < exportedProperties.Count - 1)
{
stringBuilder.Append(", ");
stringBuilder.AppendLine();
}
}
stringBuilder.UnIndent();
stringBuilder.AppendLine("};");
stringBuilder.CloseBrace();
// Modulo operator
stringBuilder.AppendLine();
stringBuilder.AppendLine($"public static {structName} operator %({structName} lhs, {structName} rhs)");
stringBuilder.OpenBrace();
stringBuilder.AppendLine($"return new {structName}");
stringBuilder.OpenBrace();
stringBuilder.AppendLine();
for (int i = 0; i < exportedProperties.Count; i++)
{
UhtProperty property = exportedProperties[i];
string scriptName = property.GetPropertyName();
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyArithmetic(stringBuilder, property, ArithmeticKind.Modulo);
if (i < exportedProperties.Count - 1)
{
stringBuilder.Append(", ");
stringBuilder.AppendLine();
}
}
stringBuilder.UnIndent();
stringBuilder.AppendLine("};");
stringBuilder.CloseBrace();
}
public static void ExportStructProperties(UhtStruct structObj, GeneratorStringBuilder stringBuilder, List<UhtProperty> exportedProperties, bool suppressOffsets, List<string> reservedNames, bool isReadOnly, bool useProperties)
{
foreach (UhtProperty property in exportedProperties)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportMirrorProperty(structObj, stringBuilder, property, suppressOffsets, reservedNames, isReadOnly, useProperties);
}
}
public static List<string> GetReservedNames(List<UhtProperty> properties)
{
List<string> reservedNames = new();
foreach (UhtProperty property in properties)
{
if (reservedNames.Contains(property.SourceName))
{
continue;
}
reservedNames.Add(property.SourceName);
}
return reservedNames;
}
public static void ExportStructMarshaller(GeneratorStringBuilder builder, UhtScriptStruct structObj)
{
string structName = structObj.GetStructName();
builder.AppendLine();
builder.AppendLine($"public static class {structName}Marshaller");
builder.OpenBrace();
builder.AppendLine($"public static {structName} FromNative(IntPtr nativeBuffer, int arrayIndex)");
builder.OpenBrace();
builder.AppendLine($"return new {structName}(nativeBuffer + (arrayIndex * GetNativeDataSize()));");
builder.CloseBrace();
builder.AppendLine();
builder.AppendLine($"public static void ToNative(IntPtr nativeBuffer, int arrayIndex, {structName} obj)");
builder.OpenBrace();
builder.AppendLine($"obj.ToNative(nativeBuffer + (arrayIndex * GetNativeDataSize()));");
builder.CloseBrace();
builder.AppendLine();
builder.AppendLine($"public static int GetNativeDataSize()");
builder.OpenBrace();
builder.AppendLine($"return {structName}.NativeDataSize;");
builder.CloseBrace();
builder.CloseBrace();
}
public static void ExportMirrorStructMarshalling(GeneratorStringBuilder builder, UhtScriptStruct structObj, List<UhtProperty> properties)
{
string structName = structObj.GetStructName();
bool isCopyable = structObj.IsStructNativelyCopyable();
bool isDestructible = structObj.IsStructNativelyDestructible();
if (isCopyable)
{
builder.AppendLine();
builder.AppendLine($"public {structName}()");
builder.OpenBrace();
builder.AppendLine(isDestructible
? "NativeHandle = new NativeStructHandle(NativeClassPtr);"
: "Allocation = new byte[NativeDataSize];");
builder.CloseBrace();
}
builder.AppendLine();
builder.AppendLine("[System.Diagnostics.CodeAnalysis.SetsRequiredMembers]");
builder.AppendLine($"public {structName}(IntPtr InNativeStruct)");
builder.OpenBrace();
builder.BeginUnsafeBlock();
if (isCopyable)
{
if (isDestructible)
{
builder.AppendLine("NativeHandle = new NativeStructHandle(NativeClassPtr);");
builder.AppendLine("fixed (NativeStructHandleData* StructDataPointer = &NativeHandle.Data)");
builder.OpenBrace();
builder.AppendLine($"IntPtr AllocationPointer = {ExporterCallbacks.UScriptStructCallbacks}.CallGetStructLocation(StructDataPointer, NativeClassPtr);");
}
else
{
builder.AppendLine("Allocation = new byte[NativeDataSize];");
builder.AppendLine("fixed (byte* AllocationPointer = Allocation)");
builder.OpenBrace();
}
builder.AppendLine($"{ExporterCallbacks.UScriptStructCallbacks}.CallNativeCopy(NativeClassPtr, InNativeStruct, (nint) AllocationPointer);");
builder.CloseBrace();
}
else
{
foreach (UhtProperty property in properties)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
string scriptName = property.GetPropertyName();
string assignmentOrReturn = $"{scriptName} =";
string offsetName = $"{property.SourceName}_Offset";
builder.TryAddWithEditor(property);
translator.ExportFromNative(builder, property, property.SourceName, assignmentOrReturn, "InNativeStruct", offsetName, false, false);
builder.TryEndWithEditor(property);
}
}
builder.EndUnsafeBlock();
builder.CloseBrace();
builder.AppendLine();
builder.AppendLine($"public static {structName} FromNative(IntPtr buffer) => new {structName}(buffer);");
builder.AppendLine();
builder.AppendLine("public void ToNative(IntPtr buffer)");
builder.OpenBrace();
builder.BeginUnsafeBlock();
if (structObj.IsStructNativelyCopyable())
{
if (structObj.IsStructNativelyDestructible())
{
builder.AppendLine("if (NativeHandle is null)");
builder.OpenBrace();
builder.AppendLine("NativeHandle = new NativeStructHandle(NativeClassPtr);");
builder.CloseBrace();
builder.AppendLine();
builder.AppendLine("fixed (NativeStructHandleData* StructDataPointer = &NativeHandle.Data)");
builder.OpenBrace();
builder.AppendLine($"IntPtr AllocationPointer = {ExporterCallbacks.UScriptStructCallbacks}.CallGetStructLocation(StructDataPointer, NativeClassPtr);");
}
else
{
builder.AppendLine("if (Allocation is null)");
builder.OpenBrace();
builder.AppendLine("Allocation = new byte[NativeDataSize];");
builder.AppendLine();
builder.CloseBrace();
builder.AppendLine("fixed (byte* AllocationPointer = Allocation)");
builder.OpenBrace();
}
builder.AppendLine($"{ExporterCallbacks.UScriptStructCallbacks}.CallNativeCopy(NativeClassPtr, (nint) AllocationPointer, buffer);");
builder.CloseBrace();
}
else
{
foreach (UhtProperty property in properties)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
string scriptName = property.GetPropertyName();
string offsetName = $"{property.SourceName}_Offset";
builder.TryAddWithEditor(property);
translator.ExportToNative(builder, property, property.SourceName, "buffer", offsetName, scriptName);
builder.TryEndWithEditor(property);
}
}
builder.EndUnsafeBlock();
builder.CloseBrace();
}
}