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;
 | |
|     }
 | |
| } |