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 Accessors { get { List 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 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 properties, Dictionary 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 functions, List overridableFunctions, Dictionary getterSetterPairs, Dictionary getSetOverrides) { List exportedFunctions = new(); bool HasFunction(List 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 GetInterfaces(this UhtClass classObj) { List 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 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; } }