428 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			428 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
|  | 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; | |||
|  |     } | |||
|  | } |