13 Commits
Author SHA1 Message Date
crazytuzi 0f2fd1382f UTF-8 2026-07-07 19:09:50 +08:00
yawn831 e5c27effd7 feat: 头文件注释 (#10)
* feat: 头文件注释

* feat: 头文件注释提交
2026-01-22 10:31:06 +08:00
crazytuzi 7bcb7823a6 Merge pull request #8 from yawn831/Black
feat: 新增blacklist
2025-06-28 20:33:16 +08:00
yawn831831 197fbbe6ce feat: 新增blacklist 2025-06-28 01:12:32 +08:00
crazytuzi bf9949b5cb Merge pull request #7 from yawn831/fixerror
fix: 修复sg报错
2025-06-24 23:03:59 +08:00
yawn831831 fbe837fca9 fix: 修复sg报错 2025-06-24 23:01:35 +08:00
crazytuzi 4aac4cc1fc Merge pull request #5 from crazytuzi/5.3
Ignore props
2024-11-17 19:02:58 +08:00
crazytuzi 4a3273ec31 Ignore props 2024-11-17 19:02:44 +08:00
crazytuzi fa2f24a764 Merge pull request #4 from Srkmn/5.5
Cross Version
2024-11-17 17:16:52 +08:00
Srkmn 90c5058a4c Cross Version 2024-11-17 17:15:24 +08:00
crazytuzi 66d28f5ff8 Merge pull request #2 from Srkmn/5.1
C# Source Code Generator
2024-11-17 16:41:41 +08:00
Srkmn 5c3d42462f Fix GetDependencyClasses For Set and Map 2024-11-17 16:40:26 +08:00
Srkmn 969682bc95 Source Code Generator 2024-11-17 14:30:55 +08:00
6 changed files with 925 additions and 1121 deletions

No files matched your search

+3
View File
@@ -72,3 +72,6 @@ Plugins/**/Intermediate/*
# Cache files for the editor to use # Cache files for the editor to use
DerivedDataCache/* DerivedDataCache/*
obj/*
SourceCodeGenerator.ubtplugin.csproj.props
-980
View File
@@ -1,980 +0,0 @@
#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<UClass*> 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<FProperty> Property(Class, EFieldIterationFlags::None); Property; ++Property)
{
if (CanExportProperty(*Property))
{
GetDependencyClasses(*Property, DependencyClasses);
ExportProperty(BodyBuilder, Class, *Property);
}
}
for (TFieldIterator<UFunction> Function(Class, EFieldIterationFlags::None); Function; ++Function)
{
if (CanExportFunction(*Function))
{
for (TFieldIterator<FProperty> 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<UPackage*, TArray<FString>> 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<FMapProperty>())
{
return false;
}
}
for (TFieldIterator<FProperty> 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<FProperty> 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 ObjectProperty = CastField<FObjectProperty>(Property))
{
if (!IsClassTypeSupported(ObjectProperty->PropertyClass))
{
return false;
}
}
if (const auto ObjectPtrProperty = CastField<FObjectPtrProperty>(Property))
{
if (!IsClassTypeSupported(ObjectPtrProperty->PropertyClass))
{
return false;
}
}
if (const auto InterfaceProperty = CastField<FInterfaceProperty>(Property))
{
if (InterfaceProperty->InterfaceClass == UInterface::StaticClass())
{
return false;
}
}
if (Property->IsA<FEnumProperty>())
{
return false;
}
if (Property->IsA<FByteProperty>())
{
return false;
}
if (Property->IsA<FDelegateProperty>())
{
return false;
}
if (Property->IsA<FMulticastDelegateProperty>())
{
return false;
}
if (Property->IsA<FFieldPathProperty>())
{
return false;
}
if (const auto StructProperty = CastField<FStructProperty>(Property))
{
if (!StructProperty->HasNoOpConstructor())
{
return false;
}
}
if (const auto ArrayProperty = CastField<FArrayProperty>(Property))
{
return IsPropertyTypeSupported(ArrayProperty->Inner);
}
if (const auto SetProperty = CastField<FSetProperty>(Property))
{
return IsPropertyTypeSupported(SetProperty->ElementProp);
}
if (const auto MapProperty = CastField<FMapProperty>(Property))
{
return IsPropertyTypeSupported(MapProperty->KeyProp) &&
IsPropertyTypeSupported(MapProperty->ValueProp);
}
auto bIsBitField = false;
if (const auto BoolProperty = CastField<FBoolProperty>(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<FStrProperty>())
{
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<FStrProperty>())
{
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<FProperty> 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<FString>{"));
auto bIsConst = false;
for (TFieldIterator<FProperty> 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<FProperty> 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<FName, FString>* 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<FObjectProperty>()
|| Param->IsA<FClassPtrProperty>()
|| Param->IsA<FObjectPtrProperty>())
{
OutString = TEXT("nullptr");
}
else
{
FString MetaData;
if (const auto CppDefaultValue = MetaDataMap->Find(CppKey))
{
if (!CppDefaultValue->IsEmpty())
{
MetaData = *CppDefaultValue;
}
}
if (const auto StructProperty = CastField<FStructProperty>(Param))
{
if (StructProperty->Struct == TBaseStructure<FVector>::Get())
{
TArray<FString> Value;
MetaData.ParseIntoArray(Value, TEXT(","));
if (Value.Num() == 3)
{
OutString = FString::Printf(TEXT(
"FVector(%lf, %lf, %lf)"
),
TCString<TCHAR>::Atod(*Value[0]),
TCString<TCHAR>::Atod(*Value[1]),
TCString<TCHAR>::Atod(*Value[2])
);
}
}
else if (StructProperty->Struct == TBaseStructure<FVector2D>::Get())
{
if (FVector2D Value; Value.InitFromString(MetaData))
{
OutString = FString::Printf(TEXT(
"FVector2D(%lf, %lf)"
),
Value.X,
Value.Y
);
}
}
else if (StructProperty->Struct == TBaseStructure<FRotator>::Get())
{
TArray<FString> Value;
MetaData.ParseIntoArray(Value, TEXT(","));
if (Value.Num() == 3)
{
OutString = FString::Printf(TEXT(
"FRotator(%lf, %lf, %lf)"
),
TCString<TCHAR>::Atod(*Value[0]),
TCString<TCHAR>::Atod(*Value[1]),
TCString<TCHAR>::Atod(*Value[2])
);
}
}
else if (StructProperty->Struct == TBaseStructure<FLinearColor>::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<FStrProperty>())
{
OutString = FString::Printf(TEXT(
"FString(\"%s\")"
),
*MetaData);
}
else if (Param->IsA<FNameProperty>())
{
OutString = FString::Printf(TEXT(
"TEXT(\"%s\")"
),
*MetaData);
}
else if (Param->IsA<FTextProperty>())
{
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<FArrayProperty>(Property))
{
String.Appendf(TEXT(
"TArray<%s>"
),
*ArrayProperty->Inner->GetCPPType()
);
}
else if (const auto SetProperty = CastField<FSetProperty>(Property))
{
String.Appendf(TEXT(
"TSet<%s>"
),
*SetProperty->ElementProp->GetCPPType()
);
}
else if (const auto MapProperty = CastField<FMapProperty>(Property))
{
String.Appendf(TEXT(
"TMap<%s, %s>"
),
*MapProperty->KeyProp->GetCPPType(),
*MapProperty->ValueProp->GetCPPType()
);
}
else if (const auto InterfaceProperty = CastField<FInterfaceProperty>(Property))
{
String.Appendf(TEXT(
"TScriptInterface<I%s>"
),
*InterfaceProperty->InterfaceClass->GetName()
);
}
else
{
String.Append(Property->GetCPPType());
}
}
void FSourceCodeGeneratorModule::GetDependencyClasses(const FProperty* Property, TSet<UClass*>& DependencyClasses)
{
if (const auto ClassProperty = CastField<FClassProperty>(Property))
{
if (ClassProperty->MetaClass != nullptr)
{
DependencyClasses.Add(ClassProperty->MetaClass);
}
}
else if (const auto ObjectProperty = CastField<FObjectProperty>(Property))
{
if (ObjectProperty->PropertyClass != nullptr)
{
DependencyClasses.Add(ObjectProperty->PropertyClass);
}
}
else if (const auto ObjectPtrProperty = CastField<FObjectPtrProperty>(Property))
{
if (ObjectPtrProperty->PropertyClass != nullptr)
{
DependencyClasses.Add(ObjectPtrProperty->PropertyClass);
}
}
else if (const auto SoftClassProperty = CastField<FSoftClassProperty>(Property))
{
if (SoftClassProperty->MetaClass != nullptr)
{
DependencyClasses.Add(SoftClassProperty->MetaClass);
}
}
else if (const auto SoftObjectProperty = CastField<FSoftObjectProperty>(Property))
{
if (SoftObjectProperty->PropertyClass != nullptr)
{
DependencyClasses.Add(SoftObjectProperty->PropertyClass);
}
}
else if (const auto ArrayProperty = CastField<FArrayProperty>(Property))
{
if (ArrayProperty->Inner != nullptr)
{
GetDependencyClasses(ArrayProperty->Inner, DependencyClasses);
}
}
else if (const auto SetProperty = CastField<FSetProperty>(Property))
{
if (SetProperty->ElementProp != nullptr)
{
GetDependencyClasses(SetProperty->ElementProp, DependencyClasses);
}
}
else if (const auto MapProperty = CastField<FMapProperty>(Property))
{
if (MapProperty->KeyProp != nullptr && MapProperty->ValueProp != nullptr)
{
GetDependencyClasses(MapProperty->KeyProp, DependencyClasses);
GetDependencyClasses(MapProperty->ValueProp, 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<FString, FString>& Plugins)
{
const auto Suffix = FString(TEXT("*.uplugin"));
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
{
TArray<FString> 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<FString, FString>& Modules)
{
const auto Suffix = FString(TEXT("*.Build.cs"));
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
{
TArray<FString> 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<FString>& Modules)
{
const auto Suffix = FString(TEXT("*.Build.cs"));
if (auto& FileManager = IFileManager::Get(); FileManager.DirectoryExists(*InPathName))
{
TArray<FString> 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)
-101
View File
@@ -1,101 +0,0 @@
#pragma once
#include "CoreMinimal.h"
#include "IScriptGeneratorPluginInterface.h"
class FSourceCodeGeneratorModule : public IScriptGeneratorPluginInterface
{
private:
static constexpr auto LargeInitialBufferSizes = 256 * 1024;
static constexpr auto SmallInitialBufferSizes = 1 * 1024;
typedef TStringBuilder<LargeInitialBufferSizes> FBigStringBuilder;
typedef TStringBuilder<SmallInitialBufferSizes> FSmallStringBuilder;
public:
/** IModuleInterface implementation */
virtual void StartupModule() override;
virtual void ShutdownModule() override;
public:
/** IScriptGeneratorPluginInterface implementation */
virtual FString GetGeneratedCodeModuleName() const override;
virtual bool SupportsTarget(const FString& TargetName) const override;
virtual bool ShouldExportClassesForModule(const FString& ModuleName, EBuildModuleType::Type ModuleType,
const FString& ModuleGeneratedIncludeDirectory) const override;
virtual void Initialize(const FString& RootLocalPath, const FString& RootBuildPath,
const FString& OutputDirectory, const FString& IncludeBase) override;
virtual void ExportClass(UClass* Class, const FString& SourceHeaderFilename,
const FString& GeneratedHeaderFilename, bool bHasChanged) override;
virtual void FinishExport() override;
virtual FString GetGeneratorName() const override;
private:
static bool CanExportClass(const UClass* Class);
static bool CanExportFunction(const UFunction* Function);
static bool CanExportProperty(const FProperty* Property);
static void ExportFunction(FBigStringBuilder& StringBuilder, const UClass* Class, const UFunction* Function);
static void ExportProperty(FBigStringBuilder& StringBuilder, const UClass* Class, const FProperty* Property);
static bool IsClassTypeSupported(const UClass* Class);
static bool IsPropertyTypeSupported(const FProperty* Property);
static FString GetReturnPropertySignature(const FProperty* Property);
static FString GetParamPropertySignature(const FProperty* Property);
static FString GetFunctionSignature(const UClass* Class, const UFunction* Function);
static FString GetFunctionParamName(const UFunction* Function);
static FString GetFunctionDefaultValue(const UFunction* Function);
static bool FindDefaultValue(const TMap<FName, FString>* MetaMap, const FProperty* Param, FString& String);
static void GetPropertySignature(const FProperty* Property, FString& String);
static void GetDependencyClasses(const FProperty* Property, TSet<UClass*>& DependencyClasses);
static void SaveIfChanged(const FString& FileName, const FString& String);
FString GetHeaderFile(UClass* Class);
static FString GenerateInclude(const FString& FileName);
FString GetInclude(UClass* Class);
static void GetPlugins(const FString& InPathName, TMap<FString, FString>& Plugins);
static void GetModules(const FString& InPathName, TMap<FString, FString>& Modules);
static void GetModules(const FString& InPathName, TSet<FString>& Modules);
private:
FString OutputPath;
TArray<FString> ExportModules;
TSet<FString> Project;
TMap<FString, FString> HeaderPath;
TArray<UClass*> ExportClasses;
const FString BindingSuffix = FString(TEXT(".binding.inl"));
const FString HeaderSuffix = FString(TEXT(".header.inl"));
};
-40
View File
@@ -1,40 +0,0 @@
using UnrealBuildTool;
namespace UnrealBuildTool.Rules
{
public class SourceCodeGenerator : ModuleRules
{
public SourceCodeGenerator(ReadOnlyTargetRules Target) : base(Target)
{
bEnforceIWYU = false;
PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
PublicIncludePaths.AddRange(
new string[]
{
"Programs/UnrealHeaderTool/Public",
}
);
PrivateIncludePaths.AddRange(
new string[]
{
"SourceCodeGenerator/Private",
}
);
PrivateDependencyModuleNames.AddRange(
new string[]
{
"Core",
"CoreUObject",
}
);
PublicDefinitions.Add("HACK_HEADER_GENERATOR=1");
}
}
}
+873
View File
@@ -0,0 +1,873 @@
// Copyright Epic Games, Inc. All Rights Reserved.
using System;
using System.Collections.Concurrent;
using EpicGames.Core;
using EpicGames.UHT.Types;
using EpicGames.UHT.Utils;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EpicGames.UHT.Tables;
using UnrealBuildTool;
namespace SourceCodeGeneratorUbtPlugin
{
[UnrealHeaderTool]
internal class SourceCodeGenerator
{
[UhtExporter(Name = "UnrealCSharp", Description = "UnrealCSharp Source Code Generator",
Options = UhtExporterOptions.Default, ModuleName = "UnrealCSharp")]
private static void SourceCodeGeneratorExporter(IUhtExportFactory factory)
{
var SettingFilePath = Path.Combine(new string(factory.Session.ProjectDirectory),
"Config",
"DefaultUnrealCSharpEditorSetting.ini");
if (File.Exists(SettingFilePath))
{
var SettingConfigFile = new ConfigFile(new FileReference(SettingFilePath));
if (SettingConfigFile.TryGetSection("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting",
out var SettingConfigSection))
{
var SettingConfigHierarchySection = new ConfigHierarchySection(
new List<ConfigFileSection> { SettingConfigSection });
if (SettingConfigHierarchySection.TryGetValue("bEnableExport",
out var Value))
{
if (bool.Parse(Value.ToLower()))
{
new SourceCodeGenerator(factory).Generate();
}
}
}
}
}
private readonly IUhtExportFactory Factory;
private UhtSession Session => Factory.Session;
private ConcurrentQueue<UhtClass> ExportClasses = new();
private HashSet<string> Project = new();
private Dictionary<string, string> HeaderPath = new();
private const string BindingSuffix = ".binding.inl";
private const string HeaderSuffix = ".header.inl";
private readonly HashSet<string> ClassBlacklist = new();
private const string GeneratedHeaderComment =
"/*===========================================================================\n" +
" Generated code exported from UnrealCSharp SourceCodeGenerator.\n" +
" DO NOT modify this manually!\n" +
"===========================================================================*/\n" +
"\n";
public SourceCodeGenerator(IUhtExportFactory factory)
{
Factory = factory;
}
/// <summary>
/// Export all the classes in all the packages
/// </summary>
private void Generate()
{
if (Session is { EngineDirectory: not null, ProjectDirectory: not null })
{
GetModules(Path.GetFullPath(Path.Combine(Session.ProjectDirectory, "Source/")), Project);
GetModules(Path.GetFullPath(Path.Combine(Session.ProjectDirectory, "Source/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.ProjectDirectory, "Plugins/")), HeaderPath);
GetPlugins(Path.GetFullPath(Path.Combine(Session.ProjectDirectory, "Plugins/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Source/Developer/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Source/Editor/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Source/Programs/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Source/Runtime/")), HeaderPath);
GetModules(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Plugins/")), HeaderPath);
GetPlugins(Path.GetFullPath(Path.Combine(Session.EngineDirectory, "Plugins/")), HeaderPath);
}
var SettingFilePath = Path.Combine(new string(Session.ProjectDirectory),
"Config",
"DefaultUnrealCSharpEditorSetting.ini");
var ExportModule = new List<string>();
if (File.Exists(SettingFilePath))
{
var SettingConfigFile = new ConfigFile(new FileReference(SettingFilePath));
if (SettingConfigFile.TryGetSection("/Script/UnrealCSharpCore.UnrealCSharpEditorSetting",
out var SettingConfigSection))
{
var SettingConfigHierarchySection = new ConfigHierarchySection(
new List<ConfigFileSection> { SettingConfigSection });
if (SettingConfigHierarchySection.TryGetValues("ExportModule",
out var Values))
{
foreach (var value in Values)
{
ExportModule.Add(value);
}
}
if (SettingConfigHierarchySection.TryGetValues("ClassBlacklist", out var BlacklistValues))
{
foreach (var value in BlacklistValues)
{
if (!string.IsNullOrEmpty(value))
{
ClassBlacklist.Add(value);
}
}
}
}
}
List<Task?> tasks = new();
foreach (var module in Session.Modules)
{
if (module.Module.ModuleType != UHTModuleType.EngineRuntime &&
module.Module.ModuleType != UHTModuleType.GameRuntime)
{
continue;
}
if (ExportModule.All(x =>
string.Compare(x, module.Module.Name, StringComparison.OrdinalIgnoreCase) != 0))
{
continue;
}
QueueClassExports(module.ScriptPackage, tasks);
}
var waitTasks = tasks.Where(Task => Task != null).Cast<Task>().ToArray();
if (waitTasks.Length > 0)
{
Task.WaitAll(waitTasks);
}
Finish();
}
/// <summary>
/// Collect the classes to be exported for the given package and type
/// </summary>
/// <param name="type">Type to test for exporting</param>
/// <param name="tasks">Collection of queued tasks</param>
private void QueueClassExports(UhtType type, List<Task?> tasks)
{
if (type is UhtClass classObj)
{
if (CanExportClass(classObj))
{
tasks.Add(Factory.CreateTask(_ => { ExportClass(classObj); }));
}
}
foreach (var child in type.Children)
{
QueueClassExports(child, tasks);
}
}
/// <summary>
/// Test to see if the given class should be exported
/// </summary>
/// <param name="classObj">Class to test</param>
/// <returns>True if the class should be exported, false if not</returns>
protected virtual bool CanExportClass(UhtClass classObj)
{
return !ClassBlacklist.Contains(classObj.SourceName) &&
!classObj.ClassFlags.HasAnyFlags(EClassFlags.Interface) &&
IsClassTypeSupported(classObj);
}
/// <summary>
/// Test to see if the given function should be exported
/// </summary>
/// <param name="function">Function to test</param>
/// <returns>True if the function should be exported</returns>
protected virtual bool CanExportFunction(UhtFunction function)
{
if (function.Outer is UhtClass classObj)
{
if (!classObj.ClassFlags.HasAnyFlags(EClassFlags.RequiredAPI) &&
!function.FunctionExportFlags.HasAnyFlags(UhtFunctionExportFlags.RequiredAPI))
{
return false;
}
}
else
{
return false;
}
if (function.FunctionType != UhtFunctionType.Function)
{
return false;
}
if (!function.FunctionFlags.HasAnyFlags(EFunctionFlags.Public))
{
return false;
}
if (function.Deprecated)
{
return false;
}
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.EditorOnly))
{
return false;
}
if (function.FunctionExportFlags.HasAnyFlags(UhtFunctionExportFlags.CustomThunk))
{
return false;
}
if (function is { HasReturnProperty: true, ReturnProperty: UhtMapProperty })
{
return false;
}
foreach (var child in function.Children)
{
if (child is UhtProperty property && !IsPropertyTypeSupported(property))
{
return false;
}
}
return true;
}
/// <summary>
/// Test to see if the given property should be exported
/// </summary>
/// <param name="property">Property to test</param>
/// <returns>True if the property should be exported</returns>
protected virtual bool CanExportProperty(UhtProperty property)
{
return !property.Deprecated &&
!property.PropertyFlags.HasAnyFlags(EPropertyFlags.EditorOnly) &&
property.PropertyFlags.HasAnyFlags(EPropertyFlags.NativeAccessSpecifierPublic) &&
IsPropertyTypeSupported(property);
}
private static bool IsClassTypeSupported(UhtClass classObj)
{
return !classObj.Deprecated &&
(classObj == classObj.Session.UObject ||
classObj == classObj.Session.UClass ||
classObj.ClassFlags.HasAnyFlags(EClassFlags.RequiredAPI | EClassFlags.MinimalAPI));
}
private static bool IsPropertyTypeSupported(UhtProperty property)
{
if (property is UhtObjectProperty objectProperty)
{
if (!IsClassTypeSupported(objectProperty.Class))
{
return false;
}
}
if (property is UhtInterfaceProperty interfaceProperty)
{
if (interfaceProperty.InterfaceClass == interfaceProperty.Session.IInterface)
{
return false;
}
}
if (property is UhtDelegateProperty or UhtMulticastDelegateProperty or UhtFieldPathProperty)
{
return false;
}
if (property is UhtStructProperty { ScriptStruct.HasNoOpConstructor: false })
{
return false;
}
if (property is UhtArrayProperty arrayProperty)
{
return IsPropertyTypeSupported(arrayProperty.ValueProperty);
}
if (property is UhtSetProperty setProperty)
{
return IsPropertyTypeSupported(setProperty.ValueProperty);
}
if (property is UhtMapProperty mapProperty)
{
return IsPropertyTypeSupported(mapProperty.KeyProperty) &&
IsPropertyTypeSupported(mapProperty.ValueProperty);
}
return property is { IsBitfield: false, IsStaticArray: false };
}
/// <summary>
/// Export the given class
/// </summary>
/// <param name="classObj">Class to export</param>
private void ExportClass(UhtClass classObj)
{
using BorrowStringBuilder borrower = new(StringBuilderCache.Big);
if (ExportClass(borrower.StringBuilder, classObj))
{
var fileName = Factory.MakePath(classObj.EngineName, BindingSuffix);
SaveIfChanged(fileName, borrower.StringBuilder);
ExportClasses.Enqueue(classObj);
}
}
private void Finish()
{
var packages = new Dictionary<UhtPackage, List<string>>();
foreach (var ExportClass in ExportClasses)
{
if (!packages.ContainsKey(ExportClass.Package))
{
packages.Add(ExportClass.Package, new List<string>());
}
packages[ExportClass.Package].AddUnique(ExportClass.EngineName);
}
foreach (var package in packages)
{
package.Value.Sort();
var borrow = new BorrowStringBuilder(StringBuilderCache.Big);
var builder = borrow.StringBuilder;
builder.Append(GeneratedHeaderComment);
builder.Append("#pragma once\r\n\r\n");
foreach (var value in package.Value)
{
builder.Append(GenerateInclude($"{value}{BindingSuffix}"));
}
var fileName = Factory.MakePath(package.Key.Module, HeaderSuffix);
SaveIfChanged(fileName, builder);
}
}
private bool ExportClass(StringBuilder builder, UhtClass classObj)
{
builder.Append(GeneratedHeaderComment);
builder.Append("#pragma once\r\n\r\n" +
"PRAGMA_DISABLE_DEPRECATION_WARNINGS\r\n\r\n");
var DependencyClasses = new HashSet<UhtClass> { classObj };
using BorrowStringBuilder bodyBorrower = new(StringBuilderCache.Big);
var bodyBuilder = bodyBorrower.StringBuilder;
bodyBuilder.AppendFormat("namespace\r\n" +
"{{\r\n" +
"\tstruct FRegister{0}\r\n" +
"\t{{\r\n" +
"\t\tFRegister{1}()\r\n" +
"\t\t{{\r\n" +
"\t\t\tTBindingClassBuilder<{2}>(NAMESPACE_BINDING)\r\n",
classObj.EngineName,
classObj.EngineName,
classObj.SourceName);
var bExportProperty = false;
foreach (var child in classObj.Children)
{
if (child is UhtProperty property && CanExportProperty(property))
{
GetDependencyClasses(property, DependencyClasses);
ExportProperty(bodyBuilder, classObj, property);
bExportProperty = true;
}
}
var bExportFunction = false;
foreach (var function in classObj.Functions.Reverse())
{
if (CanExportFunction(function))
{
foreach (var child in function.Children)
{
if (child is UhtProperty property)
{
GetDependencyClasses(property, DependencyClasses);
}
}
ExportFunction(bodyBuilder, classObj, function);
bExportFunction = true;
}
}
foreach (var DependencyClass in DependencyClasses)
{
builder.Append(GetInclude(DependencyClass));
}
builder.Append("\r\n");
bodyBuilder.Remove(bodyBuilder.Length - 2, 2);
bodyBuilder.AppendFormat(";\r\n" +
"\t\t}}\r\n" +
"\t}};\r\n" +
"\r\n" +
"\t[[maybe_unused]] FRegister{0} Register{1};\r\n" +
"}}\r\n\r\n",
classObj.EngineName,
classObj.EngineName);
builder.Append(bodyBuilder);
builder.Append("PRAGMA_ENABLE_DEPRECATION_WARNINGS\r\n");
return bExportProperty || bExportFunction;
}
private static void GetDependencyClasses(UhtProperty property, HashSet<UhtClass> DependencyClasses)
{
if (property is UhtClassProperty classProperty)
{
if (classProperty.MetaClass != null)
{
DependencyClasses.Add(classProperty.MetaClass);
}
}
else if (property is UhtObjectProperty objectProperty)
{
DependencyClasses.Add(objectProperty.Class);
}
else if (property is UhtSoftClassProperty softClassProperty)
{
if (softClassProperty.MetaClass != null)
{
DependencyClasses.Add(softClassProperty.MetaClass);
}
}
else if (property is UhtSoftObjectProperty softObjectProperty)
{
DependencyClasses.Add(softObjectProperty.Class);
}
else if (property is UhtArrayProperty arrayProperty)
{
GetDependencyClasses(arrayProperty.ValueProperty, DependencyClasses);
}
else if (property is UhtSetProperty setProperty)
{
GetDependencyClasses(setProperty.ValueProperty, DependencyClasses);
}
else if (property is UhtMapProperty mapProperty)
{
GetDependencyClasses(mapProperty.KeyProperty, DependencyClasses);
GetDependencyClasses(mapProperty.ValueProperty, DependencyClasses);
}
}
private static void ExportFunction(StringBuilder builder, UhtClass classObj, UhtFunction function)
{
builder.Append(
$"\t\t\t\t.Function(\"{function.SourceName}\", BINDING_OVERLOAD({GetFunctionSignature(classObj, function)}, &{classObj.SourceName}::{function.SourceName}");
if (function.HasParameters)
{
builder.Append($", {GetFunctionParamName(function)}");
}
if (!function.FunctionFlags.HasAnyFlags(EFunctionFlags.Static))
{
builder.Append(", EFunctionInteract::Virtual");
}
if (function.HasParameters)
{
builder.Append(GetFunctionDefaultValue(function));
}
builder.Append("))\r\n");
}
private static void ExportProperty(StringBuilder builder, UhtClass classObj, UhtProperty property)
{
builder.Append(
$"\t\t\t\t.Property(\"{property.SourceName}\", BINDING_PROPERTY(&{classObj.SourceName}::{property.SourceName}))\r\n");
}
private static string GetParamPropertySignature(UhtProperty? property)
{
if (property == null)
{
return "void";
}
var builder = new StringBuilder();
var caps = property.PropertyCaps;
var isInterfaceProp = property is UhtInterfaceProperty;
var passCppArgsByRef = caps.HasAnyFlags(UhtPropertyCaps.PassCppArgsByRef);
var isConstParam = property.PropertyFlags.HasAnyFlags(EPropertyFlags.ConstParm) ||
(isInterfaceProp && !property.PropertyFlags.HasAnyFlags(EPropertyFlags.OutParm));
var isConstArgsByRef = property.ArrayDimensions == null && passCppArgsByRef &&
!property.PropertyFlags.HasAnyFlags(
EPropertyFlags.ConstParm | EPropertyFlags.OutParm);
var isOnConstClass = false;
if (property is UhtObjectProperty objectProperty)
{
isOnConstClass = objectProperty.Class.ClassFlags.HasAnyFlags(EClassFlags.Const) ||
objectProperty.MetaData.TryGetValue(UhtNames.NativeConst, out _);
}
var shouldHaveRef =
property.PropertyFlags.HasAnyFlags(EPropertyFlags.OutParm | EPropertyFlags.ReferenceParm);
var constAtTheBeginning = isOnConstClass || isConstArgsByRef || (isConstParam && !shouldHaveRef);
if (constAtTheBeginning)
{
builder.Append("const ");
}
if (property is UhtEnumProperty enumProperty)
{
builder.Append(enumProperty.Enum.CppType);
}
else
{
property.AppendText(builder, UhtPropertyTextType.ExportMember);
}
var fromConstClass = false;
if (property.Outer is UhtClass outerClass)
{
fromConstClass = outerClass.ClassFlags.HasAnyFlags(EClassFlags.Const);
}
var constAtTheEnd = (fromConstClass || (isConstParam && shouldHaveRef)) && !constAtTheBeginning;
if (constAtTheEnd)
{
builder.Append(" const");
}
shouldHaveRef = passCppArgsByRef;
if (shouldHaveRef ||
property.ArrayDimensions == null &&
(passCppArgsByRef ||
property.PropertyFlags.HasAnyFlags(EPropertyFlags.OutParm | EPropertyFlags.ReferenceParm)))
{
if (property is UhtArrayProperty or UhtStrProperty or UhtTextProperty or UhtInterfaceProperty
or UhtSoftObjectProperty)
{
if (property.RefQualifier != UhtPropertyRefQualifier.None)
{
builder.Append('&');
}
}
else
{
builder.Append('&');
}
}
return builder.ToString();
}
private static string GetReturnPropertySignature(UhtProperty? property)
{
if (property == null)
{
return "void";
}
var builder = new StringBuilder();
if (property.PropertyFlags.HasAnyFlags(EPropertyFlags.ConstParm) ||
property.RefQualifier == UhtPropertyRefQualifier.ConstRef)
{
builder.Append("const ");
}
if (property is UhtEnumProperty enumProperty)
{
builder.Append(enumProperty.Enum.CppType);
}
else
{
property.AppendText(builder, UhtPropertyTextType.ExportMember);
}
if (property.RefQualifier == UhtPropertyRefQualifier.ConstRef)
{
builder.Append('&');
}
return builder.ToString();
}
private static string GetFunctionSignature(UhtClass classObj, UhtFunction function)
{
var builder = new StringBuilder();
builder.Append(GetReturnPropertySignature(function.ReturnProperty));
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.Static))
{
builder.Append("(*)");
}
else
{
builder.Append($"({classObj.SourceName}::*)");
}
builder.Append('(');
foreach (var child in function.Children)
{
if (child is UhtProperty property)
{
if (!property.PropertyFlags.HasAnyFlags(EPropertyFlags.ReturnParm))
{
builder.Append(GetParamPropertySignature(property));
builder.Append(", ");
}
}
}
if (function.HasParameters)
{
builder.Remove(builder.Length - 2, 2);
}
builder.Append(')');
if (function.FunctionFlags.HasAnyFlags(EFunctionFlags.Const))
{
builder.Append("const");
}
return builder.ToString();
}
private static string GetFunctionParamName(UhtFunction function)
{
var builder = new StringBuilder();
builder.Append("TArray<FString>{");
foreach (var child in function.Children)
{
if (child is UhtProperty property)
{
if (!property.PropertyFlags.HasAnyFlags(EPropertyFlags.ReturnParm))
{
builder.Append($"\"{property.SourceName}\", ");
}
}
}
builder.Remove(builder.Length - 2, 2);
builder.Append('}');
return builder.ToString();
}
private static string GetFunctionDefaultValue(UhtFunction function)
{
if (!function.FunctionFlags.HasAnyFlags(EFunctionFlags.BlueprintCallable))
{
return "";
}
var builder = new StringBuilder();
foreach (var child in function.Children)
{
if (child is UhtProperty property)
{
if (!property.PropertyFlags.HasAnyFlags(EPropertyFlags.ReturnParm))
{
if (property.DefaultValueTokens != null)
{
builder.Append(", ");
var DefaultValue = new StringBuilder();
foreach (var ValueToken in property.DefaultValueTokens)
{
DefaultValue.Append(ValueToken.Value);
}
if (DefaultValue.ToString() == "NULL")
{
builder.Append("nullptr");
}
else
{
builder.Append(DefaultValue);
}
}
}
}
}
return builder.ToString();
}
private string GetHeaderFile(UhtClass classObj)
{
return Path.Combine(HeaderPath[classObj.Package.Module.ShortName], classObj.HeaderFile.FilePath);
}
private static string GenerateInclude(string file)
{
return $"#include \"{file}\"\r\n";
}
private string GetInclude(UhtClass classObj)
{
return GenerateInclude(GetHeaderFile(classObj));
}
private void SaveIfChanged(string file, StringBuilder builder)
{
if (File.Exists(file) && File.ReadAllText(file) == builder.ToString())
{
return;
}
Factory.CommitOutput(file, builder);
}
private void GetPlugins(string InPathName, Dictionary<string, string> Plugins)
{
var Suffix = "*.uplugin";
var DirectoryInfo = new DirectoryInfo(InPathName);
foreach (var Item in DirectoryInfo.GetFiles(Suffix))
{
if (Item.DirectoryName != null)
{
Plugins[Item.Name.Remove(Item.Name.Length - Suffix.Length + 1)] = Item.DirectoryName;
}
}
foreach (var Directories in DirectoryInfo.GetDirectories())
{
foreach (var Item in Directories.GetFiles(Suffix, SearchOption.AllDirectories))
{
if (Item.DirectoryName != null)
{
Plugins[Item.Name.Remove(Item.Name.Length - Suffix.Length + 1)] = Item.DirectoryName;
}
}
}
}
private void GetModules(string InPathName, Dictionary<string, string> Modules)
{
var Suffix = "*.Build.cs";
var DirectoryInfo = new DirectoryInfo(InPathName);
foreach (var Item in DirectoryInfo.GetFiles(Suffix))
{
if (Item.DirectoryName != null)
{
Modules[Item.Name.Remove(Item.Name.Length - Suffix.Length + 1)] = Item.DirectoryName;
}
}
foreach (var Directories in DirectoryInfo.GetDirectories())
{
foreach (var Item in Directories.GetFiles(Suffix, SearchOption.AllDirectories))
{
if (Item.DirectoryName != null)
{
Modules[Item.Name.Remove(Item.Name.Length - Suffix.Length + 1)] = Item.DirectoryName;
}
}
}
}
private void GetModules(string InPathName, HashSet<string> Modules)
{
var Suffix = "*.Build.cs";
var DirectoryInfo = new DirectoryInfo(InPathName);
foreach (var Item in DirectoryInfo.GetFiles(Suffix))
{
Modules.Add(Item.Name.Remove(Item.Name.Length - Suffix.Length + 1));
}
foreach (var Directories in DirectoryInfo.GetDirectories())
{
foreach (var Item in Directories.GetFiles(Suffix, SearchOption.AllDirectories))
{
if (Item.DirectoryName != null)
{
Modules.Add(Item.Name.Remove(Item.Name.Length - Suffix.Length + 1));
}
}
}
}
}
}
+49
View File
@@ -0,0 +1,49 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="SourceCodeGenerator.ubtplugin.csproj.props" Condition="Exists('SourceCodeGenerator.ubtplugin.csproj.props')" />
<Import Project="$(EngineDir)\Source\Programs\Shared\UnrealEngine.csproj.props" />
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Configuration Condition=" '$(Configuration)' == '' ">Development</Configuration>
<OutputType>Library</OutputType>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
<Configurations>Debug;Release;Development</Configurations>
<RootNamespace>SourceCodeGenerator</RootNamespace>
<AssemblyName>SourceCodeGenerator</AssemblyName>
<OutputPath>..\..\..\..\Binaries\DotNET\UnrealBuildTool\Plugins\SourceCodeGenerator\</OutputPath>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<DebugType>pdbonly</DebugType>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Development|AnyCPU' ">
<DefineConstants>$(DefineConstants);TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DefineConstants>$(DefineConstants);TRACE</DefineConstants>
<Optimize>true</Optimize>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DefineConstants>$(DefineConstants);DEBUG;TRACE</DefineConstants>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="EpicGames.Build">
<HintPath>$(EngineDir)\Binaries\DotNET\UnrealBuildTool\EpicGames.Build.dll</HintPath>
</Reference>
<Reference Include="EpicGames.Core">
<HintPath>$(EngineDir)\Binaries\DotNET\UnrealBuildTool\EpicGames.Core.dll</HintPath>
</Reference>
<Reference Include="EpicGames.UHT">
<HintPath>$(EngineDir)\Binaries\DotNET\UnrealBuildTool\EpicGames.UHT.dll</HintPath>
</Reference>
<Reference Include="UnrealBuildTool">
<HintPath>$(EngineDir)\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll</HintPath>
</Reference>
</ItemGroup>
</Project>