#include "SourceCodeGenerator.h" #include "Features/IModularFeatures.h" #include "Misc/FileHelper.h" #include "UObject/CoreNet.h" #include "UObject/Interface.h" #define LOCTEXT_NAMESPACE "FSourceCodeGeneratorModule" void FSourceCodeGeneratorModule::StartupModule() { IModularFeatures::Get().RegisterModularFeature(TEXT("ScriptGenerator"), this); } void FSourceCodeGeneratorModule::ShutdownModule() { IModularFeatures::Get().UnregisterModularFeature(TEXT("ScriptGenerator"), this); } FString FSourceCodeGeneratorModule::GetGeneratedCodeModuleName() const { return TEXT("UnrealCSharp"); } bool FSourceCodeGeneratorModule::SupportsTarget(const FString& TargetName) const { const auto SettingFile = FPaths::Combine( FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath())), TEXT("Config/DefaultUnrealCSharpEditorSetting.ini")); auto bEnableExport = false; if (FPaths::FileExists(SettingFile)) { GConfig->GetBool( TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting"), TEXT("bEnableExport"), bEnableExport, SettingFile); } return bEnableExport; } bool FSourceCodeGeneratorModule::ShouldExportClassesForModule(const FString& ModuleName, EBuildModuleType::Type ModuleType, const FString& ModuleGeneratedIncludeDirectory) const { return ExportModules.Contains(ModuleName) && (ModuleType == EBuildModuleType::EngineRuntime || ModuleType == EBuildModuleType::GameRuntime); } void FSourceCodeGeneratorModule::Initialize(const FString& RootLocalPath, const FString& RootBuildPath, const FString& OutputDirectory, const FString& IncludeBase) { OutputPath = OutputDirectory; const auto ProjectPath = FPaths::ConvertRelativePathToFull(FPaths::GetPath(FPaths::GetProjectFilePath())); const auto ProjectSourcePath = FPaths::Combine(ProjectPath, TEXT("Source/")); const auto ProjectPluginsPath = FPaths::Combine(ProjectPath, TEXT("Plugins/")); const auto EngineSourcePath = FPaths::EngineSourceDir(); const auto EnginePluginsPath = FPaths::EnginePluginsDir(); const auto ConfigFilePath = FPaths::Combine(ProjectPath, TEXT("Config/DefaultUnrealCSharpEditorSetting.ini")); const auto ConfigSection = FString(TEXT("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting")); GetModules(ProjectSourcePath, Project); GetModules(ProjectSourcePath, HeaderPath); GetModules(ProjectPluginsPath, HeaderPath); GetPlugins(ProjectPluginsPath, HeaderPath); GetModules(FPaths::Combine(EngineSourcePath, TEXT("Developer/")), HeaderPath); GetModules(FPaths::Combine(EngineSourcePath, TEXT("Editor/")), HeaderPath); GetModules(FPaths::Combine(EngineSourcePath, TEXT("Programs/")), HeaderPath); GetModules(FPaths::Combine(EngineSourcePath, TEXT("Runtime/")), HeaderPath); GetModules(EnginePluginsPath, HeaderPath); GetPlugins(EnginePluginsPath, HeaderPath); if (FPaths::FileExists(ConfigFilePath)) { GConfig->GetArray(*ConfigSection, TEXT("+ExportModule"), ExportModules, ConfigFilePath); } } void FSourceCodeGeneratorModule::ExportClass(UClass* Class, const FString& SourceHeaderFilename, const FString& GeneratedHeaderFilename, bool bHasChanged) { if (CanExportClass(Class)) { TSet DependencyClasses = {Class}; FBigStringBuilder StringBuilder; FBigStringBuilder BodyBuilder; StringBuilder.Append(TEXT( "#pragma once\r\n\r\n" "PRAGMA_DISABLE_DEPRECATION_WARNINGS\r\n\r\n")); BodyBuilder.Appendf(TEXT( "namespace\r\n" "{\r\n" "\tstruct FRegister%s\r\n" "\t{\r\n" "\t\tFRegister%s()\r\n" "\t\t{\r\n" "\t\t\tTBindingClassBuilder<%s>(NAMESPACE_BINDING)\r\n" ), *Class->GetName(), *Class->GetName(), *(Class->GetPrefixCPP() + Class->GetName()) ); for (TFieldIterator Property(Class, EFieldIterationFlags::None); Property; ++Property) { if (CanExportProperty(*Property)) { GetDependencyClasses(*Property, DependencyClasses); ExportProperty(BodyBuilder, Class, *Property); } } for (TFieldIterator Function(Class, EFieldIterationFlags::None); Function; ++Function) { if (CanExportFunction(*Function)) { for (TFieldIterator Property(*Function); Property; ++Property) { GetDependencyClasses(*Property, DependencyClasses); } ExportFunction(BodyBuilder, Class, *Function); } } for (const auto DependencyClass : DependencyClasses) { StringBuilder.Append(GetInclude(DependencyClass)); } StringBuilder.Append(TEXT("\r\n")); BodyBuilder.RemoveSuffix(2); BodyBuilder.Appendf(TEXT( ";\r\n" "\t\t}\r\n" "\t};\r\n" "\r\n" "\t[[maybe_unused]] FRegister%s Register%s;\r\n" "}\r\n\r\n" ), *Class->GetName(), *Class->GetName() ); StringBuilder.Append(BodyBuilder); StringBuilder.Append(TEXT("PRAGMA_ENABLE_DEPRECATION_WARNINGS\r\n")); const auto FilePath = FPaths::Combine(OutputPath, *Class->GetName() + BindingSuffix); SaveIfChanged(FilePath, StringBuilder.ToString()); ExportClasses.AddUnique(Class); } } void FSourceCodeGeneratorModule::FinishExport() { TMap> Packages; for (const auto ExportClass : ExportClasses) { Packages.FindOrAdd(ExportClass->GetPackage()).Add(*ExportClass->GetName()); } for (auto& [Key, Value] : Packages) { Value.Sort(); FBigStringBuilder StringBuilder; StringBuilder.Append(TEXT("#pragma once\r\n\r\n")); for (const auto& Name : Value) { StringBuilder.Append(GenerateInclude(*Name + BindingSuffix)); } const auto FilePath = FPaths::Combine( OutputPath, *FPaths::GetCleanFilename(Key->GetName()) + HeaderSuffix); SaveIfChanged(FilePath, StringBuilder.ToString()); } } FString FSourceCodeGeneratorModule::GetGeneratorName() const { return TEXT("UnrealCSharp Source Code Generator"); } bool FSourceCodeGeneratorModule::CanExportClass(const UClass* Class) { return !Class->HasAnyClassFlags(CLASS_Interface) && IsClassTypeSupported(Class); } bool FSourceCodeGeneratorModule::CanExportFunction(const UFunction* Function) { if (const auto OwnerClass = Function->GetOwnerClass()) { if (!OwnerClass->HasAnyClassFlags(CLASS_RequiredAPI) && !Function->HasAnyFunctionFlags(FUNC_RequiredAPI)) { return false; } } if (Function->HasAnyFunctionFlags(FUNC_Delegate | FUNC_MulticastDelegate)) { return false; } if (!Function->HasAnyFunctionFlags(FUNC_Public)) { return false; } if (Function->HasAnyFunctionFlags(FUNC_EditorOnly)) { return false; } if (Function->HasMetaData(FName(TEXT("CustomThunk")))) { return false; } if (const auto ReturnProperty = Function->GetReturnProperty()) { if (ReturnProperty->IsA()) { return false; } } for (TFieldIterator Property(Function); Property; ++Property) { if (!IsPropertyTypeSupported(*Property)) { return false; } } return true; } bool FSourceCodeGeneratorModule::CanExportProperty(const FProperty* Property) { return !Property->HasAnyPropertyFlags(CPF_EditorOnly) && Property->HasAnyPropertyFlags(CPF_NativeAccessSpecifierPublic) && IsPropertyTypeSupported(Property); } void FSourceCodeGeneratorModule::ExportFunction(FBigStringBuilder& StringBuilder, const UClass* Class, const UFunction* Function) { StringBuilder.Appendf(TEXT( "\t\t\t\t.Function(\"%s\", BINDING_OVERLOAD(%s, &%s::%s" ), *Function->GetName(), *GetFunctionSignature(Class, Function), *(Class->GetPrefixCPP() + Class->GetName()), *Function->GetName()); auto bHasParameters = false; for (TFieldIterator Property(Function); Property; ++Property) { if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm)) { bHasParameters = true; break; } } if (bHasParameters) { StringBuilder.Appendf(TEXT( ", %s" ), *GetFunctionParamName(Function)); } if (!Function->HasAnyFunctionFlags(FUNC_Static)) { StringBuilder.Append(TEXT(", EFunctionInteract::Virtual")); } if (bHasParameters) { StringBuilder.Append(GetFunctionDefaultValue(Function)); } StringBuilder.Append(TEXT("))\r\n")); } void FSourceCodeGeneratorModule::ExportProperty(FBigStringBuilder& StringBuilder, const UClass* Class, const FProperty* Property) { StringBuilder.Appendf(TEXT( "\t\t\t\t.Property(\"%s\", BINDING_PROPERTY(&%s::%s))\r\n" ), *Property->GetNameCPP(), *(Class->GetPrefixCPP() + Class->GetName()), *Property->GetNameCPP()); } bool FSourceCodeGeneratorModule::IsClassTypeSupported(const UClass* Class) { return !Class->HasAnyClassFlags(CLASS_Deprecated) && (Class == UObject::StaticClass() || Class == UClass::StaticClass() || Class->HasAnyClassFlags(CLASS_RequiredAPI | CLASS_MinimalAPI)); } bool FSourceCodeGeneratorModule::IsPropertyTypeSupported(const FProperty* Property) { if (const auto ArrayProperty = CastField(Property)) { return IsPropertyTypeSupported(ArrayProperty->Inner); } if (const auto SetProperty = CastField(Property)) { return IsPropertyTypeSupported(SetProperty->ElementProp); } if (const auto MapProperty = CastField(Property)) { return IsPropertyTypeSupported(MapProperty->KeyProp) && IsPropertyTypeSupported(MapProperty->ValueProp); } if (const auto ObjectProperty = CastField(Property)) { if (!IsClassTypeSupported(ObjectProperty->PropertyClass)) { return false; } } if (const auto ObjectPtrProperty = CastField(Property)) { if (!IsClassTypeSupported(ObjectPtrProperty->PropertyClass)) { return false; } } if (const auto InterfaceProperty = CastField(Property)) { if (InterfaceProperty->InterfaceClass == UInterface::StaticClass()) { return false; } } if (Property->IsA()) { return false; } if (Property->IsA()) { return false; } if (Property->IsA()) { return false; } if (Property->IsA()) { return false; } if (Property->IsA()) { return false; } if (const auto StructProperty = CastField(Property)) { if (!StructProperty->HasNoOpConstructor()) { return false; } } auto bIsBitField = false; if (const auto BoolProperty = CastField(Property)) { if (!BoolProperty->IsNativeBool()) { bIsBitField = true; } } return !bIsBitField && !(Property->ArrayDim > 1); } FString FSourceCodeGeneratorModule::GetReturnPropertySignature(const FProperty* Property) { if (Property == nullptr) { return TEXT("void"); } FString String; auto bIsConst = false; if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst")))) { String.Append(TEXT("const ")); bIsConst = true; } GetPropertySignature(Property, String); if (Property->IsA()) { if (bIsConst && !Property->HasAnyPropertyFlags(CPF_ConstParm)) { String.Append(TEXT("&")); } } else if (Property->HasAnyPropertyFlags(CPF_ReferenceParm) && Property->HasAnyPropertyFlags(CPF_ConstParm)) { String.Append(TEXT("&")); } return String; } FString FSourceCodeGeneratorModule::GetParamPropertySignature(const FProperty* Property) { if (Property == nullptr) { return TEXT("void"); } FString String; auto bIsConst = false; if (Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasMetaData(FName(TEXT("NativeConst")))) { String.Append(TEXT("const ")); bIsConst = true; } GetPropertySignature(Property, String); if (Property->IsA()) { if (bIsConst && !Property->HasAnyPropertyFlags(CPF_ConstParm) || Property->HasAnyPropertyFlags(CPF_OutParm)) { String.Append(TEXT("&")); } } else if (Property->HasAnyPropertyFlags(CPF_OutParm) || Property->HasAnyPropertyFlags(CPF_ReferenceParm)) { String.Append(TEXT("&")); } return String; } FString FSourceCodeGeneratorModule::GetFunctionSignature(const UClass* Class, const UFunction* Function) { FString String; String.Append(GetReturnPropertySignature(Function->GetReturnProperty())); if (Function->HasAnyFunctionFlags(FUNC_Static)) { String.Append(TEXT("(*)")); } else { String.Appendf(TEXT( "(%s::*)" ), *(Class->GetPrefixCPP() + Class->GetName()) ); } String.Append(TEXT("(")); auto bHasParameters = false; for (TFieldIterator Property(Function); Property; ++Property) { if (!Property->HasAnyPropertyFlags(CPF_ReturnParm)) { String.Append(GetParamPropertySignature(*Property)); String.Append(TEXT(", ")); bHasParameters = true; } } if (bHasParameters) { String.LeftInline(String.Len() - 2); } String.Append(TEXT(")")); if (Function->HasAnyFunctionFlags(FUNC_Const)) { String.Append(TEXT("const")); } return String; } FString FSourceCodeGeneratorModule::GetFunctionParamName(const UFunction* Function) { FSmallStringBuilder Builder; Builder.Append(TEXT("TArray{")); auto bIsConst = false; for (TFieldIterator Property(Function); Property; ++Property) { if (!Property->HasAnyPropertyFlags(CPF_ReturnParm)) { Builder.Appendf(TEXT( "\"%s\", " ), *Property->GetName() ); bIsConst = true; } } if (bIsConst) { Builder.RemoveSuffix(2); } Builder.Append(TEXT("}")); return Builder.ToString(); } FString FSourceCodeGeneratorModule::GetFunctionDefaultValue(const UFunction* Function) { if (!Function->HasAnyFunctionFlags(FUNC_BlueprintCallable)) { return FString(); } FSmallStringBuilder Builder; const auto MetaDataMap = UMetaData::GetMapForObject(Function); for (TFieldIterator Property(Function); Property; ++Property) { if (Property->HasAnyPropertyFlags(CPF_Parm) && !Property->HasAnyPropertyFlags(CPF_ReturnParm)) { if (FString Value; FindDefaultValue(MetaDataMap, *Property, Value)) { Builder.Append(TEXT(", ")); if (Value == TEXT("NULL")) { Builder.Append(TEXT("nullptr")); } else { Builder.Append(Value); } } } } return Builder.ToString(); } bool FSourceCodeGeneratorModule::FindDefaultValue(const TMap* MetaDataMap, const FProperty* Param, FString& OutString) { if (MetaDataMap == nullptr || Param == nullptr) { return false; } const FName ParamName(*Param->GetName()); const FName CppKey(*(FString(TEXT("CPP_Default_")) + ParamName.ToString())); if (!MetaDataMap->Contains(CppKey)) { return false; } if (Param->IsA() || Param->IsA() || Param->IsA()) { OutString = TEXT("nullptr"); } else { FString MetaData; if (const auto CppDefaultValue = MetaDataMap->Find(CppKey)) { if (!CppDefaultValue->IsEmpty()) { MetaData = *CppDefaultValue; } } if (const auto StructProperty = CastField(Param)) { if (StructProperty->Struct == TBaseStructure::Get()) { TArray Value; MetaData.ParseIntoArray(Value, TEXT(",")); if (Value.Num() == 3) { OutString = FString::Printf(TEXT( "FVector(%lf, %lf, %lf)" ), TCString::Atod(*Value[0]), TCString::Atod(*Value[1]), TCString::Atod(*Value[2]) ); } } else if (StructProperty->Struct == TBaseStructure::Get()) { if (FVector2D Value; Value.InitFromString(MetaData)) { OutString = FString::Printf(TEXT( "FVector2D(%lf, %lf)" ), Value.X, Value.Y ); } } else if (StructProperty->Struct == TBaseStructure::Get()) { TArray Value; MetaData.ParseIntoArray(Value, TEXT(",")); if (Value.Num() == 3) { OutString = FString::Printf(TEXT( "FRotator(%lf, %lf, %lf)" ), TCString::Atod(*Value[0]), TCString::Atod(*Value[1]), TCString::Atod(*Value[2]) ); } } else if (StructProperty->Struct == TBaseStructure::Get()) { if (FLinearColor Value; Value.InitFromString(MetaData)) { OutString = FString::Printf(TEXT( "FLinearColor(%ff, %ff, %ff, %ff)" ), Value.R, Value.G, Value.B, Value.A ); } } if (OutString.IsEmpty()) { OutString = FString::Printf(TEXT( "%s%s()" ), StructProperty->Struct->GetPrefixCPP(), *StructProperty->Struct->GetName() ); } } else if (Param->IsA()) { OutString = FString::Printf(TEXT( "FString(\"%s\")" ), *MetaData); } else if (Param->IsA()) { OutString = FString::Printf(TEXT( "TEXT(\"%s\")" ), *MetaData); } else if (Param->IsA()) { static auto InvText = FString(TEXT("INVTEXT")); if (MetaData.Contains(InvText)) { MetaData.MidInline(InvText.Len() + 2, MetaData.Len() - InvText.Len() - 4); } OutString = FString::Printf(TEXT( "INVTEXT(\"%s\")" ), *MetaData ); } else { OutString = MetaData; } } return !OutString.IsEmpty(); } void FSourceCodeGeneratorModule::GetPropertySignature(const FProperty* Property, FString& String) { if (const auto ArrayProperty = CastField(Property)) { String.Appendf(TEXT( "TArray<%s>" ), *ArrayProperty->Inner->GetCPPType() ); } else if (const auto SetProperty = CastField(Property)) { String.Appendf(TEXT( "TSet<%s>" ), *SetProperty->ElementProp->GetCPPType() ); } else if (const auto MapProperty = CastField(Property)) { String.Appendf(TEXT( "TMap<%s, %s>" ), *MapProperty->KeyProp->GetCPPType(), *MapProperty->ValueProp->GetCPPType() ); } else if (const auto InterfaceProperty = CastField(Property)) { String.Appendf(TEXT( "TScriptInterface" ), *InterfaceProperty->InterfaceClass->GetName() ); } else { String.Append(Property->GetCPPType()); } } void FSourceCodeGeneratorModule::GetDependencyClasses(const FProperty* Property, TSet& DependencyClasses) { if (const auto ClassProperty = CastField(Property)) { if (ClassProperty->MetaClass != nullptr) { DependencyClasses.Add(ClassProperty->MetaClass); } } else if (const auto ObjectProperty = CastField(Property)) { if (ObjectProperty->PropertyClass != nullptr) { DependencyClasses.Add(ObjectProperty->PropertyClass); } } else if (const auto ObjectPtrProperty = CastField(Property)) { if (ObjectPtrProperty->PropertyClass != nullptr) { DependencyClasses.Add(ObjectPtrProperty->PropertyClass); } } else if (const auto SoftClassProperty = CastField(Property)) { if (SoftClassProperty->MetaClass != nullptr) { DependencyClasses.Add(SoftClassProperty->MetaClass); } } else if (const auto SoftObjectProperty = CastField(Property)) { if (SoftObjectProperty->PropertyClass != nullptr) { DependencyClasses.Add(SoftObjectProperty->PropertyClass); } } else if (const auto ArrayProperty = CastField(Property)) { if (ArrayProperty->Inner != nullptr) { GetDependencyClasses(ArrayProperty->Inner, DependencyClasses); } } } void FSourceCodeGeneratorModule::SaveIfChanged(const FString& FileName, const FString& String) { if (FPaths::FileExists(FileName)) { if (FString Result; FFileHelper::LoadFileToString(Result, *FileName) && Result == String) { return; } } FFileHelper::SaveStringToFile(String, *FileName, FFileHelper::EEncodingOptions::ForceUTF8WithoutBOM); } FString FSourceCodeGeneratorModule::GetHeaderFile(UClass* Class) { FString ModuleRelativePath = Class->GetMetaData(TEXT("ModuleRelativePath")); if (ModuleRelativePath.IsEmpty()) { if (Class == UInterface::StaticClass()) { ModuleRelativePath = TEXT("Public/UObject/Interface.h"); } else if (Class == UPackageMap::StaticClass()) { ModuleRelativePath = TEXT("Public/UObject/CoreNet.h"); } } return FPaths::Combine(HeaderPath.FindRef(FPaths::GetCleanFilename(Class->GetPackage()->GetName())), ModuleRelativePath); } FString FSourceCodeGeneratorModule::GenerateInclude(const FString& FileName) { return FString::Printf(TEXT( "#include \"%s\"\n" ), *FileName ); } FString FSourceCodeGeneratorModule::GetInclude(UClass* Class) { return GenerateInclude(GetHeaderFile(Class)); } void FSourceCodeGeneratorModule::GetPlugins(const FString& InPathName, TMap& Plugins) { const auto Suffix = FString(TEXT("*.uplugin")); if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) { TArray FileNames; FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); for (const auto& FileName : FileNames) { Plugins.Add( FPaths::GetCleanFilename(FileName).Left( FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1), FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName))); } } } void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TMap& Modules) { const auto Suffix = FString(TEXT("*.Build.cs")); if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) { TArray FileNames; FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); for (const auto& FileName : FileNames) { Modules.Add( FPaths::GetCleanFilename(FileName).Left( FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1), FPaths::ConvertRelativePathToFull(FPaths::GetPath(FileName))); } } } void FSourceCodeGeneratorModule::GetModules(const FString& InPathName, TSet& Modules) { const auto Suffix = FString(TEXT("*.Build.cs")); if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName)) { TArray FileNames; FileManager.FindFilesRecursive(FileNames, *InPathName, *Suffix, true, false); for (const auto& FileName : FileNames) { Modules.Add( FPaths::GetCleanFilename(FileName).Left( FPaths::GetCleanFilename(FileName).Len() - Suffix.Len() + 1)); } } } #undef LOCTEXT_NAMESPACE IMPLEMENT_MODULE(FSourceCodeGeneratorModule, SourceCodeGenerator)