Lua向C#逻辑迁移 一期 #13

将整个插件代码上传
This commit is contained in:
2025-10-26 21:48:39 +08:00
parent 56994b3927
commit 648386cd73
785 changed files with 53683 additions and 2 deletions

View File

@ -0,0 +1,138 @@
using System;
using System.Text;
using EpicGames.Core;
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public class AttributeBuilder
{
private readonly StringBuilder _stringBuilder;
private AttributeState _state;
public AttributeBuilder()
{
_stringBuilder = new StringBuilder("[");
_state = AttributeState.Open;
}
public AttributeBuilder(UhtType type) : this()
{
AddAttribute(GetAttributeForType(type));
}
public void AddGeneratedTypeAttribute(UhtType type)
{
AddAttribute("GeneratedType");
AddArgument($"\"{type.EngineName}\"");
string fullName = type.GetNamespace() + "." + type.EngineName;
AddArgument($"\"{fullName}\"");
}
public void AddIsBlittableAttribute()
{
AddAttribute("BlittableType");
}
public void AddStructLayoutAttribute(System.Runtime.InteropServices.LayoutKind layoutKind)
{
AddAttribute("StructLayout");
AddArgument($"LayoutKind.{layoutKind}");
}
private static string GetAttributeForType(UhtType type)
{
if (type is UhtClass uhtClass)
{
return uhtClass.HasAllFlags(EClassFlags.Interface) ? "UInterface" : "UClass";
}
if (type is UhtScriptStruct)
{
return "UStruct";
}
if (type is UhtEnum)
{
return "UEnum";
}
if (type is UhtFunction)
{
return "UFunction";
}
throw new InvalidOperationException("Invalid type");
}
public void AddAttribute(string attributeName)
{
switch (_state)
{
case AttributeState.Open:
break;
case AttributeState.InAttribute:
_stringBuilder.Append(", ");
break;
case AttributeState.InAttributeParams:
_stringBuilder.Append("), ");
break;
default:
throw new InvalidOperationException("Invalid state");
}
_stringBuilder.Append(attributeName);
_state = AttributeState.InAttribute;
}
public void AddArgument(string arg)
{
switch (_state)
{
case AttributeState.InAttribute:
_stringBuilder.Append("(");
break;
case AttributeState.InAttributeParams:
_stringBuilder.Append(", ");
break;
default:
throw new InvalidOperationException("Invalid state");
}
_stringBuilder.Append(arg);
_state = AttributeState.InAttributeParams;
}
public void Finish()
{
switch (_state)
{
case AttributeState.InAttribute:
_stringBuilder.Append("]");
break;
case AttributeState.InAttributeParams:
_stringBuilder.Append(")]");
break;
default:
throw new InvalidOperationException("Invalid state");
}
_state = AttributeState.Closed;
}
public override string ToString()
{
if (_state != AttributeState.Closed)
{
throw new InvalidOperationException("Cannot convert to string. The builder is not in the closed state.");
}
return _stringBuilder.ToString();
}
private enum AttributeState
{
Open,
Closed,
InAttribute,
InAttributeParams
}
}

View File

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using EpicGames.Core;
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public static class ClassUtilities
{
public static UhtFunction? FindFunctionByName(this UhtClass classObj, string functionName, Func<UhtFunction, string, bool>? customCompare = null, bool includeSuper = false)
=> FindTypeInHierarchy(classObj, c => c.Functions, functionName, customCompare, includeSuper);
public static UhtProperty? FindPropertyByName(this UhtClass classObj, string propertyName, Func<UhtProperty, string, bool>? customCompare = null, bool includeSuper = false)
=> FindTypeInHierarchy(classObj, c => c.Properties, propertyName, customCompare, includeSuper);
private static T? FindTypeInHierarchy<T>(UhtClass? classObj, Func<UhtClass, IEnumerable<T>> selector,
string typeName, Func<T, string, bool>? customCompare, bool includeSuper) where T : UhtType
{
for (UhtClass? current = classObj; current != null; current = includeSuper ? current.SuperClass : null)
{
T? match = FindTypeByName(typeName, selector(current), customCompare);
if (match != null)
{
return match;
}
if (!includeSuper)
{
break;
}
}
return null;
}
private static T? FindTypeByName<T>(string typeName, IEnumerable<T> types, Func<T, string, bool>? customCompare = null) where T : UhtType
{
foreach (var type in types)
{
if ((customCompare != null && customCompare(type, typeName)) ||
string.Equals(type.SourceName, typeName, StringComparison.InvariantCultureIgnoreCase))
{
return type;
}
}
return null;
}
public static UhtClass? GetInterfaceAlternateClass(this UhtClass thisInterface)
{
if (thisInterface.EngineType is not (UhtEngineType.Interface or UhtEngineType.NativeInterface))
{
return null;
}
return thisInterface.AlternateObject as UhtClass;
}
public static bool HasAnyFlags(this UhtClass classObj, EClassFlags flags)
{
return (classObj.ClassFlags & flags) != 0;
}
public static bool HasAllFlags(this UhtClass classObj, EClassFlags flags)
{
return (classObj.ClassFlags & flags) == flags;
}
}

View File

@ -0,0 +1,15 @@
namespace UnrealSharpScriptGenerator.Utilities;
public static class ExporterCallbacks
{
public const string FPropertyCallbacks = "FPropertyExporter";
public const string UClassCallbacks = "UClassExporter";
public const string CoreUObjectCallbacks = "UCoreUObjectExporter";
public const string FBoolPropertyCallbacks = "FBoolPropertyExporter";
public const string FStringCallbacks = "FStringExporter";
public const string UObjectCallbacks = "UObjectExporter";
public const string UStructCallbacks = "UStructExporter";
public const string FArrayPropertyCallbacks = "FArrayPropertyExporter";
public const string UScriptStructCallbacks = "UScriptStructExporter";
public const string UFunctionCallbacks = "UFunctionExporter";
}

View File

@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public readonly struct ProjectDirInfo
{
private readonly string _projectName;
private readonly string _projectDirectory;
public HashSet<string>? Dependencies { get; }
public ProjectDirInfo(string projectName, string projectDirectory, HashSet<string>? dependencies = null)
{
_projectName = projectName;
_projectDirectory = projectDirectory;
Dependencies = dependencies;
}
public string GlueProjectName => $"{_projectName}.Glue";
public string GlueProjectName_LEGACY => $"{_projectName}.PluginGlue";
public string GlueProjectFile => $"{GlueProjectName}.csproj";
public string ScriptDirectory => Path.Combine(_projectDirectory, "Script");
public string GlueCsProjPath => Path.Combine(GlueProjectDirectory, GlueProjectFile);
public bool IsUProject => _projectDirectory.EndsWith(".uproject", StringComparison.OrdinalIgnoreCase);
public bool IsPartOfEngine => _projectName == "Engine";
public string GlueProjectDirectory => Path.Combine(ScriptDirectory, GlueProjectName);
public string GlueProjectDirectory_LEGACY => Path.Combine(ScriptDirectory, GlueProjectName_LEGACY);
public string ProjectRoot => _projectDirectory;
}
public static class FileExporter
{
private static readonly ReaderWriterLockSlim ReadWriteLock = new();
private static readonly List<string> ChangedFiles = new();
private static readonly List<string> UnchangedFiles = new();
public static void SaveGlueToDisk(UhtType type, GeneratorStringBuilder stringBuilder)
{
string directory = GetDirectoryPath(type.Package);
SaveGlueToDisk(type.Package, directory, type.EngineName, stringBuilder.ToString());
}
public static string GetFilePath(string typeName, string directory)
{
return Path.Combine(directory, $"{typeName}.generated.cs");
}
public static void SaveGlueToDisk(UhtPackage package, string directory, string typeName, string text)
{
string absoluteFilePath = GetFilePath(typeName, directory);
bool directoryExists = Directory.Exists(directory);
bool glueExists = File.Exists(absoluteFilePath);
ReadWriteLock.EnterWriteLock();
try
{
bool matchingGlue = glueExists && File.ReadAllText(absoluteFilePath) == text;
// If the directory exists and the file exists with the same text, we can return early
if (directoryExists && matchingGlue)
{
UnchangedFiles.Add(absoluteFilePath);
return;
}
if (!directoryExists)
{
Directory.CreateDirectory(directory);
}
File.WriteAllText(absoluteFilePath, text);
ChangedFiles.Add(absoluteFilePath);
if (package.IsPartOfEngine())
{
CSharpExporter.HasModifiedEngineGlue = true;
}
}
finally
{
ReadWriteLock.ExitWriteLock();
}
}
public static void AddUnchangedType(UhtType type)
{
string directory = GetDirectoryPath(type.Package);
string filePath = GetFilePath(type.EngineName, directory);
UnchangedFiles.Add(filePath);
if (type is UhtStruct uhtStruct && uhtStruct.Functions.Any(f => f.HasMetadata("ExtensionMethod")))
{
UnchangedFiles.Add(GetFilePath($"{type.EngineName}_Extensions", directory));
}
}
public static string GetDirectoryPath(UhtPackage package)
{
if (package == null)
{
throw new InvalidOperationException("Package is null");
}
string rootPath = GetGluePath(package);
return Path.Combine(rootPath, package.GetShortName());
}
public static string GetGluePath(UhtPackage package)
{
ProjectDirInfo projectDirInfo = package.FindOrAddProjectInfo();
return projectDirInfo.GlueProjectDirectory;
}
public static void CleanOldExportedFiles()
{
Console.WriteLine("Cleaning up old generated C# glue files...");
CleanFilesInDirectories(Program.EngineGluePath);
CleanFilesInDirectories(Program.ProjectGluePath_LEGACY, true);
foreach (ProjectDirInfo pluginDirectory in Program.PluginDirs)
{
CleanFilesInDirectories(pluginDirectory.GlueProjectDirectory, true);
CleanFilesInDirectories(pluginDirectory.GlueProjectDirectory_LEGACY, true);
}
}
public static void CleanModuleFolders()
{
CleanGeneratedFolder(Program.EngineGluePath);
CleanGeneratedFolder(Program.ProjectGluePath_LEGACY);
foreach (ProjectDirInfo pluginDirectory in Program.PluginDirs)
{
CleanGeneratedFolder(pluginDirectory.GlueProjectDirectory);
CleanGeneratedFolder(pluginDirectory.GlueProjectDirectory_LEGACY);
}
}
public static void CleanGeneratedFolder(string path)
{
if (!Directory.Exists(path))
{
return;
}
HashSet<string> ignoredDirectories = GetIgnoredDirectories(path);
// TODO: Move runtime glue to a separate csproj. So we can fully clean the ProjectGlue folder.
// Below is a temporary solution to not delete runtime glue that can cause compilation errors on editor startup,
// and avoid having to restore nuget packages.
string[] directories = Directory.GetDirectories(path);
foreach (string directory in directories)
{
if (IsIntermediateDirectory(directory) || ignoredDirectories.Contains(Path.GetRelativePath(path, directory)))
{
continue;
}
Directory.Delete(directory, true);
}
}
private static HashSet<string> GetIgnoredDirectories(string path)
{
string glueIgnoreFileName = Path.Combine(path, ".glueignore");
if (!File.Exists(glueIgnoreFileName))
{
return new HashSet<string>(StringComparer.OrdinalIgnoreCase);
}
HashSet<string> ignoredDirectories = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
using StreamReader fileInput = File.OpenText(glueIgnoreFileName);
while (!fileInput.EndOfStream)
{
string? line = fileInput.ReadLine();
if (string.IsNullOrWhiteSpace(line)) continue;
ignoredDirectories.Add(line.Trim());
}
return ignoredDirectories;
}
private static void CleanFilesInDirectories(string path, bool recursive = false)
{
if (!Directory.Exists(path))
{
return;
}
string[] directories = Directory.GetDirectories(path);
HashSet<string> ignoredDirectories = GetIgnoredDirectories(path);
foreach (var directory in directories)
{
if (ignoredDirectories.Contains(Path.GetRelativePath(path, directory)))
{
continue;
}
string moduleName = Path.GetFileName(directory);
if (!CSharpExporter.HasBeenExported(moduleName))
{
continue;
}
int removedFiles = 0;
string[] files = Directory.GetFiles(directory);
foreach (var file in files)
{
if (ChangedFiles.Contains(file) || UnchangedFiles.Contains(file))
{
continue;
}
File.Delete(file);
removedFiles++;
}
if (removedFiles == files.Length)
{
Directory.Delete(directory, recursive);
}
}
}
static bool IsIntermediateDirectory(string path)
{
string directoryName = Path.GetFileName(path);
return directoryName is "obj" or "bin" or "Properties";
}
}

View File

@ -0,0 +1,306 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EpicGames.Core;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.Exporters;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public static class FunctionUtilities
{
public static bool HasAnyFlags(this UhtFunction function, EFunctionFlags flags)
{
return (function.FunctionFlags & flags) != 0;
}
public static bool HasAllFlags(this UhtFunction function, EFunctionFlags flags)
{
return (function.FunctionFlags & flags) == flags;
}
public static bool IsInterfaceFunction(this UhtFunction function)
{
if (function.Outer is not UhtClass classOwner)
{
return false;
}
if (classOwner.HasAnyFlags(EClassFlags.Interface))
{
return true;
}
string sourceName;
if (function.SourceName.EndsWith("_Implementation"))
{
sourceName = function.SourceName.Substring(0, function.SourceName.Length - 15);
}
else
{
sourceName = function.SourceName;
}
UhtClass? currentClass = classOwner;
while (currentClass != null)
{
foreach (UhtClass currentInterface in currentClass.GetInterfaces())
{
UhtClass? interfaceClass = currentInterface.GetInterfaceAlternateClass();
if (interfaceClass != null && interfaceClass.FindFunctionByName(sourceName) != null)
{
return true;
}
}
currentClass = currentClass.Super as UhtClass;
}
return false;
}
public static bool HasOutParams(this UhtFunction function)
{
// Multicast delegates can have out params, but the UFunction flag isn't set.
foreach (UhtProperty param in function.Properties)
{
if (param.HasAnyFlags(EPropertyFlags.OutParm))
{
return true;
}
}
return false;
}
public static bool HasParametersOrReturnValue(this UhtFunction function)
{
return function.HasParameters || function.ReturnProperty != null;
}
public static string GetNativeFunctionName(this UhtFunction function)
{
return $"{function.SourceName}_NativeFunction";
}
public static bool HasSameSignature(this UhtFunction function, UhtFunction otherFunction)
{
if (function.Children.Count != otherFunction.Children.Count)
{
return false;
}
for (int i = 0; i < function.Children.Count; i++)
{
UhtProperty param = (UhtProperty) function.Children[i];
UhtProperty otherParam = (UhtProperty) otherFunction.Children[i];
if (!param.IsSameType(otherParam))
{
return false;
}
}
return true;
}
public static bool IsAutocast(this UhtFunction function)
{
if (!function.FunctionFlags.HasAllFlags(EFunctionFlags.Static) || function.ReturnProperty == null || function.Children.Count != 2)
{
return false;
}
if (function.Properties.First() is not UhtStructProperty)
{
return false;
}
// These will be interfaces in C#, which implicit conversion doesn't work for.
// TODO: Support these in the future.
UhtProperty returnProperty = function.ReturnProperty!;
if (returnProperty is UhtArrayProperty or UhtSetProperty or UhtMapProperty)
{
return false;
}
if (function.HasMetadata("BlueprintAutocast"))
{
return true;
}
string sourceName = function.SourceName;
return sourceName.StartsWith("Conv_", StringComparison.OrdinalIgnoreCase) || sourceName.StartsWith("To");
}
public static string GetBlueprintAutocastName(this UhtFunction function)
{
int toTypeIndex = function.SourceName.IndexOf("Conv_", StringComparison.Ordinal);
return toTypeIndex == -1 ? function.SourceName : function.SourceName.Substring(toTypeIndex + 5);
}
private static bool IsBlueprintAccessor(this UhtFunction function, string accessorType, Func<UhtProperty, UhtFunction?> getBlueprintAccessor)
{
if (function.Properties.Count() != 1 )
{
return false;
}
if (function.HasMetadata(accessorType))
{
return true;
}
if (function.Outer is not UhtClass classObj)
{
return false;
}
foreach (UhtProperty property in classObj.Properties)
{
if (function != getBlueprintAccessor(property)! || !function.VerifyBlueprintAccessor(property))
{
continue;
}
return true;
}
return false;
}
public static bool VerifyBlueprintAccessor(this UhtFunction function, UhtProperty property)
{
if (!function.Properties.Any() || function.Properties.Count() != 1)
{
return false;
}
UhtProperty firstProperty = function.Properties.First();
return firstProperty.IsSameType(property);
}
public static bool IsNativeAccessor(this UhtFunction function, GetterSetterMode accessorType)
{
UhtClass classObj = (function.Outer as UhtClass)!;
foreach (UhtProperty property in classObj.Properties)
{
if (accessorType + property.EngineName == function.SourceName)
{
switch (accessorType)
{
case GetterSetterMode.Get:
return property.HasNativeGetter();
case GetterSetterMode.Set:
return property.HasNativeSetter();
}
}
}
return false;
}
public static bool IsAnyGetter(this UhtFunction function)
{
if (function.Properties.Count() != 1)
{
return false;
}
return function.IsBlueprintAccessor("BlueprintGetter", property => property.GetBlueprintGetter())
|| function.IsNativeAccessor(GetterSetterMode.Get);
}
public static bool IsAnySetter(this UhtFunction function)
{
if (function.Properties.Count() != 1)
{
return false;
}
return function.IsBlueprintAccessor("BlueprintSetter", property => property.GetBlueprintSetter())
|| function.IsNativeAccessor(GetterSetterMode.Set);
}
public static bool HasGenericTypeSupport(this UhtFunction function)
{
if (!function.HasMetadata("DeterminesOutputType")) return false;
var propertyDOTEngineName = function.GetMetadata("DeterminesOutputType");
var propertyDeterminingOutputType = function.Properties
.Where(p => p.EngineName == propertyDOTEngineName)
.FirstOrDefault();
if (propertyDeterminingOutputType == null) return false;
PropertyTranslator dotParamTranslator = PropertyTranslatorManager.GetTranslator(propertyDeterminingOutputType)!;
if (!dotParamTranslator.CanSupportGenericType(propertyDeterminingOutputType)) return false;
if (function.HasMetadata("DynamicOutputParam"))
{
var propertyDynamicOutputParam = function.Properties
.Where(p => p.EngineName == function.GetMetadata("DynamicOutputParam"))
.FirstOrDefault();
if (propertyDynamicOutputParam == null) return false;
if (propertyDeterminingOutputType!.GetGenericManagedType() != propertyDynamicOutputParam.GetGenericManagedType()) return false;
PropertyTranslator dopParamTranslator = PropertyTranslatorManager.GetTranslator(propertyDynamicOutputParam)!;
return dopParamTranslator.CanSupportGenericType(propertyDynamicOutputParam);
}
else if (function.HasReturnProperty)
{
PropertyTranslator returnParamTranslator = PropertyTranslatorManager.GetTranslator(function.ReturnProperty!)!;
return returnParamTranslator.CanSupportGenericType(function.ReturnProperty!);
}
return false;
}
public static string GetGenericTypeConstraint(this UhtFunction function)
{
if (!function.HasMetadata("DeterminesOutputType")) return string.Empty;
var propertyDeterminingOutputType = function.Properties
.Where(p => p.EngineName == function.GetMetadata("DeterminesOutputType"))
.FirstOrDefault();
return propertyDeterminingOutputType?.GetGenericManagedType() ?? string.Empty;
}
public static bool HasCustomStructParamSupport(this UhtFunction function)
{
if (!function.HasMetadata("CustomStructureParam")) return false;
var customStructParams = function.GetCustomStructParams();
return customStructParams.All(customParamName =>
function.Properties.Count(param => param.EngineName == customParamName) == 1);
}
public static List<string> GetCustomStructParams(this UhtFunction function)
{
if (!function.HasMetadata("CustomStructureParam")) return new List<string>();
return function.GetMetadata("CustomStructureParam").Split(",").ToList();
}
public static int GetCustomStructParamCount(this UhtFunction function) => function.GetCustomStructParams().Count;
public static List<string> GetCustomStructParamTypes(this UhtFunction function)
{
if (!function.HasMetadata("CustomStructureParam")) return new List<string>();
int paramCount = function.GetCustomStructParamCount();
if (paramCount == 1) return new List<string> { "CSP" };
return Enumerable.Range(0, paramCount).ToList().ConvertAll(i => $"CSP{i}");
}
public static bool IsBlueprintNativeEvent(this UhtFunction function)
{
return function.HasAllFlags(EFunctionFlags.BlueprintEvent | EFunctionFlags.Native);
}
public static bool IsBlueprintImplementableEvent(this UhtFunction function)
{
return function.HasAllFlags(EFunctionFlags.BlueprintEvent) && !function.HasAllFlags(EFunctionFlags.Native);
}
}

View File

@ -0,0 +1,17 @@
using System.Collections.Generic;
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public static class HeaderUtilities
{
public static IEnumerable<UhtPackage> GetPackages(this UhtHeaderFile header)
{
#if UE_5_5_OR_LATER
return header.Module.Packages;
#else
return new [] { header.Package };
#endif
}
}

View File

@ -0,0 +1,308 @@
using System;
using System.Collections.Generic;
using EpicGames.Core;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public enum ENameType
{
Parameter,
Property,
Struct,
Function
}
public static class NameMapper
{
private static readonly List<string> ReservedKeywords = new()
{
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue",
"decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally",
"fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
"long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public",
"readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch",
"this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual",
"void", "volatile", "while", "System"
};
public static string GetParameterName(this UhtProperty property)
{
string scriptName = ScriptifyName(property.GetScriptName(), ENameType.Parameter);
if (property.Outer is not UhtFunction function)
{
return scriptName;
}
foreach (UhtProperty exportedProperty in function.Properties)
{
if (exportedProperty.HasAllFlags(EPropertyFlags.ReturnParm))
{
continue;
}
if (exportedProperty != property && scriptName == ScriptifyName(exportedProperty.GetScriptName(), ENameType.Parameter))
{
return PascalToCamelCase(exportedProperty.SourceName);
}
}
return scriptName;
}
public static string GetPropertyName(this UhtProperty property)
{
string propertyName = ScriptifyName(property.GetScriptName(), ENameType.Property);
if (property.Outer!.SourceName == propertyName || IsAKeyword(propertyName))
{
propertyName = $"K2_{propertyName}";
}
return TryResolveConflictingName(property, propertyName);
}
public static string GetStructName(this UhtType type)
{
if (type.EngineType is UhtEngineType.Interface or UhtEngineType.NativeInterface || type == Program.Factory.Session.UInterface)
{
return "I" + type.EngineName;
}
if (type is UhtClass uhtClass && uhtClass.IsChildOf(Program.BlueprintFunctionLibrary))
{
return type.GetScriptName();
}
return type.SourceName;
}
public static string ExportGetAssemblyName(this UhtType type)
{
string structName = type.GetStructName();
return $"typeof({structName}).GetAssemblyName()";
}
public static string GetFullManagedName(this UhtType type)
{
return $"{type.GetNamespace()}.{type.GetStructName()}";
}
static readonly string[] MetadataKeys = { "ScriptName", "ScriptMethod", "DisplayName" };
public static string GetScriptName(this UhtType type)
{
bool OnlyContainsLetters(string str)
{
foreach (char c in str)
{
if (!char.IsLetter(c) && !char.IsWhiteSpace(c))
{
return false;
}
}
return true;
}
foreach (var key in MetadataKeys)
{
string value = type.GetMetadata(key);
if (string.IsNullOrEmpty(value) || !OnlyContainsLetters(value))
{
continue;
}
// Try remove whitespace from the value
value = value.Replace(" ", "");
return value;
}
return type.SourceName;
}
public static string GetNamespace(this UhtType typeObj)
{
UhtType outer = typeObj;
string packageShortName = "";
if (outer is UhtPackage package)
{
packageShortName = package.GetShortName();
}
else
{
while (outer.Outer != null)
{
outer = outer.Outer;
if (outer is UhtPackage header)
{
packageShortName = header.Package.GetShortName();
break;
}
}
}
if (string.IsNullOrEmpty(packageShortName))
{
throw new Exception($"Failed to find package name for {typeObj}");
}
return $"UnrealSharp.{packageShortName}";
}
public static string GetFunctionName(this UhtFunction function)
{
string functionName = function.GetScriptName();
if (function.HasAnyFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate))
{
functionName = DelegateBasePropertyTranslator.GetDelegateName(function);
}
if (functionName.StartsWith("K2_") || functionName.StartsWith("BP_"))
{
functionName = functionName.Substring(3);
}
if (function.IsInterfaceFunction() && functionName.EndsWith("_Implementation"))
{
functionName = functionName.Substring(0, functionName.Length - 15);
}
if (function.Outer is not UhtClass)
{
return functionName;
}
functionName = TryResolveConflictingName(function, functionName);
return functionName;
}
public static string TryResolveConflictingName(UhtType type, string scriptName)
{
UhtType outer = type.Outer!;
bool IsConflictingWithChild(List<UhtType> children)
{
foreach (UhtType child in children)
{
if (child == type)
{
continue;
}
if (child is UhtProperty property)
{
if (scriptName == ScriptifyName(property.GetScriptName(), ENameType.Property))
{
return true;
}
}
if (child is UhtFunction function)
{
if (scriptName == ScriptifyName(function.GetScriptName(), ENameType.Function))
{
return true;
}
}
}
return false;
}
bool isConflicting = IsConflictingWithChild(outer.Children);
if (!isConflicting && outer is UhtClass outerClass)
{
List<UhtClass> classInterfaces = outerClass.GetInterfaces();
foreach (UhtClass classInterface in classInterfaces)
{
if (classInterface.AlternateObject is not UhtClass interfaceClass)
{
continue;
}
UhtFunction? function = interfaceClass.FindFunctionByName(scriptName, (uhtFunction, s) => uhtFunction.GetFunctionName() == s);
if (function != null && type is UhtFunction typeAsFunction)
{
if (function.HasSameSignature(typeAsFunction))
{
continue;
}
isConflicting = true;
break;
}
}
}
return isConflicting ? type.EngineName : scriptName;
}
public static string ScriptifyName(string engineName, ENameType nameType)
{
string strippedName = engineName;
switch (nameType)
{
case ENameType.Parameter:
strippedName = StripPropertyPrefix(strippedName);
strippedName = PascalToCamelCase(strippedName);
break;
case ENameType.Property:
strippedName = StripPropertyPrefix(strippedName);
break;
case ENameType.Struct:
break;
case ENameType.Function:
break;
default:
throw new ArgumentOutOfRangeException(nameof(nameType), nameType, null);
}
return EscapeKeywords(strippedName);
}
public static string StripPropertyPrefix(string inName)
{
int nameOffset = 0;
while (true)
{
// Strip the "b" prefix from bool names
if (inName.Length - nameOffset >= 2 && inName[nameOffset] == 'b' && char.IsUpper(inName[nameOffset + 1]))
{
nameOffset += 1;
continue;
}
// Strip the "In" prefix from names
if (inName.Length - nameOffset >= 3 && inName[nameOffset] == 'I' && inName[nameOffset + 1] == 'n' && char.IsUpper(inName[nameOffset + 2]))
{
nameOffset += 2;
continue;
}
break;
}
return nameOffset != 0 ? inName.Substring(nameOffset) : inName;
}
public static string EscapeKeywords(string name)
{
return IsAKeyword(name) || char.IsDigit(name[0]) ? $"_{name}" : name;
}
private static bool IsAKeyword(string name)
{
return ReservedKeywords.Contains(name);
}
private static string PascalToCamelCase(string name)
{
return char.ToLowerInvariant(name[0]) + name.Substring(1);
}
}

View File

@ -0,0 +1,74 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using EpicGames.Core;
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public static class PackageUtilities
{
public const string SkipGlueGenerationDefine = "SkipGlueGeneration";
public static string GetShortName(this UhtPackage package)
{
#if UE_5_5_OR_LATER
return package.Module.ShortName;
#else
return package.ShortName;
#endif
}
public static bool IsPartOfEngine(this UhtPackage package)
{
bool isPartOfEngine = false;
#if UE_5_5_OR_LATER
isPartOfEngine = package.Module.IsPartOfEngine;
#else
isPartOfEngine = package.IsPartOfEngine;
#endif
return isPartOfEngine || package.IsForcedAsEngineGlue();
}
public static bool IsPlugin(this UhtPackage package)
{
#if UE_5_5_OR_LATER
return package.Module.IsPlugin;
#else
return package.IsPlugin;
#endif
}
public static bool IsForcedAsEngineGlue(this UhtPackage package)
{
bool hasDefine = package.GetModule().TryGetDefine("ForceAsEngineGlue", out int treatedAsEngineGlue);
return hasDefine && treatedAsEngineGlue != 0;
}
public static UHTManifest.Module GetModule(this UhtPackage package)
{
#if UE_5_5_OR_LATER
return package.Module.Module;
#else
return package.Module;
#endif
}
public static bool ShouldExport(this UhtPackage package)
{
bool foundDefine = package.GetModule().PublicDefines.Contains(SkipGlueGenerationDefine);
return !foundDefine;
}
public static IReadOnlyCollection<UhtHeaderFile> GetHeaderFiles(this UhtPackage package)
{
#if UE_5_5_OR_LATER
return package.Module.Headers;
#else
return package.Children
.OfType<UhtHeaderFile>()
.ToList();
#endif
}
}

View File

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.Model;
namespace UnrealSharpScriptGenerator.Utilities;
public static class PluginUtilities
{
public static readonly Dictionary<UhtPackage, ProjectDirInfo> PluginInfo = new();
private static readonly Dictionary<string, string> ExtractedEngineModules = new();
static PluginUtilities()
{
string? projectDirectory = Program.Factory.Session.ProjectDirectory;
string pluginDirectory = Path.Combine(projectDirectory!, "Plugins");
DirectoryInfo pluginDirInfo = new DirectoryInfo(pluginDirectory);
IEnumerable<(string DirectoryName, string FullName)> files = pluginDirInfo.GetFiles("*.uplugin", SearchOption.AllDirectories)
.Select(x => x.DirectoryName!)
.Select(x => (DirectoryName: x, ConfigPath: Path.Combine(x, "Config")))
.Select(x => (x.DirectoryName, ConfigDir: new DirectoryInfo(x.ConfigPath)))
.Where(x => x.ConfigDir.Exists)
.SelectMany(x => x.ConfigDir.GetFiles("*.ExtractedModules.json", SearchOption.AllDirectories),
(x, y) => (x.DirectoryName, FileInfo: y))
.Select(x => (x.DirectoryName, x.FileInfo.FullName));
foreach ((string pluginDir, string pluginFile) in files)
{
using FileStream fileStream = File.OpenRead(pluginFile);
try
{
List<string>? manifest = JsonSerializer.Deserialize<List<string>>(fileStream);
foreach (string module in manifest!)
{
ExtractedEngineModules.Add($"/Script/{module}", pluginDir);
}
}
catch (JsonException e)
{
Console.WriteLine($"Error reading {pluginFile}: {e.Message}");
}
}
}
public static ProjectDirInfo FindOrAddProjectInfo(this UhtPackage package)
{
if (PluginInfo.TryGetValue(package, out ProjectDirInfo plugin))
{
return plugin;
}
ProjectDirInfo info;
HashSet<string> dependencies = [];
if (package.IsPartOfEngine())
{
if (ExtractedEngineModules.TryGetValue(package.SourceName, out string? pluginPath))
{
DirectoryInfo pluginDir = new(pluginPath);
info = new ProjectDirInfo(pluginDir.Name, pluginPath, dependencies);
}
else
{
info = new ProjectDirInfo("Engine", Program.EngineGluePath, dependencies);
}
}
else
{
string baseDirectory = package.GetModule().BaseDirectory;
DirectoryInfo? currentDirectory = new DirectoryInfo(baseDirectory);
FileInfo? projectFile = null;
while (currentDirectory is not null)
{
FileInfo[] foundFiles = currentDirectory.GetFiles("*.*", SearchOption.TopDirectoryOnly);
projectFile = foundFiles.FirstOrDefault(f =>
f.Extension.Equals(".uplugin", StringComparison.OrdinalIgnoreCase) ||
f.Extension.Equals(".uproject", StringComparison.OrdinalIgnoreCase));
if (projectFile is not null)
{
break;
}
currentDirectory = currentDirectory.Parent;
}
if (projectFile is null)
{
throw new InvalidOperationException(
$"Could not find .uplugin or .uproject file for package {package.SourceName} in {baseDirectory}");
}
info = new ProjectDirInfo(Path.GetFileNameWithoutExtension(projectFile.Name), currentDirectory!.FullName, dependencies);
}
PluginInfo.Add(package, info);
foreach (UhtHeaderFile header in package.GetHeaderFiles())
{
HashSet<UhtHeaderFile> referencedHeaders = header.References.ReferencedHeaders;
referencedHeaders.UnionWith(header.ReferencedHeadersNoLock);
foreach (UhtHeaderFile refHeader in referencedHeaders)
{
foreach (UhtPackage refPackage in refHeader.GetPackages())
{
if (refPackage == package)
{
continue;
}
if (refPackage.IsPartOfEngine())
{
if (!ExtractedEngineModules.TryGetValue(refPackage.SourceName, out string? pluginPath))
{
continue;
}
if (info.IsPartOfEngine)
{
DirectoryInfo pluginDir = new(pluginPath);
info = new ProjectDirInfo(pluginDir.Name, pluginPath, dependencies);
PluginInfo[package] = info;
}
}
ProjectDirInfo projectInfo = refPackage.FindOrAddProjectInfo();
if (info.GlueCsProjPath == projectInfo.GlueCsProjPath)
{
continue;
}
dependencies.Add(projectInfo.GlueCsProjPath);
}
}
}
return info;
}
}

View File

@ -0,0 +1,372 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.Metadata;
using EpicGames.Core;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.Exporters;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public static class PropertyUtilities
{
public static bool IsOuter<T>(this UhtProperty property)
{
return property.Outer is T;
}
public static bool HasAnyFlags(this UhtProperty property, EPropertyFlags flags)
{
return (property.PropertyFlags & flags) != 0;
}
public static bool HasAllFlags(this UhtProperty property, EPropertyFlags flags)
{
return (property.PropertyFlags & flags) == flags;
}
public static string GetMetaData(this UhtProperty property, string key)
{
return property.MetaData.TryGetValue(key, out var value) ? value : string.Empty;
}
public static bool HasMetaData(this UhtProperty property, string key)
{
return property.MetaData.ContainsKey(key);
}
public static bool HasNativeGetter(this UhtProperty property)
{
if (property.Outer is UhtScriptStruct)
{
return false;
}
return !string.IsNullOrEmpty(property.Getter);
}
public static bool HasNativeSetter(this UhtProperty property)
{
if (property.Outer is UhtScriptStruct)
{
return false;
}
return !string.IsNullOrEmpty(property.Setter);
}
public static bool HasAnyNativeGetterSetter(this UhtProperty property)
{
return property.HasNativeGetter() || property.HasNativeSetter();
}
public static bool HasBlueprintGetter(this UhtProperty property)
{
return property.GetBlueprintGetter() != null;
}
public static bool HasBlueprintSetter(this UhtProperty property)
{
return property.GetBlueprintSetter() != null;
}
public static bool HasBlueprintGetterOrSetter(this UhtProperty property)
{
return property.HasBlueprintGetter() || property.HasBlueprintSetter();
}
public static bool HasBlueprintGetterSetterPair(this UhtProperty property)
{
return property.HasBlueprintGetter() && property.HasBlueprintSetter();
}
public static bool HasAnyGetterOrSetter(this UhtProperty property)
{
return property.HasAnyNativeGetterSetter() || property.HasBlueprintGetterOrSetter();
}
public static bool HasAnyGetter(this UhtProperty property)
{
return property.HasNativeGetter() || property.HasBlueprintGetter();
}
public static bool HasAnySetter(this UhtProperty property)
{
return property.HasNativeSetter() || property.HasBlueprintSetter();
}
public static bool HasGetterSetterPair(this UhtProperty property)
{
return property.HasAnyGetter() && property.HasAnySetter();
}
public static UhtFunction? GetBlueprintGetter(this UhtProperty property)
{
return property.TryGetBlueprintAccessor(GetterSetterMode.Get);
}
public static UhtFunction? GetBlueprintSetter(this UhtProperty property)
{
return property.TryGetBlueprintAccessor(GetterSetterMode.Set);
}
public static bool IsWorldContextParameter(this UhtProperty property)
{
if (property.Outer is not UhtFunction function)
{
return false;
}
if (property is not UhtObjectProperty objectProperty || objectProperty.Class != Program.Factory.Session.UObject)
{
return false;
}
string sourceName = property.SourceName;
return function.GetMetadata("WorldContext") == sourceName || sourceName is "WorldContextObject" or "WorldContext" or "ContextObject";
}
public static bool IsReadWrite(this UhtProperty property)
{
return !property.IsReadOnly() && (property.PropertyFlags.HasAnyFlags(EPropertyFlags.BlueprintVisible | EPropertyFlags.BlueprintAssignable) || property.HasAnySetter());
}
public static bool IsReadOnly(this UhtProperty property)
{
return property.HasAllFlags(EPropertyFlags.BlueprintReadOnly);
}
public static bool IsEditDefaultsOnly(this UhtProperty property)
{
return property.HasAllFlags(EPropertyFlags.Edit | EPropertyFlags.DisableEditOnInstance);
}
public static bool IsEditAnywhere(this UhtProperty property)
{
return property.HasAllFlags(EPropertyFlags.Edit);
}
public static bool IsEditInstanceOnly(this UhtProperty property)
{
return property.HasAllFlags(EPropertyFlags.Edit | EPropertyFlags.DisableEditOnTemplate);
}
public static UhtFunction? TryGetBlueprintAccessor(this UhtProperty property, GetterSetterMode accessorType)
{
if (property.Outer is UhtScriptStruct || property.Outer is not UhtClass classObj)
{
return null;
}
UhtFunction? TryFindFunction(string? name)
{
if (name is null)
{
return null;
}
UhtFunction? function = classObj.FindFunctionByName(name, (uhtFunction, typeName) =>
{
if (uhtFunction.SourceName == typeName
|| (uhtFunction.SourceName.Length == typeName.Length
&& uhtFunction.SourceName.Contains(typeName, StringComparison.InvariantCultureIgnoreCase)))
{
return true;
}
if (uhtFunction.GetScriptName() == typeName
|| (uhtFunction.GetScriptName().Length == typeName.Length
&& uhtFunction.GetScriptName().Contains(typeName, StringComparison.InvariantCultureIgnoreCase)))
{
return true;
}
return false;
});
if (function != null && function.VerifyBlueprintAccessor(property))
{
return function;
}
return null;
}
string accessorName = GetAccessorName(property, accessorType);
UhtFunction? function = TryFindFunction(accessorName);
if (function != null)
{
return function;
}
function = TryFindFunction(accessorType + property.SourceName);
if (function != null)
{
return function;
}
function = TryFindFunction(accessorType + property.GetPropertyName());
if (function != null)
{
return function;
}
function = TryFindFunction(accessorType + NameMapper.ScriptifyName(property.SourceName, ENameType.Property));
if (function != null)
{
return function;
}
return null;
}
private static string GetAccessorName(UhtProperty property, GetterSetterMode accessorType)
{
return accessorType == GetterSetterMode.Get
? property.Getter ?? property.GetMetaData("BlueprintGetter")
: property.Setter ?? property.GetMetaData("BlueprintSetter");
}
public static string GetNativePropertyName(this UhtProperty property)
{
return $"{property.SourceName}_NativeProperty";
}
public static string GetOffsetVariableName(this UhtProperty property)
{
return $"{property.Outer!.SourceName}_{property.SourceName}_Offset";
}
public static string GetProtection(this UhtProperty property)
{
UhtClass? classObj = property.Outer as UhtClass;
bool isClassOwner = classObj != null;
if (isClassOwner)
{
UhtFunction? getter = property.GetBlueprintGetter();
UhtFunction? setter = property.GetBlueprintSetter();
if ((getter != null && getter.FunctionFlags.HasAnyFlags(EFunctionFlags.Public)) || (setter != null && setter.FunctionFlags.HasAnyFlags(EFunctionFlags.Public)))
{
return ScriptGeneratorUtilities.PublicKeyword;
}
if ((getter != null && getter.FunctionFlags.HasAnyFlags(EFunctionFlags.Protected)) || (setter != null && setter.FunctionFlags.HasAnyFlags(EFunctionFlags.Protected)))
{
return ScriptGeneratorUtilities.ProtectedKeyword;
}
}
if (property.HasAllFlags(EPropertyFlags.NativeAccessSpecifierPublic) ||
(property.HasAllFlags(EPropertyFlags.NativeAccessSpecifierPrivate) && property.HasMetaData("AllowPrivateAccess")) ||
(!isClassOwner && property.HasAllFlags(EPropertyFlags.Protected)))
{
return ScriptGeneratorUtilities.PublicKeyword;
}
if (isClassOwner && property.HasAllFlags(EPropertyFlags.Protected))
{
return ScriptGeneratorUtilities.ProtectedKeyword;
}
if (property.HasAllFlags(EPropertyFlags.Edit))
{
return ScriptGeneratorUtilities.PublicKeyword;
}
return ScriptGeneratorUtilities.PrivateKeyword;
}
public static bool DeterminesOutputType(this UhtProperty property)
{
if (property.Outer is not UhtFunction function) return false;
return function.HasMetadata("DeterminesOutputType");
}
public static bool IsGenericType(this UhtProperty property)
{
if (property.Outer is not UhtFunction function) return false;
if (!function.HasGenericTypeSupport()) return false;
if (function.HasMetadata("DynamicOutputParam")
&& function.GetMetadata("DynamicOutputParam") == property.EngineName)
{
var propertyDeterminingOutputType = function.Properties
.Where(p => p.EngineName == function.GetMetadata("DeterminesOutputType"))
.FirstOrDefault();
if (propertyDeterminingOutputType == null) return false;
if (propertyDeterminingOutputType!.GetGenericManagedType() != property.GetGenericManagedType()) return false;
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
return translator.CanSupportGenericType(property);
}
else if (!function.HasMetadata("DynamicOutputParam") && property.HasAllFlags(EPropertyFlags.ReturnParm))
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
return translator.CanSupportGenericType(property);
}
else if (function.GetMetadata("DeterminesOutputType") == property.EngineName)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
return translator.CanSupportGenericType(property);
}
return false;
}
public static string GetGenericManagedType(this UhtProperty property)
{
if (property is UhtClassProperty classProperty)
{
return classProperty.MetaClass!.GetFullManagedName();
}
else if (property is UhtSoftClassProperty softClassProperty)
{
return softClassProperty.MetaClass!.GetFullManagedName();
}
else if (property is UhtContainerBaseProperty containerProperty)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(containerProperty.ValueProperty)!;
return translator.GetManagedType(containerProperty.ValueProperty);
}
else if (property is UhtObjectProperty objectProperty)
{
return objectProperty.Class.GetFullManagedName();
}
return "";
}
public static bool IsCustomStructureType(this UhtProperty property)
{
if (property.Outer is not UhtFunction function) return false;
if (!function.HasCustomStructParamSupport()) return false;
if (function.GetCustomStructParams().Contains(property.EngineName))
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
return translator.CanSupportCustomStruct(property);
}
return false;
}
public static List<UhtProperty>? GetPrecedingParams(this UhtProperty property)
{
if (property.Outer is not UhtFunction function) return null;
return function.Children.Cast<UhtProperty>().TakeWhile(param => param != property).ToList();
}
public static int GetPrecedingCustomStructParams(this UhtProperty property)
{
if (property.Outer is not UhtFunction function) return 0;
if (!function.HasCustomStructParamSupport()) return 0;
return property.GetPrecedingParams()!
.Count(param => param.IsCustomStructureType());
}
}

View File

@ -0,0 +1,428 @@
using System;
using System.Collections.Generic;
using System.Linq;
using EpicGames.Core;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.Exporters;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public class GetterSetterPair
{
public GetterSetterPair(UhtProperty property)
{
PropertyName = property.GetPropertyName();
if (!property.HasNativeGetter())
{
UhtFunction? foundGetter = property.GetBlueprintGetter();
if (foundGetter != null)
{
Getter = foundGetter;
GetterExporter = GetterSetterFunctionExporter.Create(foundGetter, property, GetterSetterMode.Get,
EFunctionProtectionMode.UseUFunctionProtection);
}
}
if (!property.HasNativeSetter())
{
UhtFunction? foundSetter = property.GetBlueprintSetter();
if (foundSetter != null)
{
Setter = foundSetter;
SetterExporter = GetterSetterFunctionExporter.Create(foundSetter, property, GetterSetterMode.Set,
EFunctionProtectionMode.UseUFunctionProtection);
}
}
}
public GetterSetterPair(string propertyName)
{
PropertyName = propertyName;
}
public readonly string PropertyName;
public UhtFunction? Getter { get; set; }
public UhtFunction? Setter { get; set; }
public GetterSetterFunctionExporter? GetterExporter { get; set; }
public GetterSetterFunctionExporter? SetterExporter { get; set; }
public List<UhtFunction> Accessors
{
get
{
List<UhtFunction> accessors = new();
UhtFunction? getter = Getter;
if (getter != null)
{
accessors.Add(getter);
}
UhtFunction? setter = Setter;
if (setter != null)
{
accessors.Add(setter);
}
return accessors;
}
}
public UhtProperty? Property { get; set; }
}
public static class ScriptGeneratorUtilities
{
public const string InteropNamespace = "UnrealSharp.Interop";
public const string MarshallerNamespace = "UnrealSharp.Core.Marshallers";
public const string AttributeNamespace = "UnrealSharp.Attributes";
public const string CoreAttributeNamespace = "UnrealSharp.Core.Attributes";
public const string InteropServicesNamespace = "System.Runtime.InteropServices";
public const string PublicKeyword = "public ";
public const string PrivateKeyword = "private ";
public const string ProtectedKeyword = "protected ";
public const string IntPtrZero = "IntPtr.Zero";
public static string TryGetPluginDefine(string key)
{
Program.PluginModule.TryGetDefine(key, out string? generatedCodePath);
return generatedCodePath!;
}
public static bool CanExportFunction(UhtFunction function)
{
if (function.HasAnyFlags(EFunctionFlags.Delegate | EFunctionFlags.MulticastDelegate))
{
return false;
}
return CanExportParameters(function);
}
public static bool CanExportParameters(UhtFunction function)
{
bool CanExportParameter(UhtProperty property, Func<PropertyTranslator, bool> isSupported)
{
PropertyTranslator? translator = PropertyTranslatorManager.GetTranslator(property);
return translator != null && isSupported(translator) && translator.CanExport(property);
}
if (function.ReturnProperty != null && !CanExportParameter(function.ReturnProperty,
translator => translator.IsSupportedAsReturnValue()))
{
return false;
}
foreach (UhtProperty parameter in function.Properties)
{
if (!CanExportParameter(parameter, translator => translator.IsSupportedAsParameter()))
{
return false;
}
}
return true;
}
public static bool CanExportProperty(UhtProperty property)
{
PropertyTranslator? translator = PropertyTranslatorManager.GetTranslator(property);
if (translator == null || !translator.CanExport(property))
{
return false;
}
bool isClassProperty = property.Outer!.EngineType == UhtEngineType.Class;
bool canBeClassProperty = isClassProperty && translator.IsSupportedAsProperty();
bool canBeStructProperty = !isClassProperty && translator.IsSupportedAsStructProperty();
return canBeClassProperty || canBeStructProperty;
}
public static string GetCleanEnumValueName(UhtEnum enumObj, UhtEnumValue enumValue)
{
if (enumObj.CppForm == UhtEnumCppForm.Regular)
{
return enumValue.Name;
}
int delimiterIndex = enumValue.Name.IndexOf("::", StringComparison.Ordinal);
return delimiterIndex < 0 ? enumValue.Name : enumValue.Name.Substring(delimiterIndex + 2);
}
public static void GetExportedProperties(UhtStruct structObj, List<UhtProperty> properties,
Dictionary<UhtProperty, GetterSetterPair> getterSetterBackedProperties)
{
if (!structObj.Properties.Any())
{
return;
}
UhtClass? classObj = structObj as UhtClass;
foreach (UhtProperty property in structObj.Properties)
{
if (!CanExportProperty(property) || InclusionLists.HasBannedProperty(property))
{
continue;
}
if (classObj != null && (property.HasAnyGetter() || property.HasAnySetter()))
{
GetterSetterPair pair = new GetterSetterPair(property);
getterSetterBackedProperties.Add(property, pair);
}
else
{
properties.Add(property);
}
}
}
public static void GetExportedFunctions(UhtClass classObj, List<UhtFunction> functions,
List<UhtFunction> overridableFunctions,
Dictionary<string, GetterSetterPair> getterSetterPairs,
Dictionary<string, GetterSetterPair> getSetOverrides)
{
List<UhtFunction> exportedFunctions = new();
bool HasFunction(List<UhtFunction> functionsToCheck, UhtFunction functionToTest)
{
foreach (UhtFunction function in functionsToCheck)
{
if (function.SourceName == functionToTest.SourceName ||
function.CppImplName == functionToTest.CppImplName)
{
return true;
}
}
return false;
}
foreach (UhtFunction function in classObj.Functions)
{
if (!CanExportFunction(function))
{
continue;
}
if (function.IsAnyGetter() || function.IsAnySetter())
{
continue;
}
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintEvent))
{
overridableFunctions.Add(function);
}
else if (function.IsAutocast())
{
functions.Add(function);
if (function.Properties.First() is not UhtStructProperty structToConvertProperty)
{
continue;
}
if (structToConvertProperty.Package.IsPartOfEngine() != function.Package.IsPartOfEngine())
{
// For auto-casts to work, they both need to be in the same generated assembly.
// Currently not supported, as we separate engine and project generated assemblies.
continue;
}
AutocastExporter.AddAutocastFunction(structToConvertProperty.ScriptStruct, function);
}
else if (!TryMakeFunctionGetterSetterPair(function, classObj, getterSetterPairs, false))
{
functions.Add(function);
}
exportedFunctions.Add(function);
}
foreach (UhtClass declaration in classObj.GetInterfaces())
{
UhtClass? interfaceClass = declaration.GetInterfaceAlternateClass();
if (interfaceClass == null)
{
continue;
}
foreach (UhtFunction function in interfaceClass.Functions)
{
if (TryMakeFunctionGetterSetterPair(function, interfaceClass, getSetOverrides, true)
|| HasFunction(exportedFunctions, function) || !CanExportFunction(function))
{
continue;
}
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintEvent))
{
overridableFunctions.Add(function);
}
else
{
functions.Add(function);
}
}
}
}
public static List<UhtClass> GetInterfaces(this UhtClass classObj)
{
List<UhtClass> interfaces = new();
foreach (UhtStruct interfaceClass in classObj.Bases)
{
UhtEngineType engineType = interfaceClass.EngineType;
if (engineType is UhtEngineType.Interface or UhtEngineType.NativeInterface)
{
interfaces.Add((UhtClass)interfaceClass);
}
}
return interfaces;
}
public static bool TryMakeFunctionGetterSetterPair(UhtFunction function, UhtClass classObj,
Dictionary<string, GetterSetterPair> getterSetterPairs, bool ignoreMatchingProperty)
{
string scriptName = function.GetFunctionName();
bool isGetter = CheckIfGetter(scriptName, function);
bool isSetter = CheckIfSetter(scriptName, function);
if (!isGetter && !isSetter)
{
return false;
}
string propertyName = scriptName.Length > 3 ? scriptName.Substring(3) : function.SourceName;
propertyName = NameMapper.EscapeKeywords(propertyName);
UhtFunction? sameNameFunction = classObj.FindFunctionByName(propertyName);
if (sameNameFunction != null && sameNameFunction != function)
{
return false;
}
bool ComparePropertyName(UhtProperty prop, string name)
{
return prop.SourceName == name || prop.GetPropertyName() == name;
}
UhtProperty? classProperty = !ignoreMatchingProperty ? classObj.FindPropertyByName(propertyName, ComparePropertyName) : null;
UhtProperty firstProperty = function.ReturnProperty ?? function.Properties.First();
if (classProperty != null && (!classProperty.IsSameType(firstProperty) || classProperty.HasAnyGetter() ||
classProperty.HasAnySetter()))
{
return false;
}
if (!getterSetterPairs.TryGetValue(propertyName, out GetterSetterPair? pair))
{
pair = new GetterSetterPair(propertyName);
getterSetterPairs[propertyName] = pair;
}
if (pair.Accessors.Count == 2)
{
return true;
}
bool isOutParm = function.Properties.Any(p =>
p.HasAllFlags(EPropertyFlags.OutParm) && !p.HasAllFlags(EPropertyFlags.ConstParm));
if (function.ReturnProperty != null || isOutParm)
{
pair.Getter = function;
// When creating the getter, bind it to the getter's own value type (return or out param)
UhtProperty getterValueProperty = function.ReturnProperty ?? function.Properties.First(p =>
p.HasAllFlags(EPropertyFlags.OutParm) && !p.HasAllFlags(EPropertyFlags.ConstParm));
pair.GetterExporter = GetterSetterFunctionExporter.Create(function, getterValueProperty, GetterSetterMode.Get,
EFunctionProtectionMode.UseUFunctionProtection);
UhtFunction? setter = classObj.FindFunctionByName("Set" + propertyName, null, true);
if (setter != null && CheckIfSetter(setter))
{
pair.Setter = setter;
// Keep using the getter's value type as the canonical property type
pair.SetterExporter = GetterSetterFunctionExporter.Create(setter, getterValueProperty, GetterSetterMode.Set,
EFunctionProtectionMode.UseUFunctionProtection);
}
}
else
{
pair.Setter = function;
pair.SetterExporter = GetterSetterFunctionExporter.Create(function, firstProperty, GetterSetterMode.Set,
EFunctionProtectionMode.UseUFunctionProtection);
UhtFunction? getter = classObj.FindFunctionByName("Get" + propertyName, null, true);
if (getter != null && CheckIfGetter(getter))
{
pair.Getter = getter;
// Prefer the getter's own value type (return or out param) for the property type
UhtProperty getterValueProperty = getter.ReturnProperty ?? getter.Properties.First(p =>
p.HasAllFlags(EPropertyFlags.OutParm) && !p.HasAllFlags(EPropertyFlags.ConstParm));
pair.GetterExporter = GetterSetterFunctionExporter.Create(getter, getterValueProperty, GetterSetterMode.Get,
EFunctionProtectionMode.UseUFunctionProtection);
// Also re-bind the setter exporter to the getter's value type so signatures align
pair.SetterExporter = GetterSetterFunctionExporter.Create(function, getterValueProperty, GetterSetterMode.Set,
EFunctionProtectionMode.UseUFunctionProtection);
}
}
// Canonical property type: prefer getter value type when available, else fall back to current function's type
if (pair.Getter != null)
{
pair.Property = pair.Getter.ReturnProperty ?? pair.Getter.Properties.First(p =>
p.HasAllFlags(EPropertyFlags.OutParm) && !p.HasAllFlags(EPropertyFlags.ConstParm));
}
else
{
pair.Property = firstProperty;
}
getterSetterPairs[propertyName] = pair;
return true;
}
static bool CheckIfGetter(string scriptName, UhtFunction function)
{
return scriptName.StartsWith("Get") && CheckIfGetter(function);
}
static bool CheckIfGetter(UhtFunction function)
{
int childrenCount = function.Children.Count;
bool hasReturnProperty = function.ReturnProperty != null;
bool hasNoParameters = !function.HasParameters;
bool hasSingleOutParam = !hasNoParameters && childrenCount == 1 && function.HasOutParams();
bool hasWorldContextPassParam =
childrenCount == 2 && function.Properties.Any(property => property.IsWorldContextParameter());
bool isNotBlueprintEvent = !function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintEvent);
return hasReturnProperty && isNotBlueprintEvent && (hasNoParameters || hasSingleOutParam || hasWorldContextPassParam);
}
static bool CheckIfSetter(string scriptName, UhtFunction function)
{
return scriptName.StartsWith("Set") && CheckIfSetter(function);
}
static bool CheckIfSetter(UhtFunction function)
{
bool hasSingleParameter = function.Properties.Count() == 1;
var property = function.Properties.FirstOrDefault();
bool isNotOutOrReferenceParam = function.HasParameters && property is not null
&& (!property.HasAllFlags(EPropertyFlags.OutParm | EPropertyFlags.ReferenceParm)
|| property.HasAllFlags(EPropertyFlags.ConstParm));
bool isNotBlueprintEvent = !function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintEvent);
return hasSingleParameter && isNotBlueprintEvent && isNotOutOrReferenceParam;
}
}

View File

@ -0,0 +1,224 @@
using System.Collections.Generic;
using EpicGames.Core;
using EpicGames.UHT.Types;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public static class StaticConstructorUtilities
{
public static void ExportStaticConstructor(GeneratorStringBuilder generatorStringBuilder,
UhtStruct structObj,
List<UhtProperty> exportedProperties,
List<UhtFunction> exportedFunctions,
Dictionary<string, GetterSetterPair> exportedGetterSetters,
Dictionary<UhtProperty, GetterSetterPair> getSetBackedProperties,
List<UhtFunction> overrides,
bool isBlittable = false, string? customStaticConstructorName = null)
{
UhtClass? classObj = structObj as UhtClass;
UhtScriptStruct? scriptStructObj = structObj as UhtScriptStruct;
string structName = structObj.GetStructName();
if (classObj != null && exportedProperties.Count == 0
&& exportedFunctions.Count == 0
&& overrides.Count == 0
&& exportedGetterSetters.Count == 0
&& getSetBackedProperties.Count == 0)
{
return;
}
bool hasStaticFunctions = true;
void CheckIfStaticFunction(UhtFunction function)
{
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.Static))
{
hasStaticFunctions = true;
}
}
foreach (UhtFunction function in exportedFunctions)
{
CheckIfStaticFunction(function);
}
foreach (GetterSetterPair pair in exportedGetterSetters.Values)
{
if (pair.Getter != null)
{
CheckIfStaticFunction(pair.Getter);
}
if (pair.Setter != null)
{
CheckIfStaticFunction(pair.Setter);
}
}
string nativeClassPtrDeclaration = string.Empty;
if (hasStaticFunctions)
{
generatorStringBuilder.AppendLine("static readonly IntPtr NativeClassPtr;");
}
else
{
nativeClassPtrDeclaration = "IntPtr ";
}
if (scriptStructObj != null)
{
if(classObj == null) generatorStringBuilder.AppendLine("public static IntPtr GetNativeClassPtr() => NativeClassPtr;");
if (isBlittable)
{
generatorStringBuilder.AppendLine("public static int GetNativeDataSize()");
generatorStringBuilder.OpenBrace();
generatorStringBuilder.BeginUnsafeBlock();
generatorStringBuilder.AppendLine($"return sizeof({structName});");
generatorStringBuilder.EndUnsafeBlock();
generatorStringBuilder.CloseBrace();
}
else
{
generatorStringBuilder.AppendLine("public static readonly int NativeDataSize;");
if (classObj == null)
{
generatorStringBuilder.AppendLine("public static int GetNativeDataSize() => NativeDataSize;");
}
}
}
string staticCtorName = customStaticConstructorName != null ? customStaticConstructorName : structName;
generatorStringBuilder.AppendLine($"static {staticCtorName}()");
generatorStringBuilder.OpenBrace();
string type = classObj != null ? "Class" : "Struct";
string engineName = structObj.EngineName;
generatorStringBuilder.AppendLine($"{nativeClassPtrDeclaration}NativeClassPtr = {ExporterCallbacks.CoreUObjectCallbacks}.CallGetNative{type}FromName({structObj.ExportGetAssemblyName()}, \"{structObj.GetNamespace()}\", \"{engineName}\");");
ExportPropertiesStaticConstructor(generatorStringBuilder, exportedProperties);
ExportGetSetBackedPropertyStaticConstructor(generatorStringBuilder, getSetBackedProperties);
if (classObj != null)
{
foreach (KeyValuePair<string, GetterSetterPair> pair in exportedGetterSetters)
{
if (pair.Value.Getter != null)
{
ExportClassFunctionStaticConstructor(generatorStringBuilder, pair.Value.Getter);
}
if (pair.Value.Setter != null)
{
ExportClassFunctionStaticConstructor(generatorStringBuilder, pair.Value.Setter);
}
}
ExportClassFunctionsStaticConstructor(generatorStringBuilder, exportedFunctions);
ExportClassOverridesStaticConstructor(generatorStringBuilder, overrides);
}
else if (!isBlittable) generatorStringBuilder.AppendLine($"NativeDataSize = {ExporterCallbacks.UScriptStructCallbacks}.CallGetNativeStructSize(NativeClassPtr);");
generatorStringBuilder.CloseBrace();
}
public static void ExportClassFunctionsStaticConstructor(GeneratorStringBuilder generatorStringBuilder, List<UhtFunction> exportedFunctions)
{
foreach (UhtFunction function in exportedFunctions)
{
ExportClassFunctionStaticConstructor(generatorStringBuilder, function);
}
}
public static void ExportClassFunctionStaticConstructor(GeneratorStringBuilder generatorStringBuilder, UhtFunction function)
{
string functionName = function.SourceName;
string nativeFunctionName = function.GetNativeFunctionName();
generatorStringBuilder.TryAddWithEditor(function);
generatorStringBuilder.AppendLine($"{nativeFunctionName} = {ExporterCallbacks.UClassCallbacks}.CallGetNativeFunctionFromClassAndName(NativeClassPtr, \"{function.EngineName}\");");
if (function.HasParametersOrReturnValue())
{
bool hasCustomStructParams = function.HasCustomStructParamSupport();
string variableName = hasCustomStructParams ? $"{functionName}_NativeParamsSize" : $"{functionName}_ParamsSize";
generatorStringBuilder.AppendLine($"{variableName} = {ExporterCallbacks.UFunctionCallbacks}.CallGetNativeFunctionParamsSize({functionName}_NativeFunction);");
foreach (UhtType parameter in function.Children)
{
if (parameter is not UhtProperty property)
{
continue;
}
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportParameterStaticConstructor(generatorStringBuilder, property, function, property.SourceName, functionName);
}
if (hasCustomStructParams)
{
List<string> customStructParams = function.GetCustomStructParams();
List<string> initializerElements = customStructParams.ConvertAll(param =>
$"{ExporterCallbacks.FPropertyCallbacks}.CallGetNativePropertyFromName({nativeFunctionName}, \"{param}\")");
generatorStringBuilder.AppendLine($"{functionName}_CustomStructureNativeProperties = new IntPtr[]{{{string.Join(", ", initializerElements)}}};");
}
}
generatorStringBuilder.TryEndWithEditor(function);
}
public static void ExportClassOverridesStaticConstructor(GeneratorStringBuilder generatorStringBuilder, List<UhtFunction> overrides)
{
foreach (UhtFunction function in overrides)
{
generatorStringBuilder.TryAddWithEditor(function);
string functionName = function.SourceName;
string intPtrDeclaration = function.IsBlueprintImplementableEvent() ? "IntPtr " : "";
generatorStringBuilder.AppendLine($"{intPtrDeclaration}{functionName}_NativeFunction = {ExporterCallbacks.UClassCallbacks}.CallGetNativeFunctionFromClassAndName(NativeClassPtr, \"{function.EngineName}\");");
if (function.HasParametersOrReturnValue())
{
generatorStringBuilder.AppendLine($"{functionName}_ParamsSize = {ExporterCallbacks.UFunctionCallbacks}.CallGetNativeFunctionParamsSize({functionName}_NativeFunction);");
foreach (UhtType parameter in function.Children)
{
if (parameter is not UhtProperty property)
{
continue;
}
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportParameterStaticConstructor(generatorStringBuilder, property, function, property.SourceName, functionName);
}
}
generatorStringBuilder.TryEndWithEditor(function);
}
}
public static void ExportPropertiesStaticConstructor(GeneratorStringBuilder generatorStringBuilder, List<UhtProperty> exportedProperties)
{
foreach (UhtProperty property in exportedProperties)
{
ExportPropertyStaticConstructor(generatorStringBuilder, property);
}
}
public static void ExportGetSetBackedPropertyStaticConstructor(GeneratorStringBuilder generatorStringBuilder, Dictionary<UhtProperty, GetterSetterPair> getSetBackedProperties)
{
foreach (KeyValuePair<UhtProperty, GetterSetterPair> pair in getSetBackedProperties)
{
ExportPropertyStaticConstructor(generatorStringBuilder, pair.Key);
}
}
private static void ExportPropertyStaticConstructor(GeneratorStringBuilder generatorStringBuilder, UhtProperty property)
{
generatorStringBuilder.TryAddWithEditor(property);
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
translator.ExportPropertyStaticConstructor(generatorStringBuilder, property, property.SourceName);
generatorStringBuilder.TryEndWithEditor(property);
}
}

View File

@ -0,0 +1,86 @@
using EpicGames.UHT.Types;
using System.Collections.Generic;
using UnrealSharpScriptGenerator.PropertyTranslators;
namespace UnrealSharpScriptGenerator.Utilities;
public static class StructUtilities
{
public static bool IsStructBlittable(this UhtStruct structObj)
{
if (PropertyTranslatorManager.SpecialTypeInfo.Structs.BlittableTypes.ContainsKey(structObj.SourceName))
{
return true;
}
// Any struct we haven't manually exported is not blittable, yet.
// The fix for this is to add a header parser to check for non-UPROPERTY properties in the struct.
// Because a struct can be recognized as blittable by the reflection data,
// but have a non-UPROPERTY property that is not picked up by UHT, that makes it not blittable causing a mismatch in memory layout.
// This is a temporary solution until we can get that working.
return false;
}
public static bool IsStructNativelyCopyable(this UhtStruct structObj)
{
return PropertyTranslatorManager.SpecialTypeInfo.Structs.NativelyCopyableTypes.ContainsKey(structObj.SourceName);
}
public static bool IsStructNativelyDestructible(this UhtStruct structObj)
{
return PropertyTranslatorManager.SpecialTypeInfo.Structs.NativelyCopyableTypes.TryGetValue(structObj.SourceName, out var info) && info.HasDestructor;
}
public static bool IsStructEquatable(this UhtStruct structObj, List<UhtProperty> exportedProperties)
{
if (InclusionLists.HasBannedEquality(structObj))
{
return false;
}
if (exportedProperties.Count == 0)
{
return false;
}
foreach (UhtProperty property in exportedProperties)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
if (!translator.IsPrimitive)
{
return false;
}
}
return true;
}
public static bool CanSupportArithmetic(this UhtStruct structObj, List<UhtProperty> exportedProperties)
{
if (InclusionLists.HasBannedEquality(structObj))
{
return false;
}
if (InclusionLists.HasBannedArithmetic(structObj))
{
return false;
}
if (exportedProperties.Count == 0)
{
return false;
}
foreach (UhtProperty property in exportedProperties)
{
PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(property)!;
if (!translator.IsNumeric)
{
return false;
}
}
return true;
}
}

View File

@ -0,0 +1,18 @@
using EpicGames.UHT.Types;
namespace UnrealSharpScriptGenerator.Utilities;
public static class UhtTypeUtilities
{
public const string NullableEnable = "NullableEnable";
public static bool HasMetadata(this UhtType type, string metadataName)
{
return type.MetaData.ContainsKey(metadataName);
}
public static string GetMetadata(this UhtType type, string metadataName, int nameIndex = -1)
{
return type.MetaData.GetValueOrDefault(metadataName, nameIndex);
}
}