using System; using EpicGames.UHT.Tables; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using EpicGames.Core; namespace UnLuaDefaultParamCollectorUbtPlugin { [UnrealHeaderTool] class UnLuaDefaultParamCollectorUbtPlugin { [UhtExporter(Name = "UnLua", Description = "UnLua Default Param Collector", Options = UhtExporterOptions.Default, ModuleName = "UnLua")] private static void UnLuaDefaultParamCollectorUbtPluginExporter(IUhtExportFactory factory) { new UnLuaDefaultParamCollectorUbtPlugin(factory).Generate(); } public UnLuaDefaultParamCollectorUbtPlugin(IUhtExportFactory factory) { Factory = factory; Borrower = new BorrowStringBuilder(StringBuilderCache.Big); bHasGameRuntime = false; bCurrentClassWritten = false; bCurrentFunctionWritten = false; GeneratedContentBuilder.Append("// Generated By C# UbtPlugin\r\n"); GeneratedContentBuilder.Append("FFunctionCollection* FC = nullptr;\r\n"); GeneratedContentBuilder.Append("FParameterCollection* PC = nullptr;\r\n"); GeneratedContentBuilder.Append("\r\n"); GeneratedContentBuilder.Append("FBoolParamValue* SharedBool_TRUE = new FBoolParamValue(true);\r\n"); GeneratedContentBuilder.Append("FBoolParamValue* SharedBool_FALSE = new FBoolParamValue(false);\r\n"); GeneratedContentBuilder.Append("FFloatParamValue* SharedFloat_Zero = new FFloatParamValue(0.000000f);\r\n"); GeneratedContentBuilder.Append("FFloatParamValue* SharedFloat_One = new FFloatParamValue(1.000000f);\r\n"); GeneratedContentBuilder.Append("FEnumParamValue* SharedEnum_Zero = new FEnumParamValue(0);\r\n"); GeneratedContentBuilder.Append("FIntParamValue* SharedInt_Zero = new FIntParamValue(0);\r\n"); GeneratedContentBuilder.Append("FByteParamValue* SharedByte_Zero = new FByteParamValue(0);\r\n"); GeneratedContentBuilder.Append("FNameParamValue* SharedFName_None = new FNameParamValue(FName(\"None\"));\r\n"); GeneratedContentBuilder.Append("\r\n"); GeneratedContentBuilder.Append("FVectorParamValue* SharedFVector_Zero = new FVectorParamValue(FVector(EForceInit::ForceInitToZero));\r\n"); GeneratedContentBuilder.Append("FVector2DParamValue* SharedFVector2D_Zero = new FVector2DParamValue(FVector2D(EForceInit::ForceInitToZero));\r\n"); GeneratedContentBuilder.Append("FRotatorParamValue* SharedFRotator_Zero = new FRotatorParamValue(FRotator(EForceInit::ForceInitToZero));\r\n"); GeneratedContentBuilder.Append("FLinearColorParamValue* SharedFLinearColor_Zero = new FLinearColorParamValue(FLinearColor(EForceInit::ForceInitToZero));\r\n"); GeneratedContentBuilder.Append("FColorParamValue* SharedFColor_Zero = new FColorParamValue(FColor(EForceInit::ForceInitToZero));\r\n"); GeneratedContentBuilder.Append("\r\n"); GeneratedContentBuilder.Append("FScriptArrayParamValue* SharedScriptArray = new FScriptArrayParamValue();\r\n"); GeneratedContentBuilder.Append("FScriptDelegateParamValue* SharedScriptDelegate = new FScriptDelegateParamValue(FScriptDelegate());\r\n"); GeneratedContentBuilder.Append("FMulticastScriptDelegateParamValue* SharedMulticastScriptDelegate = new FMulticastScriptDelegateParamValue(FMulticastScriptDelegate());\r\n"); GeneratedContentBuilder.Append("\r\n"); } private void Generate() { foreach (UhtPackage package in Session.Packages) { var moduleType = package.Module.ModuleType; ParseModule(package.Module.Name, moduleType, package.Module.OutputDirectory); if (moduleType != UHTModuleType.EngineRuntime && moduleType != UHTModuleType.GameRuntime) { continue; } QueueClassExports(package, package); } // Wait for all the classes to export Finish(); } private void QueueClassExports(UhtPackage package, UhtType type) { if (type is UhtClass classObj) { if (CanExportClass(classObj)) { ExportClass(classObj); } } foreach (UhtType child in type.Children) { QueueClassExports(package, child); } } private bool CanExportClass(UhtClass classObj) { // [Mark]: The implementation in "ScriptGeneratorPlugin" is much more complicated, is that necessary? return !classObj.ClassFlags.HasAnyFlags(EClassFlags.Interface); } private void ExportClass(UhtClass classObj) { // [Mark]: How to remove the deprecated functions? Can't find DEPRECATED flag in EFunctionFlog. foreach (UhtFunction function in classObj.Functions.Reverse()) { if (CanExportFunction(function)) { ExportFunction(classObj, function); } } if (bCurrentClassWritten) { bCurrentClassWritten = false; GeneratedContentBuilder.Append("\r\n"); } } private bool CanExportFunction(UhtFunction function) { // [Mark]: The implementation in "ScriptGeneratorPlugin" is much more complicated, is that necessary? return !function.MetaData.IsEmpty(); } private void ExportFunction(UhtClass classObj, UhtFunction function) { bCurrentFunctionWritten = false; var metaData = function.MetaData; var autoCreateRefTerm = metaData.GetValueOrDefault("AutoCreateRefTerm"); var autoEmitParameterNames = new string[] {}; if (!string.IsNullOrEmpty(autoCreateRefTerm)) { autoEmitParameterNames = autoCreateRefTerm.Split(','); for (int i = 0; i < autoEmitParameterNames.Length; i++) { autoEmitParameterNames[i] = autoEmitParameterNames[i].Trim(); } } foreach (UhtType child in function.Children) { if (child is UhtProperty property && CanExportParamProperty(classObj, property)) { ExportParamProperty(classObj, function, property, metaData, autoEmitParameterNames); } } } private bool CanExportParamProperty(UhtClass classObj, UhtProperty property) { return property.PropertyFlags.HasAnyFlags(EPropertyFlags.Parm) && !property.PropertyFlags.HasAnyFlags(EPropertyFlags.ReturnParm); } private void ExportParamProperty(UhtClass classObj, UhtFunction function, UhtProperty property, UhtMetaData metaData, string[] autoEmitParameterNames) { if (!FindDefaultValueString(metaData, property, out string valueStr)) { if (!Array.Exists(autoEmitParameterNames, element => element == property.SourceName)) { return; } } if (property is UhtStructProperty structProperty) { var structTypeName = structProperty.ScriptStruct.EngineName; if (structTypeName.Equals("Vector")) { if (string.IsNullOrEmpty(valueStr)) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFVector_Zero);\r\n", property.SourceName); } else { var values = valueStr.Split(",").Select(float.Parse).ToArray(); if (values.Length == 3) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FVectorParamValue(FVector({1:F6}f,{2:F6}f,{3:F6}f)));\r\n", property.SourceName, values[0], values[1], values[2]); } } } else if (structTypeName.Equals("Rotator")) { if (string.IsNullOrEmpty(valueStr)) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFRotator_Zero);\r\n", property.SourceName); } else { var values = valueStr.Split(",").Select(float.Parse).ToArray(); if (values.Length == 3) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FRotatorParamValue(FRotator({1:F6}f,{2:F6}f,{3:F6}f)));\r\n", property.SourceName, values[0], values[1], values[2]); } } } else if (structTypeName.Equals("Vector2D")) { if (string.IsNullOrEmpty(valueStr)) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFVector2D_Zero);\r\n", property.SourceName); } else { var values = Regex.Split(valueStr, @"[^\d.]+").Where(x => !string.IsNullOrEmpty(x)).Select(float.Parse).ToArray(); PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FVector2DParamValue(FVector2D({1:F6}f,{2:F6}f)));\r\n", property.SourceName, values[0], values[1]); } } else if (structTypeName.Equals("LinearColor")) { if (string.IsNullOrEmpty(valueStr)) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFLinearColor_Zero);\r\n", property.SourceName); } else { var values = Regex.Split(valueStr, @"[^\d.]+").Where(x => !string.IsNullOrEmpty(x)).Select(float.Parse).ToArray(); PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FLinearColorParamValue(FLinearColor({1:F6}f,{2:F6}f,{3:F6}f,{4:F6}f)));\r\n", property.SourceName, values[0], values[1], values[2], values[3]); } } else if (structTypeName.Equals("Color")) { if (string.IsNullOrEmpty(valueStr)) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFColor_Zero);\r\n", property.SourceName); } else { var values = Regex.Split(valueStr, @"[^\d.]+").Where(x => !string.IsNullOrEmpty(x)).ToArray(); PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FColorParamValue(FColor({1},{2},{3},{4})));\r\n", property.SourceName, values[0], values[1], values[2], values[3]); } } } else if (property is UhtIntProperty) { int.TryParse(valueStr, out var value); PreAddProperty(classObj, function); if (value == 0) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedInt_Zero);\r\n", property.SourceName); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FIntParamValue({1}));\r\n", property.SourceName, value); } } else if (property is UhtByteProperty byteProperty) { if (byteProperty.Enum != null) { var index = byteProperty.Enum.GetIndexByName(valueStr); var value = index == -1 ? -1 : byteProperty.Enum.EnumValues[index].Value; PreAddProperty(classObj, function); if (value == 0) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedByte_Zero);\r\n", property.SourceName); } else if (value is > 0 and <= 255) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FByteParamValue({1}));\r\n", property.SourceName, value); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FRuntimeEnumParamValue(\"{1}\",{2}));\r\n", property.SourceName, byteProperty.Enum.CppType, index); } } else { int.TryParse(valueStr, out var value); PreAddProperty(classObj, function); if (value == 0) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedByte_Zero);\r\n", property.SourceName); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FByteParamValue({1}));\r\n", property.SourceName, value); } } } else if (property is UhtEnumProperty enumProperty) { // [Mark]: A ByteProperty in C++ may be recognized as EnumProperty in C#, its UnderlyingProperty is null var index = enumProperty.Enum.GetIndexByName(valueStr); var value = index == -1 ? -1 : enumProperty.Enum.EnumValues[index].Value; PreAddProperty(classObj, function); var isFakeEnum = enumProperty.UnderlyingProperty == null; if (value == 0) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), {1});\r\n", property.SourceName, isFakeEnum ? "SharedByte_Zero" : "SharedEnum_Zero"); } else if (value is > 0 and <= 255) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new {1}({2}));\r\n", property.SourceName, isFakeEnum ? "FByteParamValue" : "FEnumParamValue", value); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FRuntimeEnumParamValue(\"{1}\",{2}));\r\n", property.SourceName, enumProperty.Enum.CppType, index); } } else if (property is UhtFloatProperty) { // 1.f is not valid in C# float.TryParse(valueStr.Replace(".f", ""), out var value); PreAddProperty(classObj, function); if (Math.Abs(value) < 1E-8) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFloat_Zero);\r\n", property.SourceName); } else if (Math.Abs(value - 1) < 1E-8) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFloat_One);\r\n", property.SourceName); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FFloatParamValue({1:F6}f));\r\n", property.SourceName, value); } } else if (property is UhtDoubleProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FDoubleParamValue({1:F6}));\r\n", property.SourceName, double.Parse(valueStr)); } else if (property is UhtBoolProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedBool_{1});\r\n", property.SourceName, valueStr.ToUpper()); } else if (property is UhtNameProperty) { PreAddProperty(classObj, function); if (valueStr.Equals("None")) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedFName_None);\r\n", property.SourceName); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FNameParamValue(FName(\"{1}\")));\r\n", property.SourceName, valueStr); } } else if (property is UhtTextProperty) { PreAddProperty(classObj, function); if (valueStr.StartsWith("INVTEXT(\"")) { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FTextParamValue({1}));\r\n", property.SourceName, valueStr); } else { GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FTextParamValue(FText::FromString(TEXT(\"{1}\"))));\r\n", property.SourceName, valueStr); } } else if (property is UhtStrProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), new FStringParamValue(TEXT(\"{1}\")));\r\n", property.SourceName, valueStr); } else if (property is UhtArrayProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedScriptArray);\r\n", property.SourceName); } else if (property is UhtDelegateProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedScriptDelegate);\r\n", property.SourceName); } else if (property is UhtMulticastDelegateProperty) { PreAddProperty(classObj, function); GeneratedContentBuilder.AppendFormat("PC->Parameters.Add(TEXT(\"{0}\"), SharedMulticastDelegate);\r\n", property.SourceName); } } private void PreAddProperty(UhtClass classObj, UhtFunction function) { if (!bCurrentClassWritten) { bCurrentClassWritten = true; GeneratedContentBuilder.AppendFormat("FC = &GDefaultParamCollection.Add(TEXT(\"{0}\"));\r\n", classObj.EngineNamePrefix + classObj.EngineName); } if (!bCurrentFunctionWritten) { bCurrentFunctionWritten = true; GeneratedContentBuilder.AppendFormat("PC = &FC->Functions.Add(TEXT(\"{0}\"));\r\n", function.StrippedFunctionName); } } private static bool FindDefaultValueString(UhtMetaData metaData, UhtProperty property, out string value) { var hasValue = metaData.TryGetValue(property.SourceName, out string? tempValue); if (!hasValue) { var cppKey = "CPP_Default_" + property.SourceName; hasValue = metaData.TryGetValue(cppKey, out tempValue); } value = tempValue ?? string.Empty; return hasValue; } private void ParseModule(string moduleName, UHTModuleType moduleType, string IncludeBase) { GeneratedContentBuilder.AppendFormat("// ModuleName {0} Type {1}({2}) ModuleGeneratedIncludeDirectory {3} \r\n", moduleName, moduleType, (int)moduleType, IncludeBase.Replace('\\', '/')); if (moduleType == UHTModuleType.GameRuntime) { bHasGameRuntime = true; } } private void Finish() { var generatedFileContent = GeneratedContentBuilder.ToString(); string filePath = Factory.MakePath("DefaultParamCollection", ".inl"); if (File.Exists(filePath)) { var fileContent = File.ReadAllText(filePath); if (bHasGameRuntime ? (!fileContent.Equals(generatedFileContent)) : (fileContent.Length != 0)) { Factory.CommitOutput(filePath, GeneratedContentBuilder); } } else { Factory.CommitOutput(filePath, GeneratedContentBuilder); } } private IUhtExportFactory Factory; private UhtSession Session => Factory.Session; private bool bHasGameRuntime; private bool bCurrentClassWritten; private bool bCurrentFunctionWritten; private BorrowStringBuilder Borrower; private StringBuilder GeneratedContentBuilder => Borrower.StringBuilder; } }