// Copyright Epic Games, Inc. All Rights Reserved. using EpicGames.Core; using EpicGames.UHT.Types; using EpicGames.UHT.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using UnrealBuildBase; using UnrealBuildTool; namespace ScriptGeneratorUbtPlugin { abstract internal class ScriptCodeGeneratorBase { public readonly IUhtExportFactory Factory; public UhtSession Session => this.Factory.Session; public ScriptCodeGeneratorBase(IUhtExportFactory Factory) { this.Factory = Factory; } /// /// Export all the classes in all the packages /// public void Generate() { DirectoryReference ConfigDirectory = DirectoryReference.Combine(Unreal.EngineDirectory, "Programs/UnrealBuildTool"); ConfigHierarchy Ini = ConfigCache.ReadHierarchy(ConfigHierarchyType.Engine, ConfigDirectory, BuildHostPlatform.Current.Platform); List? SupportedScriptModules = null; Ini.GetArray("Plugins", "ScriptSupportedModules", out SupportedScriptModules); // Loop through the packages making sure they should be exported. Queue the export of the classes List Classes = new List(); List Tasks = new List(); foreach (UhtPackage Package in this.Session.Packages) { if (Package.Module.ModuleType != UHTModuleType.EngineRuntime && Package.Module.ModuleType != UHTModuleType.GameRuntime) { continue; } if (SupportedScriptModules != null && !SupportedScriptModules.Any(x => string.Compare(x, Package.Module.Name, StringComparison.OrdinalIgnoreCase) == 0)) { continue; } QueueClassExports(Package, Package, Classes, Tasks); } // Wait for all the classes to export Task[]? WaitTasks = Tasks.Where(x => x != null).Cast().ToArray(); if (WaitTasks.Length > 0) { Task.WaitAll(WaitTasks); } // Finish the export process Finish(Classes); } /// /// Collect the classes to be exported for the given package and type /// /// Package being exported /// Type to test for exporting /// Collection of exported classes /// Collection of queued tasks private void QueueClassExports(UhtPackage Package, UhtType Type, List Classes, List Tasks) { if (Type is UhtClass Class) { if (CanExportClass(Class)) { Classes.Add(Class); Tasks.Add(Factory.CreateTask((Factory) => { ExportClass(Package, Class); })); } } foreach (UhtType Child in Type.Children) { QueueClassExports(Package, Child, Classes, Tasks); } } /// /// Test to see if the given class should be exported /// /// Class to test /// True if the class should be exported, false if not protected virtual bool CanExportClass(UhtClass Class) { return Class.ClassFlags.HasAnyFlags(EClassFlags.RequiredAPI | EClassFlags.MinimalAPI); // Don't export classes that don't export DLL symbols } /// /// Test to see if the given function should be exported /// /// Owning class of the function /// Function to test /// True if the function should be exported protected virtual bool CanExportFunction(UhtClass Class, UhtFunction Function) { // We don't support delegates and non-public functions if (Function.FunctionFlags.HasAnyFlags(EFunctionFlags.Delegate)) { return false; } // Reject if any of the parameter types is unsupported yet foreach (UhtType Child in Function.Children) { if (Child is UhtArrayProperty || Child is UhtDelegateProperty || Child is UhtMulticastDelegateProperty || Child is UhtWeakObjectPtrProperty || Child is UhtInterfaceProperty) { return false; } if (Child is UhtProperty Property && Property.bIsStaticArray) { return false; } } return true; } /// /// Test to see if the given property should be exported /// /// Owning class of the property /// Property to test /// True if the property should be exported protected virtual bool CanExportProperty(UhtClass Class, UhtProperty Property) { // Property must be DLL exported if (!Class.ClassFlags.HasAnyFlags(EClassFlags.RequiredAPI)) { return false; } // Only public, editable properties can be exported if (Property.PropertyFlags.HasAnyFlags(EPropertyFlags.NativeAccessSpecifierPrivate | EPropertyFlags.NativeAccessSpecifierProtected) || !Property.PropertyFlags.HasAnyFlags(EPropertyFlags.Edit)) { return false; } // Reject if any of the parameter types is unsupported yet if (Property.bIsStaticArray || Property is UhtArrayProperty || Property is UhtDelegateProperty || Property is UhtMulticastDelegateProperty || Property is UhtWeakObjectPtrProperty || Property is UhtInterfaceProperty || Property is UhtStructProperty) { return false; } return true; } /// /// Export the given class /// /// Factory associated with the export /// Owning package /// Class to export private void ExportClass(UhtPackage Package, UhtClass Class) { using (BorrowStringBuilder Borrower = new BorrowStringBuilder(StringBuilderCache.Big)) { ExportClass(Borrower.StringBuilder, Class); string FileName = this.Factory.MakePath(Class.EngineName, ".script.h"); //ETSTODO - WRONG this.Factory.CommitOutput(FileName, Borrower.StringBuilder); } } private void Finish(List Classes) { using (BorrowStringBuilder Borrower = new BorrowStringBuilder(StringBuilderCache.Big)) { Finish(Borrower.StringBuilder, Classes); string FileName = this.Factory.MakePath("GeneratedScriptLibraries", ".inl"); //ETSTODO - WRONG this.Factory.CommitOutput(FileName, Borrower.StringBuilder); } } abstract protected void ExportClass(StringBuilder Builder, UhtClass Class); abstract protected void Finish(StringBuilder Builder, List Classes); virtual protected StringBuilder AppendInitializeFunctionDispatchParam(StringBuilder Builder, UhtClass Class, UhtFunction? Function, UhtProperty Property, int PropertyIndex) { if (Property is UhtObjectPropertyBase) { Builder.Append("NULL"); } else { Builder.AppendPropertyText(Property, UhtPropertyTextType.GenericFunctionArgOrRetVal).Append("()"); } return Builder; } virtual protected StringBuilder AppendFunctionDispatch(StringBuilder Builder, UhtClass Class, UhtFunction Function) { bool bHasParamsOrReturnValue = Function.Children.Count > 0; if (bHasParamsOrReturnValue) { Builder.Append("\tstruct FDispatchParams\r\n"); Builder.Append("\t{\r\n"); foreach (UhtProperty Property in Function.Children) { Builder.Append("\t\t").AppendPropertyText(Property, UhtPropertyTextType.GenericFunctionArgOrRetVal).Append(' ').Append(Property.SourceName).Append(";\r\n"); } Builder.Append("\t} Params;\r\n"); int PropertyIndex = 0; foreach (UhtProperty Property in Function.Children) { Builder.Append("\tParams.").Append(Property.SourceName).Append(" = "); AppendInitializeFunctionDispatchParam(Builder, Class, Function, Property, PropertyIndex).Append(";\r\n"); PropertyIndex++; } } Builder.Append("\tstatic UFunction* Function = Obj->FindFunctionChecked(TEXT(\"").Append(Function.SourceName).Append("\"));\r\n"); if (bHasParamsOrReturnValue) { Builder.Append("\tcheck(Function->ParmsSize == sizeof(FDispatchParams));\r\n"); Builder.Append("\tObj->ProcessEvent(Function, &Params);\r\n"); } else { Builder.Append("\tObj->ProcessEvent(Function, NULL);\r\n"); } return Builder; } } }