| @ -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 | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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"; | ||||
| } | ||||
| @ -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"; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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 | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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()); | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
| @ -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; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user