347 lines
11 KiB
C#
347 lines
11 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Reflection;
|
|
using System.Text.Json;
|
|
using System.Threading.Tasks;
|
|
using EpicGames.Core;
|
|
using EpicGames.UHT.Types;
|
|
using EpicGames.UHT.Utils;
|
|
using UnrealSharpScriptGenerator.Exporters;
|
|
using UnrealSharpScriptGenerator.Model;
|
|
using UnrealSharpScriptGenerator.PropertyTranslators;
|
|
using UnrealSharpScriptGenerator.Utilities;
|
|
|
|
namespace UnrealSharpScriptGenerator;
|
|
|
|
class ModuleFolders
|
|
{
|
|
public Dictionary<string, DateTime> DirectoryToWriteTime { get; set; } = new();
|
|
public bool HasBeenExported;
|
|
}
|
|
|
|
public static class CSharpExporter
|
|
{
|
|
const string ModuleDataFileName = "UnrealSharpModuleData.json";
|
|
private const string SpecialtypesJson = "SpecialTypes.json";
|
|
public static bool HasModifiedEngineGlue;
|
|
|
|
private static readonly List<Task> Tasks = new();
|
|
private static readonly List<string> ExportedDelegates = new();
|
|
private static readonly Dictionary<string, DateTime> CachedDirectoryTimes = new();
|
|
private static Dictionary<string, ModuleFolders?> _modulesWriteInfo = new();
|
|
|
|
public static void StartExport()
|
|
{
|
|
if (!HasChangedGeneratorSourceRecently())
|
|
{
|
|
// The source for this generator hasn't changed, so we don't need to re-export the whole API.
|
|
DeserializeModuleData();
|
|
}
|
|
else
|
|
{
|
|
// Just in case the source has changed, we need to clean the old files
|
|
Console.WriteLine("Detected new source since last export, cleaning old files...");
|
|
FileExporter.CleanModuleFolders();
|
|
}
|
|
|
|
Console.WriteLine("Exporting C++ to C#...");
|
|
|
|
#if UE_5_5_OR_LATER
|
|
foreach (UhtModule module in Program.Factory.Session.Modules)
|
|
{
|
|
foreach (UhtPackage modulePackage in module.Packages)
|
|
{
|
|
ExportPackage(modulePackage);
|
|
}
|
|
}
|
|
#else
|
|
foreach (UhtPackage package in Program.Factory.Session.Packages)
|
|
{
|
|
ExportPackage(package);
|
|
}
|
|
#endif
|
|
|
|
WaitForTasks();
|
|
|
|
FunctionExporter.StartExportingExtensionMethods(Tasks);
|
|
|
|
WaitForTasks();
|
|
|
|
AutocastExporter.StartExportingAutocastFunctions(Tasks);
|
|
|
|
WaitForTasks();
|
|
|
|
SerializeModuleData();
|
|
|
|
string generatedCodeDirectory = Program.PluginModule.OutputDirectory;
|
|
string typeInfoFilePath = Path.Combine(generatedCodeDirectory, SpecialtypesJson);
|
|
OutputTypeRules(typeInfoFilePath);
|
|
}
|
|
|
|
static void DeserializeModuleData()
|
|
{
|
|
if (!Directory.Exists(Program.EngineGluePath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
string outputPath = Path.Combine(Program.PluginModule.OutputDirectory, ModuleDataFileName);
|
|
|
|
if (!File.Exists(outputPath))
|
|
{
|
|
return;
|
|
}
|
|
|
|
using FileStream fileStream = new FileStream(outputPath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
Dictionary<string, ModuleFolders>? jsonValue = JsonSerializer.Deserialize<Dictionary<string, ModuleFolders>>(fileStream);
|
|
|
|
if (jsonValue != null)
|
|
{
|
|
_modulesWriteInfo = new Dictionary<string, ModuleFolders?>(jsonValue!);
|
|
}
|
|
}
|
|
|
|
static void SerializeModuleData()
|
|
{
|
|
string outputPath = Path.Combine(Program.PluginModule.OutputDirectory, ModuleDataFileName);
|
|
using FileStream fs = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
|
|
JsonSerializer.Serialize(fs, _modulesWriteInfo);
|
|
}
|
|
|
|
static bool HasChangedGeneratorSourceRecently()
|
|
{
|
|
string executingAssemblyPath = Assembly.GetExecutingAssembly().Location;
|
|
DateTime executingAssemblyLastWriteTime = File.GetLastWriteTimeUtc(executingAssemblyPath);
|
|
|
|
string generatedCodeDirectory = Program.PluginModule.OutputDirectory;
|
|
string timestampFilePath = Path.Combine(generatedCodeDirectory, "Timestamp");
|
|
string typeInfoFilePath = Path.Combine(generatedCodeDirectory, SpecialtypesJson);
|
|
|
|
if (!File.Exists(timestampFilePath) || !File.Exists(typeInfoFilePath) || !Directory.Exists(Program.EngineGluePath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (TypeRulesChanged(typeInfoFilePath))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
DateTime savedTimestampUtc = File.GetLastWriteTimeUtc(timestampFilePath);
|
|
return executingAssemblyLastWriteTime > savedTimestampUtc;
|
|
}
|
|
|
|
static bool TypeRulesChanged(string typeInfoFilePath)
|
|
{
|
|
using var fs = new FileStream(typeInfoFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
|
|
var rules = JsonSerializer.Deserialize<SpecialTypeInfo>(fs);
|
|
if (rules == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return !rules.Equals(PropertyTranslatorManager.SpecialTypeInfo);
|
|
}
|
|
|
|
static void OutputTypeRules(string typeInfoFilePath)
|
|
{
|
|
using var fs = new FileStream(typeInfoFilePath, FileMode.Create, FileAccess.Write);
|
|
JsonSerializer.Serialize(fs, PropertyTranslatorManager.SpecialTypeInfo);
|
|
}
|
|
|
|
private static void WaitForTasks()
|
|
{
|
|
Task[] waitTasks = Tasks.ToArray();
|
|
if (waitTasks.Length > 0)
|
|
{
|
|
Task.WaitAll(waitTasks);
|
|
}
|
|
Tasks.Clear();
|
|
}
|
|
|
|
private static void ExportPackage(UhtPackage package)
|
|
{
|
|
if (!package.ShouldExport())
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!Program.BuildingEditor && package.PackageFlags.HasAnyFlags(EPackageFlags.EditorOnly | EPackageFlags.UncookedOnly))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!package.IsPartOfEngine())
|
|
{
|
|
package.FindOrAddProjectInfo();
|
|
}
|
|
|
|
string packageName = package.GetShortName();
|
|
|
|
if (!_modulesWriteInfo.TryGetValue(packageName, out ModuleFolders? lastEditTime))
|
|
{
|
|
lastEditTime = new ModuleFolders();
|
|
_modulesWriteInfo.Add(packageName, lastEditTime);
|
|
}
|
|
|
|
HashSet<string> processedDirectories = new();
|
|
|
|
string generatedPath = FileExporter.GetDirectoryPath(package);
|
|
bool doesDirectoryExist = Directory.Exists(generatedPath);
|
|
|
|
foreach (UhtType child in package.Children)
|
|
{
|
|
string directoryName = Path.GetDirectoryName(child.HeaderFile.FilePath)!;
|
|
|
|
// We only need to export the C++ directory if it doesn't exist or if it has been modified
|
|
if (!doesDirectoryExist || ShouldExportDirectory(directoryName, lastEditTime!))
|
|
{
|
|
processedDirectories.Add(directoryName);
|
|
ForEachChild(child, ExportType);
|
|
}
|
|
else
|
|
{
|
|
ForEachChild(child, FileExporter.AddUnchangedType);
|
|
}
|
|
}
|
|
|
|
if (processedDirectories.Count == 0)
|
|
{
|
|
// No directories in this package have been exported or modified
|
|
return;
|
|
}
|
|
|
|
// The glue has been exported, so we need to update the last write times
|
|
UpdateLastWriteTimes(processedDirectories, lastEditTime!);
|
|
}
|
|
|
|
private static void ForEachChild(UhtType child, Action<UhtType> action)
|
|
{
|
|
#if UE_5_5_OR_LATER
|
|
action(child);
|
|
|
|
foreach (UhtType type in child.Children)
|
|
{
|
|
action(type);
|
|
}
|
|
#else
|
|
foreach (UhtType type in child.Children)
|
|
{
|
|
action(type);
|
|
|
|
foreach (UhtType innerType in type.Children)
|
|
{
|
|
action(innerType);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
public static bool HasBeenExported(string directory)
|
|
{
|
|
return _modulesWriteInfo.TryGetValue(directory, out ModuleFolders? lastEditTime) && lastEditTime is
|
|
{
|
|
HasBeenExported: true
|
|
};
|
|
}
|
|
|
|
private static bool ShouldExportDirectory(string directoryPath, ModuleFolders lastEditTime)
|
|
{
|
|
if (!CachedDirectoryTimes.TryGetValue(directoryPath, out DateTime cachedTime))
|
|
{
|
|
DateTime currentWriteTime = Directory.GetLastWriteTimeUtc(directoryPath);
|
|
CachedDirectoryTimes[directoryPath] = currentWriteTime;
|
|
cachedTime = currentWriteTime;
|
|
}
|
|
|
|
return !lastEditTime.DirectoryToWriteTime.TryGetValue(directoryPath, out DateTime lastEditTimeValue) || lastEditTimeValue != cachedTime;
|
|
}
|
|
|
|
private static void UpdateLastWriteTimes(HashSet<string> directories, ModuleFolders lastEditTime)
|
|
{
|
|
foreach (string directory in directories)
|
|
{
|
|
if (!CachedDirectoryTimes.TryGetValue(directory, out DateTime cachedTime))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
lastEditTime.DirectoryToWriteTime[directory] = cachedTime;
|
|
lastEditTime.HasBeenExported = true;
|
|
}
|
|
}
|
|
|
|
private static void ExportType(UhtType type)
|
|
{
|
|
if (type.HasMetadata(PackageUtilities.SkipGlueGenerationDefine)
|
|
|| PropertyTranslatorManager.SpecialTypeInfo.Structs.SkippedTypes.Contains(type.SourceName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
bool isManualExport = PropertyTranslatorManager.SpecialTypeInfo.Structs.BlittableTypes.ContainsKey(type.SourceName);
|
|
|
|
if (type is UhtClass classObj)
|
|
{
|
|
if (classObj.HasAllFlags(EClassFlags.Interface))
|
|
{
|
|
if (isManualExport)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (classObj.ClassType is not UhtClassType.Interface && type != Program.Factory.Session.IInterface)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Tasks.Add(Program.Factory.CreateTask(_ => { InterfaceExporter.ExportInterface(classObj); })!);
|
|
}
|
|
else
|
|
{
|
|
Tasks.Add(Program.Factory.CreateTask(_ => { ClassExporter.ExportClass(classObj, isManualExport); })!);
|
|
}
|
|
}
|
|
else if (type is UhtEnum enumObj)
|
|
{
|
|
if (isManualExport)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Tasks.Add(Program.Factory.CreateTask(_ => { EnumExporter.ExportEnum(enumObj); })!);
|
|
}
|
|
else if (type is UhtScriptStruct structObj)
|
|
{
|
|
isManualExport = PropertyTranslatorManager.SpecialTypeInfo.Structs.BlittableTypes.TryGetValue(structObj.SourceName, out var info) && info.ManagedType is not null;
|
|
Tasks.Add(Program.Factory.CreateTask(_ => { StructExporter.ExportStruct(structObj, isManualExport); })!);
|
|
}
|
|
else if (type.EngineType == UhtEngineType.Delegate)
|
|
{
|
|
if (isManualExport)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UhtFunction delegateFunction = (UhtFunction) type;
|
|
if (!ScriptGeneratorUtilities.CanExportParameters(delegateFunction) || delegateFunction.ReturnProperty != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// There are some duplicate delegates in the same modules, so we need to check if we already exported it
|
|
string delegateName = DelegateBasePropertyTranslator.GetFullDelegateName(delegateFunction);
|
|
if (ExportedDelegates.Contains(delegateName))
|
|
{
|
|
return;
|
|
}
|
|
|
|
ExportedDelegates.Add(delegateName);
|
|
Tasks.Add(Program.Factory.CreateTask(_ => { DelegateExporter.ExportDelegate(delegateFunction); })!);
|
|
}
|
|
}
|
|
}
|