using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Data.Common; using System.Linq; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using System.Runtime.InteropServices; using System.Threading.Tasks; using EpicGames.Core; using EpicGames.UHT.Types; using UnrealSharpScriptGenerator.PropertyTranslators; using UnrealSharpScriptGenerator.Tooltip; using UnrealSharpScriptGenerator.Utilities; namespace UnrealSharpScriptGenerator.Exporters; public enum EFunctionProtectionMode { UseUFunctionProtection, OverrideWithInternal, } public struct ExtensionMethod { public UhtClass Class; public UhtFunction Function; public UhtProperty SelfParameter; } public enum FunctionType { Normal, BlueprintEvent, ExtensionOnAnotherClass, InternalWhitelisted, GetterSetter, }; public enum OverloadMode { AllowOverloads, SuppressOverloads, }; public enum EBlueprintVisibility { Call, Event, GetterSetter, Throwing }; public struct FunctionOverload { public string ParamStringApiWithDefaults; public string ParamsStringCall; public string CSharpParamName; public string CppDefaultValue; public PropertyTranslator Translator; public UhtProperty Parameter; } public class FunctionExporter { protected static readonly ConcurrentDictionary> ExtensionMethods = new(); public UhtFunction Function { get; } protected string _functionName = null!; protected List _parameterTranslators = null!; protected PropertyTranslator? ReturnValueTranslator => Function.ReturnProperty != null ? _parameterTranslators.Last() : null; protected OverloadMode _overloadMode = OverloadMode.AllowOverloads; protected EFunctionProtectionMode _protectionMode = EFunctionProtectionMode.UseUFunctionProtection; protected EBlueprintVisibility _blueprintVisibility = EBlueprintVisibility.Call; protected bool BlittableFunction; public string Modifiers { get; private set; } = ""; protected bool BlueprintEvent => Function.HasAllFlags(EFunctionFlags.BlueprintEvent); protected bool BlueprintNativeEvent => Function.IsBlueprintNativeEvent(); protected bool BlueprintImplementableEvent => Function.IsBlueprintImplementableEvent(); protected bool Throwing => _blueprintVisibility == EBlueprintVisibility.Throwing; protected string _invokeFunction = ""; protected string _invokeFirstArgument = ""; protected string _customInvoke = ""; protected string _paramStringApiWithDefaults = ""; protected string _paramsStringCall = ""; protected bool _hasGenericTypeSupport = false; protected bool _hasCustomStructParamSupport = false; protected List _customStructParamTypes = null!; protected readonly UhtProperty? _selfParameter; protected readonly UhtClass? _classBeingExtended; protected readonly List _overloads = new(); public string NativeFunctionIntPtr => $"{Function.SourceName}_NativeFunction"; public string InstanceFunctionPtr => $"{Function.SourceName}_InstanceFunction"; public FunctionExporter(ExtensionMethod extensionMethod) { _selfParameter = extensionMethod.SelfParameter; Function = extensionMethod.Function; _classBeingExtended = extensionMethod.Class; } public FunctionExporter(UhtFunction function) { Function = function; } string GetRefQualifier(UhtProperty parameter) { if (parameter.HasAllFlags(EPropertyFlags.ConstParm)) { return ""; } if (parameter.HasAllFlags(EPropertyFlags.ReferenceParm)) { return "ref "; } if (parameter.HasAllFlags(EPropertyFlags.OutParm)) { return "out "; } return ""; } public void Initialize(OverloadMode overloadMode, EFunctionProtectionMode protectionMode, EBlueprintVisibility blueprintVisibility, bool withGenerics = false) { _functionName = protectionMode != EFunctionProtectionMode.OverrideWithInternal ? Function.GetFunctionName() : Function.SourceName; _overloadMode = overloadMode; _protectionMode = protectionMode; _blueprintVisibility = blueprintVisibility; _parameterTranslators = new List(Function.Children.Count); bool isBlittable = true; foreach (UhtProperty parameter in Function.Properties) { PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(parameter)!; _parameterTranslators.Add(translator); if (!translator.IsBlittable && isBlittable) { isBlittable = false; } } BlittableFunction = isBlittable; _hasGenericTypeSupport = Function.HasGenericTypeSupport(); _hasCustomStructParamSupport = Function.HasCustomStructParamSupport(); DetermineProtectionMode(); _invokeFunction = DetermineInvokeFunction(); if (Function.HasAllFlags(EFunctionFlags.Static)) { Modifiers += "static "; _invokeFirstArgument = "NativeClassPtr"; } else if (Function.HasAllFlags(EFunctionFlags.Delegate)) { if (Function.HasParametersOrReturnValue()) { _customInvoke = "ProcessDelegate(paramsBuffer);"; } else { _customInvoke = "ProcessDelegate(IntPtr.Zero);"; } } else { if (BlueprintEvent) { Modifiers += "virtual "; } if (Function.IsInterfaceFunction()) { Modifiers = ScriptGeneratorUtilities.PublicKeyword; } _invokeFirstArgument = "NativeObject"; } string paramString = ""; bool hasDefaultParameters = false; if (_selfParameter != null) { PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(_selfParameter)!; string paramType = _classBeingExtended != null ? _classBeingExtended.GetFullManagedName() : translator.GetManagedType(_selfParameter); paramString = $"this {paramType} {_selfParameter.GetParameterName()}, "; _paramStringApiWithDefaults = paramString; } string paramsStringCallNative = ""; string paramsStringCallGenerics = ""; string paramStringApiWithDefaultsWithGenerics = ""; bool hasGenericClassParam = false; _customStructParamTypes = Function.GetCustomStructParamTypes(); for (int i = 0; i < Function.Children.Count; i++) { UhtProperty parameter = (UhtProperty) Function.Children[i]; if (parameter.HasAllFlags(EPropertyFlags.ReturnParm)) { continue; } PropertyTranslator translator = _parameterTranslators[i]; string refQualifier = GetRefQualifier(parameter); string parameterName = GetParameterName(parameter); string parameterManagedType = translator.GetManagedType(parameter); if (!translator.ShouldBeDeclaredAsParameter) { continue; } if (_selfParameter == parameter) { if (string.IsNullOrEmpty(paramsStringCallGenerics)) { paramsStringCallGenerics += refQualifier + parameterName; } else { paramsStringCallGenerics = $"{parameterName}, " + _paramsStringCall.Substring(0, _paramsStringCall.Length - 2); } if (string.IsNullOrEmpty(_paramsStringCall)) { _paramsStringCall += refQualifier + parameterName; } else { _paramsStringCall = $"{parameterName}, " + _paramsStringCall.Substring(0, _paramsStringCall.Length - 2); } paramsStringCallNative += parameterName; } else { string cppDefaultValue = translator.GetCppDefaultValue(Function, parameter); bool isGenericClassParam = _hasGenericTypeSupport && parameter.IsGenericType() && !parameter.HasAnyFlags(EPropertyFlags.OutParm) && parameter is UhtClassProperty; if (cppDefaultValue == "()" && parameter is UhtStructProperty structProperty) { _paramsStringCall += $"new {structProperty.ScriptStruct.GetFullManagedName()}()"; paramsStringCallGenerics += $"new {structProperty.ScriptStruct.GetFullManagedName()}()"; } else if (isGenericClassParam) { _paramsStringCall += $"{refQualifier}{parameterName}"; paramsStringCallGenerics += $"typeof(DOT)"; hasGenericClassParam = true; } else { _paramsStringCall += $"{refQualifier}{parameterName}"; paramsStringCallGenerics += $"{refQualifier}{parameterName}"; } paramsStringCallNative += $"{refQualifier}{parameterName}"; paramString += $"{refQualifier}{parameterManagedType} {parameterName}"; if (!isGenericClassParam) { paramStringApiWithDefaultsWithGenerics += $"{refQualifier}{parameterManagedType} {parameterName}"; } if ((hasDefaultParameters || cppDefaultValue.Length > 0) && _overloadMode == OverloadMode.AllowOverloads) { hasDefaultParameters = true; string csharpDefaultValue = ""; if (cppDefaultValue.Length == 0 || cppDefaultValue == "None") { csharpDefaultValue = translator.GetNullValue(parameter); } else if (translator.ExportDefaultParameter) { csharpDefaultValue = translator.ConvertCPPDefaultValue(cppDefaultValue, Function, parameter); } if (!string.IsNullOrEmpty(csharpDefaultValue)) { string defaultValue = $" = {csharpDefaultValue}"; _paramStringApiWithDefaults += $"{refQualifier}{parameterManagedType} {parameterName}{defaultValue}"; } else { if (_paramStringApiWithDefaults.Length > 0) { _paramStringApiWithDefaults = _paramStringApiWithDefaults.Substring(0, _paramStringApiWithDefaults.Length - 2); } FunctionOverload overload = new FunctionOverload { ParamStringApiWithDefaults = _paramStringApiWithDefaults, ParamsStringCall = _paramsStringCall, CSharpParamName = parameterName, CppDefaultValue = cppDefaultValue, Translator = translator, Parameter = parameter, }; _overloads.Add(overload); _paramStringApiWithDefaults = paramString; } } else { _paramStringApiWithDefaults = paramString; } paramString += ", "; _paramStringApiWithDefaults += ", "; if (!isGenericClassParam) { paramStringApiWithDefaultsWithGenerics += ", "; } } _paramsStringCall += ", "; paramsStringCallGenerics += ", "; paramsStringCallNative += ", "; } if (_selfParameter == null) { _paramsStringCall = paramsStringCallNative; } // remove last comma if (_paramStringApiWithDefaults.Length > 0) { _paramStringApiWithDefaults = _paramStringApiWithDefaults.Substring(0, _paramStringApiWithDefaults.Length - 2); } if (_paramsStringCall.Length > 0) { _paramsStringCall = _paramsStringCall.Substring(0, _paramsStringCall.Length - 2); } if (paramsStringCallGenerics.Length > 0) { paramsStringCallGenerics = paramsStringCallGenerics[..^2]; } if (paramStringApiWithDefaultsWithGenerics.Length > 0) { paramStringApiWithDefaultsWithGenerics = paramStringApiWithDefaultsWithGenerics[..^2]; } if (hasGenericClassParam) { FunctionOverload overload = new FunctionOverload { ParamStringApiWithDefaults = paramStringApiWithDefaultsWithGenerics, ParamsStringCall = paramsStringCallGenerics, }; _overloads.Add(overload); } } protected virtual string GetParameterName(UhtProperty parameter) { return parameter.GetParameterName(); } public static void TryAddExtensionMethod(UhtFunction function) { if (!function.HasMetadata("ExtensionMethod") && !function.IsAutocast()) { return; } UhtPackage package = function.Outer!.Package; if (!ExtensionMethods.TryGetValue(package, out var extensionMethods)) { extensionMethods = new List(); ExtensionMethods.TryAdd(package, extensionMethods); } UhtProperty firstParameter = (function.Children[0] as UhtProperty)!; ExtensionMethod newExtensionMethod = new ExtensionMethod { Function = function, SelfParameter = firstParameter, }; if (firstParameter is UhtObjectPropertyBase objectSelfProperty) { newExtensionMethod.Class = objectSelfProperty.MetaClass!; } extensionMethods.Add(newExtensionMethod); } public static void StartExportingExtensionMethods(List tasks) { foreach (KeyValuePair> extensionInfo in ExtensionMethods) { tasks.Add(Program.Factory.CreateTask(_ => { ExtensionsClassExporter.ExportExtensionsClass(extensionInfo.Key, extensionInfo.Value); })!); } } public static FunctionExporter ExportFunction(GeneratorStringBuilder builder, UhtFunction function, FunctionType functionType, HashSet? exportedFunctions = null) { EFunctionProtectionMode protectionMode = EFunctionProtectionMode.UseUFunctionProtection; OverloadMode overloadMode = OverloadMode.AllowOverloads; EBlueprintVisibility blueprintVisibility = EBlueprintVisibility.Call; if (functionType == FunctionType.ExtensionOnAnotherClass) { protectionMode = EFunctionProtectionMode.OverrideWithInternal; overloadMode = OverloadMode.SuppressOverloads; } else if (functionType == FunctionType.BlueprintEvent) { overloadMode = OverloadMode.SuppressOverloads; blueprintVisibility = EBlueprintVisibility.Event; } else if (functionType == FunctionType.GetterSetter) { protectionMode = EFunctionProtectionMode.OverrideWithInternal; overloadMode = OverloadMode.SuppressOverloads; blueprintVisibility = EBlueprintVisibility.GetterSetter; } builder.TryAddWithEditor(function); FunctionExporter exporter = new FunctionExporter(function); exporter.Initialize(overloadMode, protectionMode, blueprintVisibility); if (exportedFunctions is null || !exportedFunctions.Contains(function.SourceName)) { exporter.ExportFunctionVariables(builder); } exporter.ExportOverloads(builder); exporter.ExportFunction(builder); builder.TryEndWithEditor(function); return exporter; } public static void ExportOverridableFunction(GeneratorStringBuilder builder, UhtFunction function, HashSet? exportedFunctions = null) { builder.TryAddWithEditor(function); string paramsStringApi = ""; string paramsCallString = ""; string methodName = function.GetFunctionName(); foreach (UhtProperty parameter in function.Properties) { if (parameter.HasAllFlags(EPropertyFlags.ReturnParm)) { continue; } string paramName = parameter.GetParameterName(); string paramType = PropertyTranslatorManager.GetTranslator(parameter)!.GetManagedType(parameter); string refQualifier = ""; if (!parameter.HasAllFlags(EPropertyFlags.ConstParm)) { if (parameter.HasAllFlags(EPropertyFlags.ReferenceParm)) { refQualifier = "ref "; } else if (parameter.HasAllFlags(EPropertyFlags.OutParm)) { refQualifier = "out "; } } paramsStringApi += $"{refQualifier}{paramType} {paramName}, "; paramsCallString += $"{refQualifier}{paramName}, "; } if (paramsStringApi.Length > 0) { paramsStringApi = paramsStringApi.Substring(0, paramsStringApi.Length - 2); } if (paramsCallString.Length > 0) { paramsCallString = paramsCallString.Substring(0, paramsCallString.Length - 2); } FunctionExporter exportFunction = ExportFunction(builder, function, FunctionType.BlueprintEvent, exportedFunctions); string returnType = function.ReturnProperty != null ? PropertyTranslatorManager.GetTranslator(function.ReturnProperty)!.GetManagedType(function.ReturnProperty) : "void"; builder.AppendLine("// Hide implementation function from Intellisense"); builder.AppendLine("[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]"); builder.AppendLine($"protected virtual {returnType} {methodName}_Implementation({paramsStringApi})"); builder.OpenBrace(); if (exportFunction.BlueprintNativeEvent) { exportFunction.ExportInvoke(builder); } else { exportFunction.ForEachParameter((translator, parameter) => { if (!parameter.HasAllFlags(EPropertyFlags.OutParm) || parameter.HasAnyFlags(EPropertyFlags.ReturnParm | EPropertyFlags.ConstParm | EPropertyFlags.ReferenceParm)) { return; } string paramName = parameter.GetParameterName(); string nullValue = translator.GetNullValue(parameter); builder.AppendLine($"{paramName} = {nullValue};"); }); if (function.ReturnProperty != null) { PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(function.ReturnProperty)!; string nullValue = translator.GetNullValue(function.ReturnProperty); builder.AppendLine($"return {nullValue};"); } } builder.CloseBrace(); builder.AppendLine($"void Invoke_{function.EngineName}(IntPtr buffer, IntPtr returnBuffer)"); builder.OpenBrace(); builder.BeginUnsafeBlock(); string returnAssignment = ""; exportFunction.ForEachParameter((translator, parameter) => { string paramType = translator.GetManagedType(parameter); if (parameter.HasAllFlags(EPropertyFlags.ReturnParm)) { returnAssignment = $"{paramType} returnValue = "; } else if (!parameter.HasAnyFlags(EPropertyFlags.ConstParm) && !parameter.HasAnyFlags(EPropertyFlags.ReferenceParm) && parameter.HasAnyFlags(EPropertyFlags.OutParm)) { builder.AppendLine($"{paramType} {parameter.GetParameterName()} = default;"); } else { string parameterName = parameter.GetParameterName(); string assignmentOrReturn = $"{paramType} {parameterName} = "; string offsetName = parameter.GetOffsetVariableName(); translator.ExportFromNative(builder, parameter, parameter.SourceName, assignmentOrReturn, "buffer", offsetName, false, false); } }); builder.AppendLine($"{returnAssignment}{methodName}_Implementation({paramsCallString});"); if (function.ReturnProperty != null) { PropertyTranslator translator = PropertyTranslatorManager.GetTranslator(function.ReturnProperty)!; translator.ExportToNative(builder, function.ReturnProperty, function.ReturnProperty.SourceName, "returnBuffer", "0", "returnValue"); } exportFunction.ForEachParameter((translator, parameter) => { if (parameter.HasAnyFlags(EPropertyFlags.ReturnParm | EPropertyFlags.ConstParm) || !parameter.HasAnyFlags(EPropertyFlags.OutParm)) { return; } translator.ExportToNative(builder, parameter, parameter.SourceName, "buffer", parameter.GetOffsetVariableName(), parameter.GetParameterName()); }); builder.EndUnsafeBlock(); builder.CloseBrace(); builder.TryEndWithEditor(function); builder.AppendLine(); } public static FunctionExporter ExportDelegateSignature(GeneratorStringBuilder builder, UhtFunction function, string delegateName) { FunctionExporter exporter = new FunctionExporter(function); exporter.Initialize(OverloadMode.SuppressOverloads, EFunctionProtectionMode.UseUFunctionProtection, EBlueprintVisibility.Call); AttributeBuilder attributeBuilder = new AttributeBuilder(); attributeBuilder.AddGeneratedTypeAttribute(function); if (function.HasAllFlags(EFunctionFlags.MulticastDelegate)) { attributeBuilder.AddAttribute("UMultiDelegate"); } else { attributeBuilder.AddAttribute("USingleDelegate"); } attributeBuilder.Finish(); builder.AppendLine(attributeBuilder.ToString()); builder.AppendLine($"public delegate void {delegateName}({exporter._paramStringApiWithDefaults});"); builder.AppendLine(); return exporter; } public static void ExportDelegateGlue(GeneratorStringBuilder builder, FunctionExporter exporter) { exporter.ExportFunctionVariables(builder); builder.AppendLine(); builder.AppendLine($"protected void Invoker({exporter._paramStringApiWithDefaults})"); builder.OpenBrace(); exporter.ExportInvoke(builder); builder.CloseBrace(); } public static void ExportInterfaceFunction(GeneratorStringBuilder builder, UhtFunction function) { builder.TryAddWithEditor(function); FunctionExporter exporter = new FunctionExporter(function); exporter.Initialize(OverloadMode.SuppressOverloads, EFunctionProtectionMode.UseUFunctionProtection, EBlueprintVisibility.Call); exporter.ExportSignature(builder, ScriptGeneratorUtilities.PublicKeyword); builder.Append(";"); builder.TryEndWithEditor(function); } public void ForEachParameter(Action action) { for (int i = 0; i < Function.Children.Count; i++) { UhtProperty parameter = (UhtProperty) Function.Children[i]; PropertyTranslator translator = _parameterTranslators[i]; action(translator, parameter); } } public void ExportExtensionMethodOverloads(GeneratorStringBuilder builder) { foreach (FunctionOverload overload in _overloads) { builder.AppendLine(); ExportDeprecation(builder); string returnType = "void"; string returnStatement = ""; if (Function.ReturnProperty != null) { returnType = ReturnValueTranslator!.GetManagedType(Function.ReturnProperty); returnStatement = "return "; } List genericTypes = new List(); List genericConstraints = new List(); if (_hasGenericTypeSupport) { genericTypes.Add("DOT"); genericConstraints.Add(Function.GetGenericTypeConstraint()); } if (_hasCustomStructParamSupport) { genericTypes.AddRange(_customStructParamTypes); genericConstraints.AddRange(_customStructParamTypes.ConvertAll(paramType => $"MarshalledStruct<{paramType}>")); } string genericTypeString = string.Join(", ", genericTypes); if (genericTypes.Count > 0) { PropertyTranslator translator = _parameterTranslators[0]; string paramType = _classBeingExtended != null ? _classBeingExtended.GetFullManagedName() : translator.GetManagedType(_selfParameter!); builder.AppendLine($"{Modifiers}{returnType} {_functionName}<{genericTypeString}>(this {paramType} {_selfParameter!.GetParameterName()}, {overload.ParamStringApiWithDefaults})"); builder.Indent(); foreach (var (genericType, constraint) in genericTypes.Zip(genericConstraints)) builder.AppendLine($"where {genericType} : {constraint}"); builder.UnIndent(); } else { builder.AppendLine($"{Modifiers}{returnType} {_functionName}(this {overload.ParamStringApiWithDefaults})"); } builder.OpenBrace(); overload.Translator?.ExportCppDefaultParameterAsLocalVariable(builder, overload.CSharpParamName, overload.CppDefaultValue, Function, overload.Parameter); UhtClass functionOwner = (UhtClass) Function.Outer!; string fullClassName = functionOwner.GetFullManagedName(); if (genericTypes.Count > 0) { builder.AppendLine($"{returnStatement}{fullClassName}.{_functionName}<{genericTypeString}>({overload.ParamsStringCall});"); } else { builder.AppendLine($"{returnStatement}{fullClassName}.{_functionName}({overload.ParamsStringCall});"); } builder.CloseBrace(); } } public void ExportExtensionMethod(GeneratorStringBuilder builder) { builder.AppendLine(); builder.AppendTooltip(Function); ExportDeprecation(builder); string returnManagedType = "void"; if (ReturnValueTranslator != null) { returnManagedType = ReturnValueTranslator.GetManagedType(Function.ReturnProperty!); } string functionNameToUse = Function.IsAutocast() ? Function.GetBlueprintAutocastName() : _functionName; List genericTypes = new List(); List genericConstraints = new List(); if (_hasGenericTypeSupport) { genericTypes.Add("DOT"); genericConstraints.Add(Function.GetGenericTypeConstraint()); } if (_hasCustomStructParamSupport) { genericTypes.AddRange(_customStructParamTypes); genericConstraints.AddRange(_customStructParamTypes.ConvertAll(paramType => $"MarshalledStruct<{paramType}>")); } string genericTypeString = string.Join(", ", genericTypes); if (genericTypes.Count > 0) { builder.AppendLine($"{Modifiers}{returnManagedType} {functionNameToUse}<{genericTypeString}>({_paramStringApiWithDefaults})"); builder.Indent(); foreach (var (genericType, constraint) in genericTypes.Zip(genericConstraints)) builder.AppendLine($"where {genericType} : {constraint}"); builder.UnIndent(); } else { builder.AppendLine($"{Modifiers}{returnManagedType} {functionNameToUse}({_paramStringApiWithDefaults})"); } builder.OpenBrace(); string returnStatement = Function.ReturnProperty != null ? "return " : ""; UhtClass functionOwner = (UhtClass) Function.Outer!; string fullClassName = functionOwner.GetFullManagedName(); builder.AppendLine($"{returnStatement}{fullClassName}.{_functionName}({_paramsStringCall});"); builder.CloseBrace(); } public void ExportFunctionVariables(GeneratorStringBuilder builder) { builder.AppendLine($"// {Function.SourceName}"); if (!BlueprintImplementableEvent) { builder.AppendLine($"static IntPtr {NativeFunctionIntPtr};"); } if (BlueprintEvent) { builder.AppendLine($"IntPtr {InstanceFunctionPtr};"); } if (Function.HasParametersOrReturnValue()) { if (_hasCustomStructParamSupport) { string genericTypes = string.Join(", ", _customStructParamTypes); builder.AppendLine($"static int {Function.SourceName}_NativeParamsSize;"); builder.AppendLine($"static int {Function.SourceName}_ParamsSize<{genericTypes}>()"); builder.Indent(); foreach (string genericType in _customStructParamTypes) { builder.AppendLine($"where {genericType}: MarshalledStruct<{genericType}>"); } List variableNames = new List{$"{Function.SourceName}_NativeParamsSize"}; int customStructureParamIndex = 0; ForEachParameter((translator, parameter) => { if (!parameter.IsCustomStructureType()) return; variableNames.Add($"{_customStructParamTypes[customStructureParamIndex]}.GetNativeDataSize()"); customStructureParamIndex++; }); builder.AppendLine($"=> {string.Join(" + ", variableNames)};"); builder.UnIndent(); builder.AppendLine($"static IntPtr[] {Function.SourceName}_CustomStructureNativeProperties;"); } else { builder.AppendLine($"static int {Function.SourceName}_ParamsSize;"); } } ForEachParameter((translator, parameter) => { translator.ExportParameterVariables(builder, Function, Function.SourceName, parameter, parameter.SourceName); }); } void ExportOverloads(GeneratorStringBuilder builder) { foreach (FunctionOverload overload in _overloads) { builder.AppendLine(); ExportDeprecation(builder); string returnType = "void"; string returnStatement = ""; if (Function.ReturnProperty != null) { returnType = ReturnValueTranslator!.GetManagedType(Function.ReturnProperty); returnStatement = "return "; } List genericTypes = new List(); List genericConstraints = new List(); if (_hasGenericTypeSupport) { genericTypes.Add("DOT"); genericConstraints.Add(Function.GetGenericTypeConstraint()); } if (_hasCustomStructParamSupport) { genericTypes.AddRange(_customStructParamTypes); genericConstraints.AddRange(_customStructParamTypes.ConvertAll(paramType => $"MarshalledStruct<{paramType}>")); } string genericTypeString = string.Join(", ", genericTypes); if (genericTypes.Count > 0) { builder.AppendLine($"{Modifiers}{returnType} {_functionName}<{genericTypeString}>({overload.ParamStringApiWithDefaults})"); builder.Indent(); foreach (var (genericType, constraint) in genericTypes.Zip(genericConstraints)) builder.AppendLine($"where {genericType} : {constraint}"); builder.UnIndent(); } else { builder.AppendLine($"{Modifiers}{returnType} {_functionName}({overload.ParamStringApiWithDefaults})"); } builder.OpenBrace(); overload.Translator?.ExportCppDefaultParameterAsLocalVariable(builder, overload.CSharpParamName, overload.CppDefaultValue, Function, overload.Parameter); if (genericTypes.Count > 0) { builder.AppendLine($"{returnStatement}{_functionName}<{genericTypeString}>({overload.ParamsStringCall});"); } else { builder.AppendLine($"{returnStatement}{_functionName}({overload.ParamsStringCall});"); } builder.CloseBrace(); } } void ExportFunction(GeneratorStringBuilder builder) { builder.AppendLine(); ExportDeprecation(builder); ExportSpecializationGetter(builder); ExportSignature(builder, Modifiers); builder.OpenBrace(); if (Throwing) { builder.AppendLine($"throw new InvalidOperationException(\"Function {Function.EngineName} cannot be called on a Blueprint-only implementer\");"); } else if (BlueprintEvent) { builder.AppendLine($"if ({InstanceFunctionPtr} == IntPtr.Zero)"); builder.OpenBrace(); builder.AppendLine($"{InstanceFunctionPtr} = {ExporterCallbacks.UClassCallbacks}.CallGetNativeFunctionFromInstanceAndName(NativeObject, \"{Function.EngineName}\");"); builder.CloseBrace(); if (BlueprintImplementableEvent) { builder.AppendLine($"if (!{ExporterCallbacks.UFunctionCallbacks}.IsFunctionImplemented({InstanceFunctionPtr}))"); builder.OpenBrace(); builder.AppendLine($"throw new System.NotImplementedException(\"Tried calling {Function.Outer!.EngineName}.{_functionName} which is not implemented in C# or Blueprint!\");"); builder.CloseBrace(); } ExportInvoke(builder, InstanceFunctionPtr); } else { ExportInvoke(builder); } builder.CloseBrace(); builder.AppendLine(); } public void ExportInvoke(GeneratorStringBuilder builder, string functionPtr = "") { builder.BeginUnsafeBlock(); string nativeFunctionIntPtr = string.IsNullOrEmpty(functionPtr) ? NativeFunctionIntPtr : functionPtr; if (!Function.HasParametersOrReturnValue()) { if (string.IsNullOrEmpty(_customInvoke)) { builder.AppendLine($"{_invokeFunction}({_invokeFirstArgument}, {nativeFunctionIntPtr}, {ScriptGeneratorUtilities.IntPtrZero}, {ScriptGeneratorUtilities.IntPtrZero});"); } else { builder.AppendLine(_customInvoke); } } else { if (_hasCustomStructParamSupport) { string genericTypes = string.Join(", ", _customStructParamTypes); builder.AppendLine($"IntPtr Specialization = {Function.SourceName}_GetSpecialization<{genericTypes}>();"); builder.AppendStackAllocFunction($"{Function.SourceName}_ParamsSize<{genericTypes}>()", "Specialization"); } else { builder.AppendStackAllocFunction($"{Function.SourceName}_ParamsSize", nativeFunctionIntPtr, !BlittableFunction); } ForEachParameter((translator, parameter) => { if (parameter.HasAllFlags(EPropertyFlags.ReturnParm)) { return; } string propertyName = GetParameterName(parameter); if (parameter.HasAllFlags(EPropertyFlags.ReferenceParm) || !parameter.HasAllFlags(EPropertyFlags.OutParm)) { string offsetName = TryAddPrecedingCustomStructParams(parameter, parameter.GetOffsetVariableName()); translator.ExportToNative(builder, parameter, parameter.SourceName, "paramsBuffer", offsetName, propertyName); } }); builder.AppendLine(); if (string.IsNullOrEmpty(_customInvoke)) { string invokedFunctionIntPtr = _hasCustomStructParamSupport ? "Specialization" : nativeFunctionIntPtr; string returnValueAddressStr = Function.ReturnProperty != null ? $"paramsBuffer + {TryAddPrecedingCustomStructParams(Function.ReturnProperty, Function.ReturnProperty.GetOffsetVariableName())}" : ScriptGeneratorUtilities.IntPtrZero; builder.AppendLine($"{_invokeFunction}({_invokeFirstArgument}, {invokedFunctionIntPtr}, paramsBuffer, {returnValueAddressStr});"); } else { builder.AppendLine(_customInvoke); } if (Function.ReturnProperty != null || Function.HasOutParams()) { builder.AppendLine(); ForEachParameter((translator, parameter) => { if (!parameter.HasAllFlags(EPropertyFlags.ReturnParm) && (parameter.HasAllFlags(EPropertyFlags.ConstParm) || !parameter.HasAllFlags(EPropertyFlags.OutParm))) { return; } string marshalDestination; if (parameter.HasAllFlags(EPropertyFlags.ReturnParm)) { builder.AppendLine($"{ReturnValueTranslator!.GetManagedType(parameter)} returnValue;"); marshalDestination = "returnValue"; } else { marshalDestination = MakeOutMarshalDestination(parameter, translator, builder); } string offsetName = TryAddPrecedingCustomStructParams(parameter, parameter.GetOffsetVariableName()); translator.ExportFromNative(builder, parameter, parameter.SourceName, $"{marshalDestination} =", "paramsBuffer", offsetName, true, parameter.HasAllFlags(EPropertyFlags.ReferenceParm) && !parameter.HasAllFlags(EPropertyFlags.ReturnParm)); }); } builder.AppendLine(); ForEachParameter((translator, parameter) => { if (!parameter.HasAnyFlags(EPropertyFlags.ReturnParm | EPropertyFlags.OutParm)) { translator.ExportCleanupMarshallingBuffer(builder, parameter, parameter.SourceName); } }); ExportReturnStatement(builder); } builder.EndUnsafeBlock(); } protected virtual string MakeOutMarshalDestination(UhtProperty parameter, PropertyTranslator propertyTranslator, GeneratorStringBuilder builder) { return GetParameterName(parameter); } protected virtual void ExportReturnStatement(GeneratorStringBuilder builder) { if (Function.ReturnProperty != null) { builder.AppendLine("return returnValue;"); } } void ExportSignature(GeneratorStringBuilder builder, string protection) { builder.AppendTooltip(Function); AttributeBuilder attributeBuilder = new AttributeBuilder(Function); if (BlueprintEvent) { attributeBuilder.AddArgument("FunctionFlags.BlueprintEvent"); } attributeBuilder.AddGeneratedTypeAttribute(Function); if (_hasGenericTypeSupport) { if (Function.HasMetadata("DeterminesOutputType")) { attributeBuilder.AddAttribute("UMetaData"); attributeBuilder.AddArgument($"\"DeterminesOutputType\""); attributeBuilder.AddArgument($"\"{Function.GetMetadata("DeterminesOutputType")}\""); } if (Function.HasMetadata("DynamicOutputParam")) { attributeBuilder.AddAttribute("UMetaData"); attributeBuilder.AddArgument($"\"DynamicOutputParam\""); attributeBuilder.AddArgument($"\"{Function.GetMetadata("DynamicOutputParam")}\""); } } if (_hasCustomStructParamSupport) { attributeBuilder.AddAttribute("UMetaData"); attributeBuilder.AddArgument($"\"CustomStructureParam\""); attributeBuilder.AddArgument($"\"{Function.GetMetadata("CustomStructureParam")}\""); } attributeBuilder.Finish(); builder.AppendLine(attributeBuilder.ToString()); string returnType = Function.ReturnProperty != null ? ReturnValueTranslator!.GetManagedType(Function.ReturnProperty) : "void"; List genericTypes = new List(); List genericConstraints = new List(); if (_hasGenericTypeSupport) { genericTypes.Add("DOT"); genericConstraints.Add(Function.GetGenericTypeConstraint()); } if (_hasCustomStructParamSupport) { genericTypes.AddRange(_customStructParamTypes); genericConstraints.AddRange(_customStructParamTypes.ConvertAll(paramType => $"MarshalledStruct<{paramType}>")); } if (genericTypes.Count > 0) { builder.AppendLine($"{protection}{returnType} {_functionName}<{string.Join(", ", genericTypes)}>({_paramStringApiWithDefaults})"); builder.Indent(); foreach (var (genericType, constraint) in genericTypes.Zip(genericConstraints)) builder.AppendLine($"where {genericType} : {constraint}"); builder.UnIndent(); } else { builder.AppendLine($"{protection}{returnType} {_functionName}({_paramStringApiWithDefaults})"); } } void ExportDeprecation(GeneratorStringBuilder builder) { if (Function.HasMetadata("DeprecatedFunction")) { string deprecationMessage = Function.GetMetadata("DeprecationMessage"); if (deprecationMessage.Length == 0) { deprecationMessage = "This function is deprecated."; } else { // Remove nested quotes deprecationMessage = deprecationMessage.Replace("\"", ""); } builder.AppendLine($"[Obsolete(\"{Function.SourceName} is deprecated: {deprecationMessage}\")]"); } } void ExportSpecializationGetter(GeneratorStringBuilder builder) { if (_hasCustomStructParamSupport) { int customStructureParamCount = _customStructParamTypes.Count; string dictionaryKey = customStructureParamCount == 1 ? "IntPtr" : $"({string.Join(", ", Enumerable.Repeat("IntPtr", customStructureParamCount))})"; builder.AppendLine($"static Dictionary<{dictionaryKey}, IntPtr> {Function.SourceName}_Specializations = new Dictionary<{dictionaryKey}, IntPtr>();"); builder.AppendLine($"static IntPtr {Function.SourceName}_GetSpecialization<{string.Join(", ", _customStructParamTypes)}>()"); builder.Indent(); foreach (string customStructParamType in _customStructParamTypes) { builder.AppendLine($"where {customStructParamType} : MarshalledStruct<{customStructParamType}>"); } builder.UnIndent(); builder.OpenBrace(); builder.AppendLine("IntPtr specializationNativeFunction;"); List nativeClassPtrs = _customStructParamTypes.ConvertAll(customStructParamType => $"{customStructParamType}.GetNativeClassPtr()"); string specializationKeyInitializer = nativeClassPtrs.Count == 1 ? nativeClassPtrs[0] : $"({string.Join(", ", nativeClassPtrs)})"; builder.AppendLine($"{dictionaryKey} specializationKey = {specializationKeyInitializer};"); builder.AppendLine($"if(!{Function.SourceName}_Specializations.TryGetValue(specializationKey, out specializationNativeFunction))"); builder.OpenBrace(); builder.BeginUnsafeBlock(); string customStructBufferInitializationList = customStructureParamCount == 1 ? "specializationKey" : string.Join(", ", Enumerable.Range(1, customStructureParamCount).ToList().ConvertAll(i => $"specializationKey.Item{i}")); builder.AppendLine($"IntPtr* customStructBufferAllocation = stackalloc IntPtr[]{{{customStructBufferInitializationList}}};"); builder.AppendLine("IntPtr customStructBuffer = (IntPtr) customStructBufferAllocation;"); string nativeFunctionIntPtr = $"{Function.SourceName}_NativeFunction"; string customStructNativePropertiesIntPtr = $"{Function.SourceName}_CustomStructureNativeProperties"; builder.AppendLine($"fixed(nint* nativePropertyBuffer = {customStructNativePropertiesIntPtr})"); builder.OpenBrace(); builder.AppendLine($"specializationNativeFunction = {ExporterCallbacks.UFunctionCallbacks}.CallCreateNativeFunctionCustomStructSpecialization({nativeFunctionIntPtr}, (nint) nativePropertyBuffer, customStructBuffer);"); builder.CloseBrace(); builder.AppendLine($"{Function.SourceName}_Specializations.Add(specializationKey, specializationNativeFunction);"); builder.EndUnsafeBlock(); builder.CloseBrace(); builder.AppendLine("return specializationNativeFunction;"); builder.CloseBrace(); } } void DetermineProtectionMode() { switch (_protectionMode) { case EFunctionProtectionMode.UseUFunctionProtection: if (Function.HasAnyFlags(EFunctionFlags.Public | EFunctionFlags.BlueprintCallable)) { Modifiers = ScriptGeneratorUtilities.PublicKeyword; } else if (Function.HasAllFlags(EFunctionFlags.Protected) || Function.HasMetadata("BlueprintProtected")) { Modifiers = ScriptGeneratorUtilities.ProtectedKeyword; } else { Modifiers = ScriptGeneratorUtilities.PrivateKeyword; } break; case EFunctionProtectionMode.OverrideWithInternal: Modifiers = "internal "; break; } } string DetermineInvokeFunction() { string invokeFunction = ExporterCallbacks.UObjectCallbacks; if (Function.HasAllFlags(EFunctionFlags.Static)) { return invokeFunction + ".CallInvokeNativeStaticFunction"; } if (Function.HasAllFlags(EFunctionFlags.Net)) { return invokeFunction + ".CallInvokeNativeNetFunction"; } if (Function.HasAllFlags(EFunctionFlags.HasOutParms) || Function.HasReturnProperty) { return invokeFunction + ".CallInvokeNativeFunctionOutParms"; } return invokeFunction + ".CallInvokeNativeFunction"; } public string TryAddPrecedingCustomStructParams(UhtProperty parameter, string name) { if (!_hasCustomStructParamSupport) { return name; } int precedingCustomStructParams = parameter.GetPrecedingCustomStructParams(); if (precedingCustomStructParams > 0) { return name + $"<{string.Join(", ", _customStructParamTypes.GetRange(0, precedingCustomStructParams))}>()"; } return name; } }